/*
 * @(#) Config.cpp
 *
 * This file is part of webCDwriter - Network CD Writing.
 *
 * Copyright (C) 1999-2004 Jörg P. M. Haeger
 *
 * webCDwriter is free software. See CDWserver.cpp for details.
 *
 * Jörg Haeger, 25.05.1999
 */

#include <stdlib.h>
#include <sys/types.h>
#include "Accounts.h"
#include "Config.h"
#include "FileInputStream.h"
#include "PrintWriter.h"
#include "String.h"
#include "StringTokenizer.h"
#include "Version.h"

class DevicesParser {
	private: StringTokenizer tokenizer;
	private: String dev, raw;

	public: DevicesParser(String &devices)
			: tokenizer(devices.getBytes()) {
	}

	public: String *getCdrecordDevArg() {
		return new String(dev);
	}

	public: String *getRawDevice() {
		return new String(raw);
	}

	public: bool next() {
		try {
			dev = tokenizer.nextToken(" )(;");
			if (dev.equals(","))
				dev = tokenizer.nextToken(" (;");
			raw = tokenizer.nextToken(" ;)");
		}
		catch (NoSuchElementException *e) {
			delete e;
			return false;
		}
		return true;
	}
};

Config::Config() {
	accountsFile = "/etc/CDWserver/accounts";
	debugCache = "";
	logLevelCache = 0;
	mimeTypes = "";
	supportFormCache = false;
}

void Config::cdrecordInq(int writerNo) {
	String info = cdrecordInq(getWriter(writerNo));
	if (info.length() > 0)
		writers[writerNo].info = info;
}

String *Config::cdrecordInq(const char *dev) {
	Process p(getCdrecordPath());
	char str[4 * 1024];
	sprintf(str, "dev=%s", dev);
	p.addArg(str);
	p.addArg("-inq");
	p.start();

	String info = "";

	int errorCode = 0;
	str[0] = 0;
	while (1) {
		const char *lineStr = p.readLine();
		if (lineStr == NULL)
			break;

		String line = lineStr;
		if (line.startsWith("Vendor_info")
				|| line.startsWith("Identifikation")
				|| line.startsWith("Revision")) {
			int p = line.indexOf("'");
			if (p >= 0 && p + 1 < line.length()) {
				line = line.substring(p + 1);
				p = line.indexOf("'");
				while (p > 0 && line.charAt(p - 1) <= 32)
					p--;
				if (p > 0) {
					if (info.length() > 0)
						info = info + " ";
					info = info + line.substring(0, p);
				}
			}
		}
	}

	if (p.getResult() != 0)
		throw new Exception(S.e + "Error running cdrecord dev="
			+ dev + " -inq");

	return new String(info);
}

void Config::cdrecordScanbus() {
	hasReader = false;
	numOfWriters = 0;
	for (int i = 0; i < maxNumOfWriters; i++)
		writers[i].rawDevice = "";
	maxSpeed = 1;
	int numOfIdeCdDevices = 0;
	String kernelBoot = "";

	Process p3(getActiveValue("rootGatePath"));
	p3.addArg("-listIDEDevices");
	p3.start();
	while (1) {
		const char *lineStr = p3.readLine();
		if (lineStr == NULL)
			break;
		log.put(5, S.e + lineStr);

		String dev, media, driver;
		StringTokenizer tokenizer(lineStr, " \t");
		try {
			dev = tokenizer.nextToken();
			media = tokenizer.nextToken();
			driver = tokenizer.nextToken();
			if (media.equals("cdrom") && driver.startsWith("ide-cd")) {
				numOfIdeCdDevices++;
				File f = new File(dev);
				if (kernelBoot.length() > 0)
					kernelBoot = kernelBoot + " ";
				kernelBoot = kernelBoot + f.getName() + "=ide-scsi";
				log.put(3, S.e + media + ": " + dev);
			}
		}
		catch (NoSuchElementException *e) {
			delete e;
		}
	}

	bool cdrecordError = false;
	String devices = getActiveValue("devices");
	bool autoDetect = devices.indexOf("auto") >= 0;
	if (autoDetect) {
		Process p(getCdrecordPath());
		p.addArg("-scanbus");
		p.start();
		cdrecordScanbus2(p);
		cdrecordError = p.getResult() != 0;
	}
	else {
		DevicesParser parser(devices);
		while (parser.next()) {
			String dev = parser.getCdrecordDevArg();
			String model = cdrecordInq(dev.getBytes());
			String rawDevice = parser.getRawDevice();
			String line = "\'";
			line = line + model;
			cdrecordScanbus3(dev, rawDevice, line);
		}
	}

	if (autoDetect && numOfIdeCdDevices > 0) {
		cdrecordError = false;
		int i = numOfWriters;
		Process p2(getCdrecordPath());
		p2.addArg(S.e + "dev=" + ATAPI);
		p2.addArg("-scanbus");
		p2.start();
		cdrecordScanbus2(p2, ATAPI);

		if (getActiveBoolean("ATAPIRawDeviceSupport")) {
			Process p3(getActiveValue("rootGatePath"));
			p3.addArg("-listIDEDevices");
			p3.start();
			while (1) {
				const char *lineStr = p3.readLine();
				if (lineStr == NULL)
					break;
				log.put(5, S.e + lineStr);

				String dev, media, driver;
				StringTokenizer tokenizer(lineStr, " \t");
				try {
					dev = tokenizer.nextToken();
					media = tokenizer.nextToken();
					driver = tokenizer.nextToken();
					if (media.equals("cdrom")
							&& driver.startsWith("ide-cd")) {
						writers[i].dev = dev;
						writers[i].rawDevice = dev;
						i++;
						log.put(3, S.e + ATAPI + ": " + dev);
					}
				}
				catch (NoSuchElementException *e) {
					delete e;
				}
			}
		}
	}

	// read only devices first, fast CD-R writers first
	for (int i = 0; i < numOfWriters - 1; i++) {
		for (int j = i + 1; j < numOfWriters; j++) {
			Device d1 = writers[i], d2 = writers[j];
			bool readonly1 = !d1.CDR && !d1.CDRW
				&& !d1.DVDMinusR && !d1.DVDMinusRW
				&& !d1.DVDPlusR && !d1.DVDPlusRW;
			bool readonly2 = !d2.CDR && !d2.CDRW
				&& !d2.DVDMinusR && !d2.DVDMinusRW
				&& !d2.DVDPlusR && !d2.DVDPlusRW;
			String str = d1.id;
			str = str + ".CD-R";
			int speed1 = getActiveInt(str.getBytes());
			str = d2.id;
			str = str + ".CD-R";
			int speed2 = getActiveInt(str.getBytes());
			if ((!readonly1 && readonly2)
					|| (readonly1 == readonly2 && speed1 < speed2)) {
				Device buf = writers[i];
				writers[i] = writers[j];
				writers[j] = buf;
			}
		}
	}

	log.put(2, S.e + "writers = " + numOfWriters);

	if (cdrecordError || numOfWriters == 0) {
		String str;
		if (kernelBoot.length() == 0)
			str = str + "Install a CD- or DVD-writer or try the "
				+ "<a href=\"/config/general.html\">"
				+ "simulation mode</h3></a>!";
		else
			str = str + "Add \"" + kernelBoot
				+ "\" to your kernel boot parameters!";
		if (cdrecordError)
			throw new Exception(S.e
				+ "Error running \"cdrecord -scanbus\"", S.e + str);
		else
			throw new Exception(S.e
				+ "Cannot autodetect a writer.", S.e + str);
	}

	for (int i = 0; i < numOfWriters; i++) {
		if (writers[i].rawDevice.length() == 0)
			throw new Exception(S.e + "Cannot find writer "
				+ writers[i].dev);

		String name = "cdimage";
		name = name + i + ".iso";
		File f = new File(getSpoolDir(), name);
		writers[i].imageFile = f.getPath();

//		writers[i].info = "unknown";

		log.put(2, S.e + "writer = [" + writers[i].dev
			+ "], writerRawDevice = [" + writers[i].rawDevice
			+ "]");
	}
}

void Config::cdrecordScanbus2(Process &p, const char *driver) {
	int numOfCDROMs = 0;
	while (1) {
		const char *lineStr = p.readLine();
		if (lineStr == NULL)
			break;

		if (strstr(lineStr, "devname: ") == lineStr
				&& strstr(lineStr, "ATA") != NULL
				&& strlen(driver) == 0)
			break;

		if (strstr(lineStr, "CD-ROM") == NULL
				&& strstr(lineStr, "WORM") == NULL)
			continue;

		log.put(lineStr);
		int bus, ID, LUN;
		if (sscanf(lineStr, "%d,%d,%d", &bus, &ID, &LUN) != 3)
			continue;

		String dev = driver;
		if (dev.length() > 0)
			dev = dev + ":";
		dev = dev + bus + "," + ID + "," + LUN;

		String line = lineStr;
		String rawDevice = getRawDevice(bus, ID, LUN, numOfCDROMs, driver);
		if (cdrecordScanbus3(dev, rawDevice, line) >= 0)
			numOfCDROMs++;
	}
}

int Config::cdrecordScanbus3(
			String &dev, String &rawDevice, String &line) {
		int p0 = line.indexOf("'");
		if (p0 < 0)
			return -1;

		// create the device name
		StringBuffer id = "device.";
		StringBuffer modelBuf = "";
		for (int i = p0 + 1, j = 0; i < line.length() && j < 3; i++)
			if (line.charAt(i) == '\'')
				j++;
			else {
				if (line.charAt(i) > 32 && line.charAt(i) != '=')
					id.append((char)line.charAt(i));
				modelBuf.append((char)line.charAt(i));
			}
		String device = new String(id);
		log.put(5, S.e + device);
		String model = modelBuf;

		// avoid duplicates by adding a number
		String deviceTmp = device;
		deviceTmp = deviceTmp + "-1";
		int count = 1;
		for (int i = 0; i < numOfWriters; i++)
			if (writers[i].id.equals(deviceTmp)) {
				if (dev.startsWith(ATAPI)
						&& !writers[i].dev.startsWith(ATAPI)) {
					// probably already a SCSI device -> skip
					count = -1;
					break;
				}
				count++;
				deviceTmp = device;
				deviceTmp = deviceTmp + "-" + count;
				i = 0;
			}
		if (count < 0)
			// skip SCSI device
			return -1;
		device = deviceTmp;

		bool notMounted = true;
		try {
			String str = rawDevice;
			str = str + " ";
			File file = new File("/proc/mounts");
			FileInputStream in = new FileInputStream(file);
			StringBuffer sb = new StringBuffer(file.getSize());
			while (true) {
				char buf[4094];
				int n = in.read(buf, 0, sizeof buf);
				if (n <= 0)
					break;
				sb.append(buf, 0, n);
			}
			if (sb.indexOf(str.getBytes()) >= 0)
				notMounted = false;
		}
		catch (Exception *e) {
			delete e;
		}

		String str = device;
		str = str + ".CD";
		if (notMounted && search(str.getBytes()) < 0)
			add(str.getBytes(), 16);
		if (notMounted && getActiveInt(str.getBytes()) > 0)
			hasReader = true;

		// CD/DVD writer autodetection
		Process prcap(getCdrecordPath());
		prcap.addArg(S.e + "dev=" + dev);
		prcap.addArg("-prcap");
		prcap.start();

		while (true) {
			const char *lineStr = prcap.readLine();
			if (lineStr == NULL)
				break;

			if (!notMounted)
				continue;

			String line = lineStr;
			if (line.indexOf("Does write CD-R ") >= 0)
				setDevice(device, "CD-R", 8);
			if (line.indexOf("Does write CD-RW") >= 0)
				setDevice(device, "CD-RW", 4);

			if (getActiveBoolean(DVDSUPPORT)) {
				if (line.indexOf("Does write DVD-R ") >= 0) {
					setDevice(device, "DVD-R", 2);
					setDevice(device, "DVD-RW", 2);
				}
				if (model.indexOf("DVD+") >= 0) {
					setDevice(device, "DVD+R", 2);
					setDevice(device, "DVD+RW", 2);
				}
			}
		}

		writers[numOfWriters].dev = dev;
		writers[numOfWriters].id = device;
		writers[numOfWriters].info = model;
		writers[numOfWriters].rawDevice = rawDevice;
		writers[numOfWriters].CD = getMaxSpeed(numOfWriters, "CD");
		writers[numOfWriters].CDR = getMaxSpeed(numOfWriters, "CD-R");
		writers[numOfWriters].CDRW = getMaxSpeed(numOfWriters, "CD-RW");
		writers[numOfWriters].DVDMinusR
			= getMaxSpeed(numOfWriters, "DVD");
		writers[numOfWriters].DVDMinusR
			= getMaxSpeed(numOfWriters, "DVD-R");
		writers[numOfWriters].DVDMinusRW
			= getMaxSpeed(numOfWriters, "DVD-RW");
		writers[numOfWriters].DVDPlusR
			= getMaxSpeed(numOfWriters, "DVD+R");
		writers[numOfWriters].DVDPlusRW
			= getMaxSpeed(numOfWriters, "DVD+RW");

		String supports = "";
		if (writers[numOfWriters].CDR > 0)
			supports = supports + "CD-R, ";
		if (writers[numOfWriters].CDRW > 0)
			supports = supports + "CD-RW, ";
		if (writers[numOfWriters].DVDMinusR > 0)
			supports = supports + "DVD-R, ";
		if (writers[numOfWriters].DVDMinusRW > 0)
			supports = supports + "DVD-RW, ";
		if (writers[numOfWriters].DVDPlusR > 0)
			supports = supports + "DVD+R, ";
		if (writers[numOfWriters].DVDPlusRW > 0)
			supports = supports + "DVD+RW, ";

		if (supports.length() < 2)
			writers[numOfWriters].supports = "-";
		else
			writers[numOfWriters].supports
				= supports.substring(0, supports.length() - 2);
		numOfWriters++;

		try {
			String str = device;
			str = str + ".CD-R";
			int speed = getActiveInt(str.getBytes());
			if (speed > maxSpeed)
				maxSpeed = speed;
		}
		catch (Exception *e) {
			delete e;
		}

		return 0;
}

	void Config::cdrecordVersion() {
		Process p(getCdrecordPath());
		p.addArg("-version");
		p.start();
		const char *lineStr = p.readLine();
		if (lineStr == NULL)
			throw new Exception("Cannot run cdrecord");

		char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
		if (sscanf(lineStr, "%s %s", str1, str2) != 2)
			throw new Exception("Cannot get version of cdrecord");
		cdrecordInfo = "";
		cdrecordInfo = cdrecordInfo + str1 + " " + str2;
		log.put(lineStr);

		if (!getActiveBoolean(SIMULATION))
			checkForResMgrPatch("cdrecord");

		if (!config.getActiveBoolean("tryPatchedCdrecord")) {
			String name = str1, version = str2;
			if (name.indexOf("DVD") >= 0
				|| name.indexOf("dvd") >= 0
				|| version.indexOf("dvd") >= 0)
				throw new Exception(S.e + "no original cdrecord",
					S.e + "/config/error/noOriginalCdrecord.html");
		}

		while (p.readLine() != NULL)
			;
		if (p.getResult() != 0)
			throw new Exception("Error running \"cdrecord -version\"");
	}

void Config::checkExecAsRoot(const char *execName) {
	struct stat statBuf;
	if (stat(execName, &statBuf) != 0)
		throw new Exception(S.e + "Cannot find " + execName);

	if (uid == 0)
		return;

	if (statBuf.st_uid != 0
			|| (statBuf.st_mode & S_ISUID) == 0
			|| (((statBuf.st_mode & S_IXGRP) == 0
					|| statBuf.st_gid != gid)
				&& (statBuf.st_mode & S_IXOTH) == 0))
		throw new Exception(S.e + "Cannot run " + execName + " as root",
			S.e + "Try\n<ul>\n"
				+ "<li>run<br>[chown root." + getGroup()
					+ " " + execName + "<br>\n"
					+ "chmod 4750 " + execName + "]</li></ul>");
}

	void Config::checkFile(const char *name, String &path) {
		if (uid == 0)
			return;

		if (File(path).isReadAndWritable(uid, gid) != 0)
			throw new Exception(S.e + "The user \"" + user
				+ "\" cannot read and/or write the " + name
				+ " \"" + path + "\"",
				S.e + "Try\n<ul>\n"
					+ "<li>run<br>[chown " + user
					+ " " + path + "]</li></ul>");
	}

	String *Config::createCheckbox(Device &device, const char *medium) {
		String str = device.id;
		str = str + "." + medium;
		String checked = "";
		try {
			if (getEditorBoolean(str))
				checked = " checked=\"checked\"";
		}
		catch (Exception *e) {
			delete e;
		}
		String input = "";
		input = input
			+ "<input type=\"hidden\""
				+ " name=\"" + str + "\""
				+ " value=\"off\">"
			+ "<input type=\"checkbox\""
				+ " name=\"" + str + "\""
				+ " value=\"on\""
				+ checked + ">\n";
		return new String(input);
	}

	String *Config::createSpeedbox(Device &device, const char *medium) {
		String str = device.id;
		str = str + "." + medium;
		String checked = "";
		int speed = 0;
		try {
			int index = indexOf(str.getBytes());
			Object &object = items[index]->editor;
			if (!object.instanceof("Integer"))
				throw new Exception("Integer expected");
			Integer *i = (Integer *)&object;
			speed = i->intValue();
		}
		catch (Exception *e) {
			delete e;
		}
		String select = "";
		select = select
			+ "<select name=\"" + str + "\" size=\"1\">";
		select = select
			+ "<option value=\"0\">-</option>";
		for (int i = 1; i <= 64; i++) {
			if ((i > 2 && i % 2 > 0)
					|| (i > 12 && i % 4 > 0)
					|| (i > 32 && str.endsWith(".DVD"))
					|| (i > 16 && str.indexOf(".DVD-") >= 0)
					|| (i > 16 && str.indexOf(".DVD+") >= 0))
				continue;
			select = select
				+ "<option";
			if (i == speed)
				select = select
					+ " selected";
			select = select
				+ " value=\"" + i + "\">" + i + "x</option>";
		}
		select = select
			+ "</select>\n";
		return new String(select);
	}

void Config::evaluate(
		int lineNo, const char *str1, const char *str2) {
	if (strcasecmp(str1, "maxSpeed") == 0
			|| strcasecmp(str1, "maxVerifySpeed") == 0)
		return;

	if (strcasecmp(str1, "maxOpenSessions") == 0
			&& strcasecmp(str2, "auto") == 0)
		str2 = "3";

	int index = search(str1);
	if (index >= 0) {
		if (items[index]->lineInConfig >= 0)
			throw new Exception(S.e
				+ str1 + " already set in line "
				+ items[index]->lineInConfig);
		items[index]->setConfig(str2);
		items[index]->lineInConfig = lineNo;
	}
	else
	if (strstr(str1, "device.") == str1)
		add(str1, Integer::getInt(str2));
	else
	if (strcasecmp(str1, "cdrecordDevArg") == 0
		|| strcasecmp(str1, "CDwriter") == 0
		|| strcasecmp(str1, "CDwriters") == 0)
//		parseCDROMs(str2, writers, numOfWriters);
//		throw new Exception(S.e + "CDwriters");
		log.put(2, "CDwriters line found -> ignored");
	else
	if (strcasecmp(str1, "CDROM") == 0
		|| strcasecmp(str1, "CDROMs") == 0)
//		parseCDROMs(str2, CDROMs, numOfReaders);
		log.put(2, "CDROMs line found");
	else
	if (strcasecmp(str1, "eMailOnError") == 0)
		eMailOnError = strDup(str2);
	else
	if (strcasecmp(str1, "group") == 0) {
		if (!userSave) {
			gid = toGid(str2);
			group = strDup(str2);
			log.put(2, S.e + "group = " + group + ", gid = " + gid);
		}
	}
	else
	if (strcasecmp(str1, "homeDir") == 0)
		setenv("HOME", str2, 1);
	else
	if (strcasecmp(str1, "imageFile") == 0)
		throw new Exception("now internal, remove line");
	else
	if (strcasecmp(str1, "imageOnTheFly") == 0)
		imageOnTheFly = toBoolean(str2);
	else
	if (strcasecmp(str1, "laser") == 0)
		laserOn = toBoolean(str2);
	else
	if (strcasecmp(str1, "logRequests") == 0) {
		if (toBoolean(str2) && getLogLevel() < 3)
			setConfig("logLevel", "3");
	}
	else
	if (strcasecmp(str1, "logSubProcess") == 0) {
		if (toBoolean(str2) && getLogLevel() < 5)
			setConfig("logLevel", "5");
	}
	else
	if (strcasecmp(str1, "logVerbose") == 0) {
		if (toBoolean(str2) && getLogLevel() < 4)
			setConfig("logLevel", "4");
	}
	else
	if (strcasecmp(str1, "maxCopies") == 0)
		maxCopies = toInteger(str2);
	else
	if (strcasecmp(str1, "maxStoreMBs") == 0)
		throw new Exception(
			": use maxMBytesInSpoolDir (see CDWserver/config.eg)");
	else
	if (strcasecmp(str1, "organization") == 0)
		organization = strDup(str2);
	else
	if (strcasecmp(str1, "removeSessions") == 0)
		removeSessions = toBoolean(str2);
	else
	if (strcasecmp(str1, "statusPortNo") == 0)
		statusPortNo = toInteger(str2);
	else
	if (strcasecmp(str1, "user") == 0) {
		if (!userSave) {
			uid = toUid(str2);
			user = strDup(str2);
			log.put(2, S.e + "user = " + user + ", uid = " + uid);
		}
	}
	else
	if (strcasecmp(str1, "waitForCD") == 0)
		waitForCD = toInteger(str2);
	else
		throw new Exception("unknown symbol");
}

	String *Config::getMimeType(File &file) {
		String key = file.getPath();
		int p0 = key.lastIndexOf(".");
		if (p0 >= 0)
			key = key.substring(p0 + 1);

		p0 = 1;
		while (true) {
			int p = mimeTypes.indexOf(key.getBytes(), p0);
			if (p < p0)
				return new String("application/octet-stream");
			char ch0 = mimeTypes.charAt(p - 1);
			char ch1 = ' ';
			if (p + key.length() < mimeTypes.length())
				ch1 = mimeTypes.charAt(p + key.length());
			p0 = p + 1;
			if (ch0 == ' ' || ch0 == '\t')
				if (ch1 == ' ' || ch1 == '\t'
						|| ch1 == '\n' || ch1 == '\r')
					break;
		}

		while (p0 > 0 && mimeTypes.charAt(p0 - 1) != '\n')
			p0--;

		int p = p0;
		while (mimeTypes.charAt(p) != '\t'
				&& mimeTypes.charAt(p) != ' ')
			p++;

		return mimeTypes.substring(p0, p);
	}

	String *Config::getRawDevice(
			int bus, int ID, int LUN, int no, const char *driver) {
		String path = "";
#ifdef FreeBSD
		path = path + "/dev/su" + bus + "-" + ID + "-" + LUN;
		File f = new File(path);
		if (!f.exists()) {
			String path2 = "";
			path2 = path2 + "/dev/cd" + no;
			f = new File(path2);
			if (f.exists())
				path = path2;
		}
#else
		String str = driver;
		if (str.equals(ATAPI)) {
			int count = 0;
			for (char ch = 'a'; ch <= 'z'; ch++) {
				String path2 = "";
				path2 = path2 + "/proc/ide/hd" + ch + "/media";
				FILE *in = fopen(path2.getBytes(), "r");
				if (in == NULL)
					continue;
				char buf[1024];
				int n = fread(buf, 1, sizeof buf - 1, in);
				buf[n] = 0;
				fclose(in);
				String str = buf;
				if (str.startsWith("cdrom"))
					count++;
				if (count == no + 1) {
					path = path + "/dev/hd" + ch;
					return new String(path);
				}
			}
		}
		path = path + "/dev/scd" + no;
		File f = new File(path);
		if (!f.exists()) {
			String path2 = "";
			path2 = path2 + "/dev/sr" + no;
			f = new File(path2);
			if (f.exists())
				path = path2;
		}
#endif
		return new String(path);
	}

	String *Config::getWritersForm() {
		String td = "<td bgcolor=\"<i18n>colorTableCellBackground</i18n>\">";
		String TD = "</td>";
		String str = "";

		for (int i = 0; i < getNumOfWriters(); i++) {
			str = str
				+ "<tr>"
				+ td + (i + 1) + TD
//				+ td + "<input type=\"text\" value=\""
//					+ writers[i].dev + "\">" + TD
//				+ td + writers[i].rawDevice + TD
				+ td + getWriterInfo(i) + TD
				+ td + createSpeedbox(writers[i], "CD") + TD
				+ td + createSpeedbox(writers[i], "CD-R") + TD
				+ td + createSpeedbox(writers[i], "CD-RW") + TD
				+ td + createSpeedbox(writers[i], "DVD") + TD
				+ td + createSpeedbox(writers[i], "DVD-R") + TD
				+ td + createSpeedbox(writers[i], "DVD-RW") + TD
				+ td + createSpeedbox(writers[i], "DVD+R") + TD
				+ td + createSpeedbox(writers[i], "DVD+RW") + TD
				+ "</tr>";
		}

		return new String(str);
	}

void Config::load(String &aFileName) {
	try {
		load2(aFileName);
	} catch (Exception *e) {
		String msg = "Config::load(): ";
		msg = msg + e->toString();
		printf("%s\n", msg.getBytes());
	}
}

void Config::load2(String &aFileName) {
	cdrdaoInfo = "";
	cdrecordInfo = "cdrecord";
	growisofsInfo = "";
	mkisofsInfo = "mkisofs";
	mpg123Info = "";
	ogg123Info = "";
	soxInfo = "";

	pthread_mutex_init(&mutex, NULL);

	const char *envStr = getenv("CONFIGDIR");
	if (envStr == NULL)
		configDir = ::configDir;
	else
		configDir = envStr;
	File file = new File(configDir, "config");
	fileName = file.getPath();

	envStr = getenv("CDWserverConfig");
	if (envStr != NULL) {
		file = new File(envStr);
		configDir = file.getParent();
		fileName = file.getPath();
	}

	if (aFileName.length() > 0)
		fileName = aFileName;

	firstError = "";
	firstErrorHelp = "";
	firstErrorPriority = 0;
	hasReader = false;
	numOfWriters = 0;

	// values
	accounts = 1;
	file = new File(configDir, "accounts");
	accountsFile = file.getPath();
	debugCache = "";
	eMailOnError = NULL;
	group = defaultGroup;
	imageOnTheFly = 1;
	file = new File(configDir, "greeting");
	infoFile = file.getPath();
	laserOn = 1;
	maxCopies = 10;
	maxSpeed = 8;
	mkisofsWithUTF8 = false;
	organization = "unknown";
	removeSessions = 0;
	statusPortNo = 0;
	supportFormCache = true;
	user = defaultUser;
	userSave = true;
	waitForCD = 10;
	file = new File(configDir, "waitForCD");
	waitForCDMsgFile = file.getPath();

	numOfItems = 0;
	add("anonymousUse", true);
	add("askForProjectInfo", false);
	add("ATAPIRawDeviceSupport", runsOnLinux26());
	add("audioCDSupport", true);
	add("binDir", path(BINDIR, binDir));
	add("burnProof", false);
	add("cdrdao", false);
	add("cdrdaoArgs",
		"--source-driver generic-mmc-raw --driver generic-mmc");
	add("cdrdaoInternalPath", path(TOOLSDIR, toolsDir, "cdrdao"));
	add("cdrdaoPath", path(BINDIR, binDir, "cdrdao"));
	add("cdrdaoSimulationPath", path(BINDIR, binDir, "cdrdao-dummy"));
	add("cdrecordArgs", "");
	add("cdrecordInternalPath", path(TOOLSDIR, toolsDir, "cdrecord"));
	add("cdrecordPath", path(BINDIR, binDir, "cdrecord"));
	add("cdrecordSimulationPath", path(BINDIR, binDir, "cdrecord-dummy"));
	add("copySupport", true);
	add("CSSFile", "/default.css");
	add("debug", debugCache.getBytes());
	file = new File(configDir, "default_files");
	add("defaultFilesDir", file.getPath().getBytes());
	add(DEFAULTSPEED, 8);
	add(DEFAULTSPEEDAUDIO, 4);
	add(DEFAULTSPEEDCDRW, 4);
	add("devices", "auto");
	add("dvd+rw-formatPath", path(BINDIR, binDir, "dvd+rw-format"));
	add("dvd+rw-formatSimulationPath",
		path(BINDIR, binDir, "dvd+rw-format-dummy"));
	add("dvd+rw-mediainfoPath", path(BINDIR, binDir, "dvd+rw-mediainfo"));
	add(DVDSUPPORT, false);
	add("ejectOnDone", true);
	add("ejectOnReservation", true);
	add("exportArchive", true);
	add("exportDir", path("EXPORTDIR", exportDir));
	add("feature.calculateMD5", 2);
	add("gracetime", 9);
	add("growisofsPath", path(BINDIR, binDir, "growisofs"));
	add("growisofsSimulationPath", path(BINDIR, binDir, "growisofs-dummy"));
	add("historyLength", 10);
	add("httpDir", path("HTTPDIR", httpDir));
	add("logDir", path("LOGDIR", logDir));
	add("logLevel", 1);
	logLevelCache = getActiveInt("logLevel");
	add("logoURL", "http://@SERVER@:@PORT@/penguin.png");
	add("managedAccounts", false);
	add("maxMBytesInSpoolDir", 4000);
	maxMBytesInSpoolDirCache = getActiveInt("maxMBytesInSpoolDir");
	add("maxMBytesPerSession", 800);
	maxMBytesPerSessionCache = getActiveInt("maxMBytesPerSession");
	add("maxOpenConnections", 10);
	add("maxOpenSessions", 3);
	add("mkisofsArgs", "-disable-deep-relocation");
	add("mkisofsInternalPath", path(TOOLSDIR, toolsDir, "mkisofs"));
	add("mkisofsPath", path(BINDIR, binDir, "mkisofs"));
	add("MP3decoding", false);
	add("mpg123Args", "-q -r 44100 -s --stereo -");
	add("mpg123Path", path(BINDIR, binDir, "mpg123"));
	add("ogg123Path", path(BINDIR, binDir, "ogg123"));
	add("oggDecoding", false);
	add("overburnSupport", false);
	add("overburnSupportArgs", "-dao -overburn");
	add("PAM", false);
	add(PASSWORD, "");
	add("portNo", 12411);
	add("projectsDir", path("PROJECTSDIR", projectsDir));
	add("readcdInternalPath", path(TOOLSDIR, toolsDir, "readcd"));
	add("readcdPath", path(BINDIR, binDir, "readcd"));
	add("reservedMBytes", 100);
	reservedMBytesCache = getActiveInt("reservedMBytes");
	add("rootGatePath", path(BINDIR, binDir, "CDWrootGate"));
	add("saveImageAs", true);
	add("setSchedulerPath", path(BINDIR, binDir, "setScheduler"));
	add("showUsers", true);
	add(SIMULATION, false);
//	add("SMTPserver", "localhost");
	add("soxPath", path(BINDIR, binDir, "sox"));
	add("spoolDir", path("SPOOLDIR", spoolDir));
	add("supportForm", supportFormCache);
	add("transferLimitMB", 5000);
	add("tryPatchedCdrecord", false);
	add("useBurnFree", true);
	add("useUTF8", false);
	add("verify", 2);
	add("verifyPath", path(BINDIR, binDir, "CDWverify"));
	add("verifySimulationPath", path(BINDIR, binDir, "CDWverify-dummy"));
	add("webCDcreatorDir", "webCDcreator");
	add("writeProtection", true);

	try {
		gid = toGid(group);
	}
	catch (Exception *error) {
		gid = 0;
		String str = toGroup(gid);
		group = strDup(str.getBytes());
		delete error;
	}

	try {
		uid = toUid(user);
	}
	catch (Exception *error) {
		uid = 0;
		String str = toUser(uid);
		user = strDup(str.getBytes());
		delete error;
	}

	try {
		readConfigFile();
	}
	catch (Exception *error) {
		if (firstErrorPriority < 100) {
			firstError = error->toString();
			firstErrorPriority = 100;
		}
		delete error;
	}

	String password = readPassword();
	if (password.equals("changeit")) {
		char passwd[8 + 1];
		time_t t;
		time(&t);
		srandom(t);
		for (int i = 0; i < sizeof passwd - 1; i++)
			passwd[i] = 'A' + random() % ('Z' - 'A' + 1);
		passwd[sizeof passwd - 1] = 0;
		password = passwd;
		writePassword(password);
	}
}

void Config::parseLine(
		const char *lineStr, char *str1, char *str2) {
	str1[0] = str2[0] = 0;

	if (lineStr[0] == '#')
		return;

	int i = 0;
	while (lineStr[i] == '\t'
		|| lineStr[i] == ' ')
		i++;

	int j = 0;
	for (; lineStr[i] != 0
		&& lineStr[i] != '\n'
		&& lineStr[i] != '\t'
		&& lineStr[i] != ' '
		&& lineStr[i] != '='; i++)
		str1[j++] = lineStr[i];
	str1[j] = 0;

	while (lineStr[i] == '\t'
		|| lineStr[i] == ' '
		|| lineStr[i] == '=')
		i++;

	j = 0;
	while (lineStr[i] != 0
		&& lineStr[i] != '\n')
		str2[j++] = lineStr[i++];

	// truncate trailing whitespace
	while (j > 0
		&& (str2[j - 1] == '\t'
			|| str2[j - 1] == ' '))
		j--;
	str2[j] = 0;
}

	String *Config::path(const char *env, const char *configure) {
		const char *dir = getenv(env);
		if (dir == NULL)
			dir = configure;

		return new String(dir);
	}

	String *Config::path(
			const char *env, const char *configure, String command) {
		String dir = path(env, configure);
		File f = new File(dir.getBytes(), command);

		if (env == BINDIR && !f.exists()) {
			File f2 = new File("/usr/bin", command);
			if (f2.exists())
				f = f2;
			else {
				f2 = new File("/usr/local/bin", command);
				if (f2.exists())
					f = f2;
			}
		}

		return new String(f.getPath());
	}

void Config::readConfigFile() {
	if (!File(fileName).exists()
		|| File(fileName).getSize() == 0)
		writeConfigFile();

	if (!File(infoFile).exists()
		|| File(infoFile).getSize() == 0)
		writeInfoFile();

	if (!File(waitForCDMsgFile).exists()
		|| File(waitForCDMsgFile).getSize() == 0)
		writeWaitForCDMsgFile();

	String name = fileName;
	log.put(2, S.e + "Reading config file " + name + "...");
	userSave = true;
	readConfigFile(name);

	name = name + "-root";
	log.put(2, S.e + "Reading root config file " + name + "...");
	userSave = false;
	readConfigFile(name);

	activateConfig();
}

void Config::readConfigFile(String &name) {
	FILE *inStream = fopen(name.getBytes(), "r");
	if (inStream == NULL)
		if (userSave)
			throw new Exception(S.e
				+ "Cannot open the config file " + name);
		else
			return;

	if (userSave) {
		resetItems();
		numOfWriters = 0;
	}

	try {
		readConfigFile(name, inStream);
	}
	catch (Exception *e) {
		fclose(inStream);
		throw e;
	}
	fclose(inStream);
}

void Config::readConfigFile(String &name, FILE *inStream) {
	int lineNo = 0;
	char lineStr[1024];
	while (fgets(lineStr, sizeof lineStr, inStream) != NULL) {
		lineNo++;
		char str1[1024], str2[1024];
		parseLine(lineStr, str1, str2);

		try {
			if (strlen(str1) == 0 && strlen(str2) == 0)
				continue;
			else
				evaluate(lineNo, str1, str2);
		}
		catch (Exception *error) {
			if (firstErrorPriority < 100) {
				firstError = firstError
					+ "Error in " + name
					+ ": line " + lineNo
					+ ": \"" + str1 + "=" + str2 + "\": "
					+ error->toString();
				firstErrorPriority = 100;
				log.put(1, S.e + firstError);
			}
			delete error;
		}
	}
}

	String *Config::readPassword() {
		File file = new File(getConfigDir(), PASSWORD);
		FILE *in = fopen(file.getPath().getBytes(), "r");
		if (in == NULL)
			return new String("changeit");

		StringBuffer sb = new StringBuffer(20);
		while (true) {
			int ch = fgetc(in);
			if (ch == EOF || ch < 32)
				break;
			sb.append((char)ch);
		}

		fclose(in);
		return new String(sb);
	}

void Config::reload() {
	log.put(1, "Config::reload() started");

	if (firstErrorPriority >= 100
			|| firstError.indexOf("pending") >= 0) {
		firstError = "";
		firstErrorHelp = "";
		firstErrorPriority = 0;
	}

	{
		Process p("/bin/uname");
		p.setProtoFile(NULL);
		p.addArg("-a");
//		p.show();
		p.start();
		while (1) {
			const char *lineStr = p.readLine();
			if (lineStr == NULL)
				break;
			String line = lineStr;
			if (line.startsWith("1"))
				;
		}
	}

	{
		Process p("/bin/rpm");
		p.setProtoFile(NULL);
		p.addArg("-q");
		p.addArg("webCDwriter");
		p.run();
	}

	{
		Process p(getActiveValue("cdrecordInternalPath"));
		p.setProtoFile(NULL);
		p.addArg("-version");
		p.run();
	}

	{
		Process p(getActiveValue("mkisofsInternalPath"));
		p.setProtoFile(NULL);
		p.addArg("-version");
		p.run();
	}

	try {
		File file = new File(getConfigDir(), "mime.types");
		FileInputStream in = new FileInputStream(file);
		StringBuffer sb = new StringBuffer(file.getSize());
		while (true) {
			char buf[4094];
			int n = in.read(buf, 0, sizeof buf);
			if (n <= 0)
				break;
			sb.append(buf, 0, n);
		}
		mimeTypes = new String(sb);
	}
	catch (Exception *e) {
		delete e;
	}

	try {
		readConfigFile();

		try {
			for (int i = 0; i < numOfItems; i++)
				log.put(5, S.e + "\"" + items[i]->key
					+ "\" \"" + items[i]->active.toString() + "\"");
		}
		catch (Exception *e) {
			log.put(2, S.e + e->getMessage());
			delete e;
		}

		if (uid != geteuid() || gid != getegid())
			throw new Exception(S.e + "Cannot set user \""
				+ user + "\" and group \"" + group + "\"",
				S.e + "Restart " + product + " as root by"
				+ " [" + initDir + "/CDWserver restart]");
		log.put(5, S.e + "Config::reload(): geteuid() = " + (int) geteuid());

		String logDir = getActiveValue("logDir");
		if (!File(logDir).exists())
			throw new Exception(S.e
				+ "logDir " + logDir + " does not exist");
		log.put(5, S.e + "Config::reload(): logDir = " + logDir);

		String projectsDir = getActiveValue("projectsDir");
		if (accounts && !File(projectsDir).exists())
			throw new Exception(S.e
				+ "projectsDir " + projectsDir + " does not exist");
		log.put(5, S.e + "Config::reload(): projectsDir = " + projectsDir);

		String spoolDir = getActiveValue("spoolDir");
		if (!File(spoolDir).exists())
			throw new Exception(S.e
				+ "spoolDir " + spoolDir + " does not exist");
		log.put(5, S.e + "Config::reload(): spoolDir = " + spoolDir);

		if (getActiveBoolean("cdrdao"))
			preparePath("cdrdao", PATH_COPY | PATH_DUMMY);

		preparePath("cdrecord", PATH_COPY | PATH_DUMMY);

		if (config.getActiveBoolean(DVDSUPPORT)) {
			preparePath("dvd+rw-format", PATH_DUMMY);
			preparePath("dvd+rw-mediainfo");
			preparePath("growisofs", PATH_DUMMY);
			testGrowisofs();
		}

		preparePath("mkisofs", PATH_COPY);
		if (getActiveBoolean(SIMULATION))
			checkCopy("mkisofs");

		if (getActiveBoolean("audioCDSupport")) {
			if (getActiveBoolean("MP3decoding"))
				preparePath("mpg123");
			if (getActiveBoolean("oggDecoding"))
				preparePath("ogg123");
			preparePath("sox");
		}

		preparePath("setScheduler", PATH_ROOT);

		if (getActiveInt("verify") > 0)
			preparePath("verify", PATH_DUMMY | PATH_ROOT);

		activateConfig();

		checkFile("accounts", accountsFile);
		checkFile("config", fileName);

		File file = new File(getConfigDir(), PASSWORD);
		checkFile(PASSWORD, file.getPath());

		if (uid != 0 && File(logDir).isReadAndWritable(uid, gid) != 0)
			throw new Exception(S.e
				+ "logDir " + logDir + " or a file below it"
				+ " is not read and/or writable by the user \""
					+ user + "\"",
				S.e + "Try one of\n<ul>\n"
					+ "<li>change "
						+ "<a href=\"/config/directories.html\">"
						+ "logDir</a></li>\n"
					+ "<li>run<br>[chown "
						+ user + " " + logDir + " -R]</li></ul>");

		if (uid != 0 && File(projectsDir).isReadAndWritable(uid, gid) != 0)
			throw new Exception(S.e
				+ "projectsDir " + projectsDir + " or a file below it"
				+ " is not read and/or writable by the user \""
					+ user + "\"",
				S.e + "Try one of\n<ul>\n"
					+ "<li>change "
						+ "<a href=\"/config/directories.html\">"
						+ "projectsDir</a></li>\n"
					+ "<li>run<br>[chown "
						+ user + " " + projectsDir + " -R]</li></ul>");

		if (uid != 0 && File(spoolDir).checkOwner(uid) != 0)
			throw new Exception(S.e
				+ "spoolDir " + spoolDir + " or a file below it"
				+ " is not owned by the user \"" + user + "\"",
				S.e + "Try one of\n<ul>\n"
					+ "<li>change "
						+ "<a href=\"/config/directories.html\">"
						+ "spoolDir</a></li>\n"
					+ "<li>run<br>[chown "
						+ user + " " + spoolDir + " -R]</li></ul>");

/*		if (!config.getImageOnTheFly()) {
			FILE *outStream = fopen(imageFile, "w");
			if (outStream == NULL)
				throw new Exception("can't create imageFile %s",
					imageFile);
			else
				fclose(outStream);
		}
*/
		testMkisofs();
		cdrecordVersion();
		cdrecordHelp();
		cdrecordScanbus();
		for (int i = 0; i < numOfWriters; i++)
			cdrecordInq(i);
		checkRawDevices();

		if (getActiveBoolean("audioCDSupport")) {
			if (getActiveBoolean("MP3decoding"))
				testMpg123();
			if (getActiveBoolean("oggDecoding")) {
				testOgg123();
				if (ogg123Info.indexOf("1.0rc") >= 0)
					throw new Exception(S.e + "ogg123 " + ogg123Info
						+ " does not work with webCDwriter. Get 1.0!",
						S.e + "/config/error/ogg123.html");
			}
			testSox();
		}

		activateConfig();
		resetEditor();
	}
	catch (Exception *error) {
		log.put(1, S.e + "reload(): " + error->toString());
		if (firstErrorPriority < 100) {
			firstError = error->toString();
			firstErrorHelp = error->getHelp();
			firstErrorPriority = 100;
		}
		delete error;
	}
}

void Config::setPermissions() {
	char fileName[4*1024];
	sprintf(fileName, "%s/connects",
		config.getLogDir());
	chown(fileName, uid, gid);
	sprintf(fileName, "%s/log",
		config.getLogDir(), version);
	chown(fileName, uid, gid);
//	chown(imageFile, uid, gid);
}

	void Config::testGrowisofs() {
		String helpURL = "/config/error/dvd+rw-tools.html";

		Process growisofs(getActiveBinPath("growisofs"));
		growisofs.addArg("-version");
		growisofs.start();
		const char *lineStr = growisofs.readLine();
		if (lineStr == NULL)
			throw new Exception(S.e + "Cannot run growisofs ("
				+ getActiveValue("growisofsPath") + ")",
				S.e + helpURL);

		String line = lineStr;
		int a = 0, b = -1;
		if (line.startsWith("growisofs "))
			growisofsInfo = line.substring(10);
		else
		if (line.indexOf("growisofs by") >= 0) {
			int p = line.indexOf("version ");
			if (p >= 0)
				growisofsInfo = line.substring(p + 8);
		}

		sscanf(growisofsInfo.getBytes(), "%d.%d", &a, &b);

		if (b < 0)
			throw new Exception(S.e
				+ "Unexpected output of \"growisofs -version\"",
				S.e + helpURL);

		if (a < 5 || b < 10)
			throw new Exception(S.e + "growisofs " + a + "." + b
				+ " is to old",
				S.e + helpURL);

		int comma = growisofsInfo.indexOf(",");
		if (comma > 0)
			growisofsInfo = growisofsInfo.substring(0, comma);
	}

void Config::testMkisofs() {
	Process p(getMkisofsPath());
	p.addArg("-version");
	p.start();

	String line = "";
	while (true) {
		const char *str = p.readLine();
		if (str == NULL)
			break;
		line = str;
		if (line.indexOf(".mkisofsrc") < 0)
			break;
	}

	if (line.length() == 0)
		throw new Exception("Cannot run mkisofs");

	if (p.getResult() != 0)
		throw new Exception("Error running mkisofs -version");

	log.put(3, S.e + "Config::testMkisofs(): " + line);

	String command = "", version = "";
	StringTokenizer tokenizer(line.getBytes(), " \t");
	try {
		command = tokenizer.nextToken();
		version = tokenizer.nextToken();
	}
	catch (NoSuchElementException *e) {
		delete e;
		throw new Exception("Cannot get the version of mkisofs");
	}

	mkisofsWithUTF8 = version.indexOf("UTF-8") >= 0;

	mkisofsInfo = "";
	mkisofsInfo = mkisofsInfo + command + " " + version;
}

void Config::testMpg123() {
	Process mpg123(config.getActiveValue("mpg123Path"));
	mpg123.addArg("--longhelp");
	mpg123.start();
	const char *lineStr = mpg123.readLine();
	if (lineStr == NULL)
		throw new Exception(S.e + "Cannot run mpg123 ("
			+ config.getActiveValue("mpg123Path") + ")",
				S.e + "/config/error/mpg123.html");
	do {
		char str[strlen(lineStr) + 1];
		if (sscanf(lineStr, "Version %s", str) == 1)
			mpg123Info = str;
		lineStr = mpg123.readLine();
	} while (lineStr != NULL);
}

	void Config::testOgg123() {
		Process ogg123(getActiveValue("ogg123Path"));
		ogg123.addArg("--version");
		ogg123.start();
		const char *lineStr = ogg123.readLine();
		if (lineStr == NULL)
			throw new Exception(S.e + "Cannot run ogg123 ("
				+ getActiveValue("ogg123Path") + ")",
				S.e + "/config/error/ogg123.html");
		do {
			char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
			if (sscanf(lineStr, "%s %[^:]", str1, str2) == 2)
				ogg123Info = str2;
			lineStr = ogg123.readLine();
		} while (lineStr != NULL);
	}

	void Config::testSox() {
		Process sox(getActiveValue("soxPath"));
		sox.addArg("-h");
		sox.start();
		const char *lineStr = sox.readLine();
		if (lineStr == NULL)
			throw new Exception(S.e + "Cannot run sox ("
				+ getActiveValue("soxPath") + ")",
				S.e + "/config/error/sox.html");
		do {
			char str1[strlen(lineStr) + 1], str2[strlen(lineStr) + 1];
			if (sscanf(lineStr, "%[^:]: Version %s", str1, str2) == 2)
				soxInfo = str2;
			lineStr = sox.readLine();
		} while (lineStr != NULL);
	}

String *getTabItem(const char *file,
		String &key, int inCol,
		int getCol) {
	FILE *in = fopen(file, "r");
	if (in == NULL)
		return new String();

	String value = "";
	while (true) {
		char line[1024];
		if (fgets(line, sizeof line, in) == NULL)
			break;
		String str = line;

		int p0 = 0;
		for (int i = 0; i < inCol && p0 < str.length(); p0++)
			if (str.charAt(p0) == ':')
				i++;

		int p1 = p0 + 1;
		for (; p1 < str.length(); p1++)
			if (str.charAt(p1) == ':')
				break;

		String item = str.substring(p0, p1);
		if (!item.equals(key))
			continue;

		p0 = 0;
		for (int i = 0; i < getCol && p0 < str.length(); p0++)
			if (str.charAt(p0) == ':')
				i++;

		p1 = p0 + 1;
		for (; p1 < str.length(); p1++)
			if (str.charAt(p1) == ':')
				break;

		value = str.substring(p0, p1);
//		printf("<%s><%s>\n", item.getBytes(), value.getBytes());
		break;
	}

	fclose(in);
	return new String(value);
}

int Config::toGid(const char *groupName) {
	String name = groupName;
	String id = getTabItem("/etc/group", name, 0, 2);
	if (id.length() == 0)
		throw new Exception(S.e + "Unknown group: " + name);
	return Integer::getInt(id.getBytes());
}

String *Config::toGroup(int gid) {
	String id = String::valueOf(gid);
	String name = getTabItem("/etc/group", id, 2, 0);
	if (name.length() == 0)
		return new String(id);
	else
		return new String(name);
}

String &Config::toHTML() {
	text = "";
	HTML = 1;

	text = text
		+ "<html>\n"
		+ "<head><title>Configuration of CDWserver</title></head>"
		+ "<body>\n\n"
		+ "<h1># Configuration of CDWserver</h1>\n\n";

	write();

	text = text
		+ "</body>\n"
		+ "</html>\n";

	return text;
}

const char *Config::toString(boolean b) {
	if (b)
		return "on";
	else
		return "off";
}

int Config::toUid(const char *userName) {
	String name = userName;
	String id = getTabItem("/etc/passwd", name, 0, 2);
	if (id.length() == 0)
		throw new Exception(S.e + "Unknown user: " + name);
	return Integer::getInt(id.getBytes());
}

String *Config::toUser(int uid) {
	String id = String::valueOf(uid);
	String name = getTabItem("/etc/passwd", id, 2, 0);
	if (name.length() == 0)
		return new String(id);
	else
		return new String(name);
}

	void Config::updateConfigFile() {
		FILE *in = fopen(fileName.getBytes(), "r");
		if (in == NULL)
			throw new Exception(S.e + "Cannot open " + fileName);
		StringBuffer sb = new StringBuffer(10000);
		while (true) {
			int ch = fgetc(in);
			if (ch == EOF)
				break;
			sb.append((char)ch);
		}
		fclose(in);

		if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '\n')
			sb.append('\n');

		for (int i = 0; i < numOfItems; i++) {
			if (items[i]->key.equals("gracetime")
					|| items[i]->key.equals(PASSWORD))
				continue;
			int done = false;
			for (int p0 = 0; ; p0++) {
				p0 = sb.indexOf(items[i]->key.getBytes(), p0);
				if (p0 < 0)
					break;
				if (p0 > 0 && sb.charAt(p0 - 1) != '\n')
					continue;
				int p1 = sb.indexOf("\n", p0);
				if (p1 < 0)
					p1 = sb.length();
				sb.delete_(p0, p1);
				String str = createConfigLine(i);
				if (str.startsWith("device.")) {
					if (sb.length() > p0 && sb.charAt(p0) == '\n')
						sb.delete_(p0, p0 + 1);
					if (!str.endsWith("=0") || str.endsWith(".CD=0")) {
						str = str + "\n";
						sb.append(str.getBytes());
					}
				}
				else
					sb.insert(p0, str.getBytes());
				done = true;
				break;
			}
			if (done || items[i]->isDefault())
				continue;
			String key = "#";
			key = key + items[i]->key;
			for (int p0 = 0; ; p0++) {
				p0 = sb.indexOf(key.getBytes(), p0);
				if (p0 < 0)
					break;
				if (p0 > 0 && sb.charAt(p0 - 1) != '\n')
					continue;
				int p1 = sb.indexOf("\n", p0);
				if (p1 < 0)
					p1 = sb.length();
				sb.delete_(p0, p1);
				String str = createConfigLine(i);
				sb.insert(p0, str.getBytes());
				done = true;
				break;
			}
			if (done)
				continue;
			String str = createConfigLine(i);
			str = str + "\n";
			sb.append(str.getBytes());
		}

		FILE *out = fopen(fileName.getBytes(), "w");
		if (out == NULL)
			throw new Exception(S.e + "Cannot write " + fileName);
		String str = new String(sb);
		fwrite(str.getBytes(), 1, str.length(), out);
		fclose(out);
	}

	void Config::updateEditor(String &str) {
		log.debug("POST", S.e + str);

		bool newPasswordMode = false;
		String newPassword1 = "", newPassword2 = "";

		int i = 0;
		while (true) {
			while (i < str.length()
					&& (str.charAt(i) == '\n'
						|| str.charAt(i) == ' '))
				i++;
			if (i == str.length())
				break;

			int p0 = i;
			while (i < str.length()
					&& str.charAt(i) != '\n'
					&& str.charAt(i) != ' '
					&& str.charAt(i) != '=')
				i++;
			if (i == p0)
				continue;
			String key = str.substring(p0, i);

			while (i < str.length()
					&& str.charAt(i) == ' ')
				i++;
			if (i == str.length()
					|| str.charAt(i) != '=')
				continue;
			i++;

			p0 = i;
			while (i < str.length()
					&& str.charAt(i) != '\n')
				i++;
			String value = str.substring(p0, i);

			if (key.equals("newPassword")) {
				newPasswordMode = true;
				newPassword1 = value;
				continue;
			}

			if (key.equals("newPassword2")) {
				newPasswordMode = true;
				newPassword2 = value;
				continue;
			}

			int index = search(key.getBytes());
			if (index < 0)
				continue;
			items[index]->setEditor(value);
		}

		String passwdKey = PASSWORD;
		String password = getEditorValue(passwdKey);
		log.debug(PASSWORD, S.e + "input = <" + password + ">");
		String password0 = readPassword();
		log.debug(PASSWORD, S.e + "file = <" + password0 + ">");
		if (!password.equals(password0)) {
			setError(PASSWORD,
				"<font color=\"#FF0000\">Invalid password</font>");
			throw new Exception(S.e + "Invalid password");
		}

		if (newPasswordMode)
			if (newPassword1.equals(newPassword2)) {
				writePassword(newPassword1);
				setEditor(PASSWORD, newPassword1);
			}
			else {
				setError(PASSWORD,
					"<font color=\"#FF0000\">Do not match</font>");
				throw new Exception(S.e + "Do not match");
			}

		updateConfig();
		updateConfigFile();
		setWarning(95, S.e + "Reload config pending",
			"Close all connections to CDWserver!");
	}

void Config::write() {
	write("# 1. Features");
//	write("accounts", toString(accounts));
	write("audioCDSupport", toString(getActiveBoolean("audioCDSupport")));
	write("cdrdao", toString(getActiveBoolean("cdrdao")));
	write("copySupport", toString(getActiveBoolean("copySupport")));
	write("imageOnTheFly", toString(imageOnTheFly));
	write("laser", toString(laserOn));
	write("MP3decoding", toString(getActiveBoolean("MP3decoding")));
	write(SIMULATION, toString(getActiveBoolean(SIMULATION)));
	write("useUTF8", toString(getMkisofsWithUTF8()));
	write("verify", getVerify());

	write("# 2. Server");
	if (!HTML) {
		write("group", group);
		write("user", user);
	}
	write("portNo", getActiveInt("portNo"));
	write("statusPortNo", statusPortNo);
	write("maxOpenConnections", getMaxOpenConnections());

	write("# 3. Directories");
	write("exportDir", getActiveValue("exportDir").getBytes());
	write("logDir", getLogDir());
	write("projectsDir", getProjectsDir());
	write("spoolDir", getSpoolDir());

	write("# 4. Cache");
	write("maxMBytesInSpoolDir", getMaxMBytesInSpoolDir());
	write("maxMBytesPerSession", getMaxMBytesPerSession());
	write("maxOpenSessions", getMaxOpenSessions());
	write("reservedMBytes", getReservedMBytes());

	write("# 5. CD-ROM(s) and CD-writer(s)");
	write(DEFAULTSPEED, getDefaultSpeed());
	write("maxSpeed", maxSpeed);
	write("useBurnFree", toString(getBurnFree()));

	write("# 6. Programs");
	write("cdrdaoPath", getCdrdaoPath());
	write("cdrecordPath", getCdrecordPath());
	if (config.getActiveBoolean(DVDSUPPORT)) {
		write("dvd+rw-formatPath",
			getActiveBinPath("dvd+rw-format"));
		write("dvd+rw-mediainfoPath",
			getActiveBinPath("dvd+rw-mediainfo"));
		write("growisofsPath",
			getActiveBinPath("growisofs"));
	}
	write("mkisofsPath", getMkisofsPath());
	write("mpg123Path", getActiveValue("mpg123Path").getBytes());
	write("ogg123Path", getActiveValue("ogg123Path").getBytes());
	write("readcdPath", getReadcdPath());
	write("soxPath", getActiveValue("soxPath").getBytes());

	write("# 7. Options");
	write("anonymousUse", toString(getAnonymousUse()));
	write("cdrecordArgs", getCdrecordArgs());
	write("debug", getActiveValue("debug").getBytes());
	write("defaultFilesDir", getDefaultFilesDir());
	write("ejectOnDone", toString(getEjectOnDone()));
	write("ejectOnReservation", toString(getEjectOnReservation()));
	if (eMailOnError == NULL)
		write("#eMailOnError", "unknown@unknown.org");
	else
		write("eMailOnError", eMailOnError);
	write("logLevel", getLogLevel());
	write("logoURL", getLogoURL().getBytes());
	write("maxCopies", maxCopies);
	write("mkisofsArgs", getMkisofsArgs());
	write("organization", organization);
	write("removeSessions", toString(removeSessions));
	write("showUsers", toString(getShowUsers()));
	write("waitForCD", waitForCD);
}

void Config::write(const char *str) {
	if (HTML)
		text = text + "<h2>" + str + "</h2>\n";
	else
		text = text + "\n" + str + "\n\n";
}

void Config::write(const char *str1, const char *str2) {
	if (HTML)
		text = text + str1 + " = " + str2 + "<br>\n";
	else
		text = text + str1 + "=" + str2 + "\n";
}

void Config::write(const char *str, int number) {
	if (HTML)
		text = text + str + " = " + number + "<br>\n";
	else
		text = text + str + "=" + number + "\n";
}

void Config::writeConfigFile() {
	int out = open(fileName.getBytes(), O_CREAT | O_WRONLY,
		S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	if (out < 0)
		throw new Exception(S.e + "Cannot create configFile " + fileName);

	PrintWriter writer = new PrintWriter(out);
	text = "";
	HTML = 0;

	text = text
		+ "# Configuration of CDWserver\n"
		+ "# See config.html for details\n\n";

	write();

	writer.write(text);
	writer.flush();
	close(out);
	chown(fileName.getBytes(), uid, gid);
}

void Config::writeInfoFile() {
	FILE *out = fopen(infoFile.getBytes(), "w");
	if (out == NULL)
		throw new Exception(S.e + "Cannot create " + infoFile);

	fprintf(out, "Welcome to CDWserver.\n");
	fprintf(out, "\n");
	fprintf(out, "This is the contents of %s.\n", infoFile.getBytes());
	fclose(out);
}

	void Config::writePassword(String &password) {
		File file = new File(getConfigDir(), PASSWORD);
		int out = creat(file.getPath().getBytes(), S_IRUSR | S_IWUSR);
		int res;
		if (out < 0)
			res = -1;
		else {
			res = ::write(out, password.getBytes(), password.length());
			close(out);
			chown(file.getPath().getBytes(), uid, gid);
		}
		if (res < 0)
			throw new Exception(S.e + "Cannot write password to "
				+ file.getPath());
	}

void Config::writeWaitForCDMsgFile() {
	FILE *out = fopen(waitForCDMsgFile.getBytes(), "w");
	if (out == NULL)
		throw new Exception(S.e + "Cannot create " + waitForCDMsgFile);

	fprintf(out, "The CD-writer No. $writerNo ($writerType) "
		"was reserved for $reservedMinutes minutes.\n");
	fprintf(out, "Please insert a CD-R(W) "
		"and wait until it is ejected!\n");
	fprintf(out, "\n");
	fprintf(out, "This is the contents of %s.\n",
		waitForCDMsgFile.getBytes());
	fclose(out);
}
