/* cfgfile.c
 * Configuration file parsing functions.
 *
 * Ziproxy - the HTTP acceleration proxy
 * This code is under the following conditions:
 *
 * ---------------------------------------------------------------------
 * Copyright (c)2005-2021 Daniel Mealha Cabrita
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
 * ---------------------------------------------------------------------
 *
 * This code also contains portions under the following conditions:
 *
 * ---------------------------------------------------------------------
 * Copyright (c) Juraj Variny, 2004
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * THIS SOFTWARE IS PROVIDED BY JURAJ VARINY
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * ---------------------------------------------------------------------
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
#include <resolv.h>
#include <pwd.h>
#include <grp.h>
#include <sys/stat.h>
#include <unistd.h>

#include "qparser.h"
#include "image.h"
#include "jp2tools.h"
#include "cfgfile.h"
#include "urltables.h"
#include "simplelist.h"
#include "cttables.h"
#include "auth.h"
#include "log.h"


t_qp_bool DoGzip, UseContentLength, AllowLookCh, ProcessJPG, ProcessPNG, ProcessGIF, PreemptNameRes, PreemptNameResBC, TransparentProxy, ConventionalProxy, ProcessHTML, ProcessCSS, ProcessJS, ProcessHTML_CSS, ProcessHTML_JS, ProcessHTML_tags, ProcessHTML_text, ProcessHTML_PRE, ProcessHTML_NoComments, ProcessHTML_TEXTAREA, AllowMethodCONNECT, OverrideAcceptEncoding, DecompressIncomingGzipData, WA_MSIE_FriendlyErrMsgs, InterceptCrashes, TOSMarking, TOSMarkAsDiffCTAlsoXST, URLReplaceDataCTListAlsoXST, LosslessCompressCTAlsoXST, ConvertToGrayscale;

int Port, NextPort, ConnTimeout, MaxSize, PreemptNameResMax, MaxUncompressedGzipRatio, MinUncompressedGzipStreamEval, MaxUncompressedImageRatio;
int ZiproxyTimeout; // deprecated
int ImageQuality[4];
int AlphaRemovalMinAvgOpacity;
int JP2ImageQuality[4];
int RestrictOutPortHTTP [MAX_RESTRICTOUTPORTHTTP_LEN];
int RestrictOutPortCONNECT [MAX_RESTRICTOUTPORTCONNECT_LEN];
int RestrictOutPortHTTP_len;
int RestrictOutPortCONNECT_len;
char *ServHost, *OnlyFrom, *NextProxy, *ServUrl;
char **Compressible;	// deprecated
char *LosslessCompressCT;
char *CustomError400, *CustomError403, *CustomError404, *CustomError407, *CustomError408, *CustomError409, *CustomError500, *CustomError503;
char *Address;
char *AccessLogFileName; // deprecated
char *DebugLog;
char *AccessLog;
char *ErrorLog;
char *RedefineUserAgent;
char *URLNoProcessing;
char *URLReplaceData;
char *URLReplaceDataCT;
char *URLDeny;
char *tmpBindOutgoingExList;
char *tmpBindOutgoingExAddr;
t_st_strtable *BindOutgoingExList;
in_addr_t *BindOutgoingExAddr;
in_addr_t *BindOutgoing;
int BindOutgoing_entries;

int TOSFlagsDefault_cfg;
int TOSFlagsDiff_cfg;
char *TOSMarkAsDiffURL_cfg;
char *TOSMarkAsDiffCT;
int TOSMarkAsDiffSizeBT_cfg;

int MaxActiveUserConnections;

char *PIDFile;
char *cli_PIDFile;

char *RunAsUser;
char *cli_RunAsUser;
char *RunAsGroup;
char *cli_RunAsGroup;

int AuthMode;
char *AuthPasswdFile;
#ifdef SASL
char *AuthSASLConfPath;
#endif

#ifdef JP2K
t_qp_bool ProcessJP2, ForceOutputNoJP2, ProcessToJP2, AnnounceJP2Capability, JP2OutRequiresExpCap;
int JP2Colorspace_cfg;
t_color_space JP2Colorspace;	// 0=RGB 1=YUV
int JP2Upsampler_cfg;
t_upsampler JP2Upsampler;	// upsampler method: 0-linear, 1-lanczos
int JP2BitResYA[8];	// 4x qualities 2 components: YA YA YA YA
int JP2BitResRGBA[16];	// 4x qualities 4 components: RGBA RGBA RGBA RGBA
int JP2BitResYUVA[16];	// 4x qualities 4 components: YUVA YUVA YUVA YUVA
int JP2CSamplingYA[32];	// 4x qualities 2 components 4 settings: YxYyYwYh AxAyAwAh YxYyYwYh AxAyAwAh YxYyYwYh AxAyAwAh YxYyYwYh AxAyAwAh
int JP2CSamplingRGBA[64];	// 4x qualities 4 components 4 settings: RxRyRwRh GxGyGwGh BxByBwBh AxAyAwAh ... (4 times)
int JP2CSamplingYUVA[64];	// 4x qualities 4 components 4 settings: YxYyYwYh UxUyUwUh VxVyVwVh AxAyAwAh ... (4 times)
#endif

#ifndef DefaultCfgLocation
char DefaultCfgLocation[] = "/etc/ziproxy/ziproxy.conf";
#endif

int LoadParmMatrix(int *destarray, t_qp_configfile *conf_handler, const char *conf_key, const int *dvalues, const int min_entries, const int max_entries, const int min_value, const int max_value);
const t_st_strtable *LoadParmMatrixStr(t_qp_configfile *conf_handler, const char *conf_key, const char **dvalues, int dvalues_len, const int min_entries, const int max_entries);
const t_ut_urltable *load_URLtable (const char *urltable_filename);
const t_ct_cttable *LoadParmMatrixCT (t_qp_configfile *conf_handler, const char *conf_key, const char **dvalues, int dvalues_len, const int min_entries, const int max_entries, const int subtype_add_x_prefix);
int check_int_ranges (const char *conf_key, const int inval, const int vlow, const int vhigh);
int check_int_minimum (const char *conf_key, const int inval, const int vlow);
int check_directory (const char *conf_key, const char *dirname);

const t_ut_urltable *tos_markasdiff_url_cfg;
const t_ct_cttable *tos_maskasdiff_ct_cfg;

const t_ut_urltable *urltable_noprocessing;
const t_ut_urltable *urltable_replacedata;
const t_ut_urltable *urltable_replacedatact;
const t_ct_cttable *urltable_replacedatactlist;
const t_ut_urltable *urltable_deny;

const t_ct_cttable *lossless_compress_ct;

/* dump parsing errors (if any) to stderr
 * returns: ==0: no errors, !=0 errors */
int dump_errors (t_qp_configfile *conf_handler)
{
	int counter = 0;
	const char *conf_key;
	t_qp_error error_code;
	int has_errors = 0;

	while (qp_get_error (conf_handler, counter++, &conf_key, &error_code)) {
		switch (error_code) {
		case QP_ERROR_SYNTAX:
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Incorrect syntax of parameter -> %s\n", conf_key);
			break;
		case QP_ERROR_MISSING_CONFKEY:
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Required parameter missing -> %s\n", conf_key);
			break;
		case QP_ERROR_INVALID_PARM:
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Invalid parameter -> %s\n", conf_key);
			break;
		case QP_ERROR_DUPLICATED:
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Parameter defined twice or more -> %s\n", conf_key);
			break;
		case QP_ERROR_OBSOLETE:
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"OBSOLETE parameter (see documentation for details) -> %s\n", conf_key);
			break;
		case QP_ERROR_NOT_SUPPORTED:
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"UNSUPPORTED parameter (disabled during compilation) -> %s\n", conf_key);
			break;
		case QP_ERROR_MORE_ERRORS:
			error_log_puts (LOGMT_FATALERROR, LOGSS_CONFIG,
				"More configuration errors present, but not displayed.");
			break;
		default:
			error_log_puts (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Parser bug.");
 		}

		has_errors = 1;
	}

	return (has_errors);
}

int ReadCfgFile(char * cfg_file)
{
	t_qp_configfile *conf_handler;
	int i, n;
#ifdef JP2K
	const int DefaultJP2ImageQuality[] = { 30, 25, 25, 20 };
	const int DefaultJP2BitResYA[] = { 6, 4, 7, 5, 8, 6, 8, 6 };
	const int DefaultJP2BitResRGBA[] = { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };
	const int DefaultJP2BitResYUVA[] = { 6, 5, 5, 4, 7, 6, 6, 5, 8, 7, 7, 6, 8, 8, 8, 6 };
	const int DefaultJP2CSamplingYA[] = { 	0, 0, 1, 1,  0, 0, 1, 1, \
						0, 0, 1, 1,  0, 0, 1, 1, \
						0, 0, 1, 1,  0, 0, 2, 2, \
						0, 0, 1, 1,  0, 0, 2, 2 };
	const int DefaultJP2CSamplingRGBA[] = {	0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1, \
						0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1, \
						0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1, \
						0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1 };
	const int DefaultJP2CSamplingYUVA[] = {	0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1,  0, 0, 1, 1, \
						0, 0, 1, 1,  0, 0, 1, 2,  0, 0, 2, 1,  0, 0, 1, 1, \
						0, 0, 1, 1,  0, 0, 2, 2,  0, 0, 2, 1,  0, 0, 2, 2, \
						0, 0, 1, 1,  0, 0, 2, 2,  0, 0, 2, 2,  0, 0, 2, 2 };

	
#endif
	const int DefaultImageQuality[] = { 30, 25, 25, 20 };

	Port = NextPort = 8080;
	ConnTimeout = 90;
	MaxSize = 1048576;
	PreemptNameResMax = 50;
	MaxUncompressedGzipRatio = 2000;
	MinUncompressedGzipStreamEval = 10000000;
	MaxUncompressedImageRatio = 500;
	PreemptNameRes = QP_FALSE;
	DoGzip = UseContentLength = ProcessJPG = ProcessPNG = ProcessGIF = QP_TRUE;
	ProcessHTML = ProcessCSS = ProcessJS = QP_FALSE;
	WA_MSIE_FriendlyErrMsgs = QP_TRUE;
	ProcessHTML_CSS = ProcessHTML_JS = ProcessHTML_tags = ProcessHTML_text = ProcessHTML_PRE = ProcessHTML_NoComments = ProcessHTML_TEXTAREA = QP_TRUE;
	AllowLookCh = PreemptNameResBC = TransparentProxy = QP_FALSE;
	ConvertToGrayscale = QP_FALSE;
	ConventionalProxy = QP_TRUE;
	ServHost = ServUrl = OnlyFrom = NextProxy = NULL;
	Compressible = NULL;	// deprecated
	lossless_compress_ct = NULL;
	LosslessCompressCTAlsoXST = QP_TRUE;
	CustomError400 = CustomError403 = CustomError404 = CustomError407 = CustomError408 = CustomError409 = CustomError500 = CustomError503 = NULL;
	Address = NULL;
	DebugLog = NULL;
	AccessLog = NULL;
	ErrorLog = NULL;
	AllowMethodCONNECT = QP_TRUE;
	OverrideAcceptEncoding = QP_TRUE;
	RedefineUserAgent = NULL;
	DecompressIncomingGzipData = QP_TRUE;
	BindOutgoing = NULL;
	BindOutgoing_entries = 0;
	tmpBindOutgoingExAddr = NULL;
	tmpBindOutgoingExList = NULL;
	BindOutgoingExAddr = NULL;
	BindOutgoingExList = NULL;
	URLNoProcessing = NULL;
	URLReplaceData = NULL;
	URLReplaceDataCT = NULL;
	URLReplaceDataCTListAlsoXST = QP_TRUE;
	URLDeny = NULL;
	urltable_noprocessing = NULL;
	urltable_replacedata = NULL;
	urltable_replacedatact = NULL;
	urltable_replacedatactlist = NULL;
	InterceptCrashes = QP_FALSE;
	TOSMarking = QP_FALSE;
	TOSFlagsDefault_cfg = -1;
	TOSFlagsDiff_cfg = -1;
	TOSMarkAsDiffURL_cfg = NULL;
	TOSMarkAsDiffSizeBT_cfg = -1;
	tos_markasdiff_url_cfg = NULL;
	tos_maskasdiff_ct_cfg = NULL;
	TOSMarkAsDiffCTAlsoXST = QP_TRUE;
	MaxActiveUserConnections = 0;
	PIDFile = cli_PIDFile;		/* defaults to CLI parameter, if specified */
	RunAsUser = cli_RunAsUser;	/* defaults to CLI parameter, if specified */
	RunAsGroup = cli_RunAsGroup;	/* defaults to CLI parameter, if specified */
	AuthMode = AUTH_NONE;
	AuthPasswdFile = NULL;
	AlphaRemovalMinAvgOpacity = 1000000;
#ifdef SASL
	AuthSASLConfPath = NULL;
#endif
#ifdef JP2K
	ProcessJP2 = QP_FALSE;
	ForceOutputNoJP2 = QP_FALSE;
	ProcessToJP2 = QP_FALSE;
	AnnounceJP2Capability = QP_FALSE;
	JP2OutRequiresExpCap = QP_FALSE;
	JP2Colorspace_cfg = 1;	// defaults to YUV
	JP2Upsampler = 0;	// defaults to linear
#endif

	if ((conf_handler = qp_init (cfg_file, QP_INITFLAG_IGNORECASE)) == NULL) {
		error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG, "Unable to open config file '%s'\n", cfg_file);
		return (1);
	}

	qp_getconf_str (conf_handler, "ViaServer", &ServHost, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "ServerUrl", &ServUrl, QP_FLAG_NONE);
        qp_getconf_int (conf_handler, "Port", &Port, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "OnlyFrom", &OnlyFrom, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "DebugLog", &DebugLog, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "AccessLog", &AccessLog, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "ErrorLog", &ErrorLog, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "NextProxy", &NextProxy, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "NextPort", &NextPort, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "ConnTimeout", &ConnTimeout, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "ZiproxyTimeout", &ZiproxyTimeout, QP_FLAG_NONE); // deprecated
	qp_getconf_bool (conf_handler, "Gzip", &DoGzip, QP_FLAG_NONE);
	qp_getconf_array_str (conf_handler, "Compressible", 0, NULL, QP_FLAG_NONE);	// deprecated
	qp_getconf_array_str (conf_handler, "LosslessCompressCT", 0, NULL, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "LosslessCompressCTAlsoXST", &LosslessCompressCTAlsoXST, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "MaxSize", &MaxSize, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "UseContentLength", &UseContentLength, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "ImageQuality", 0, NULL, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "AlphaRemovalMinAvgOpacity", &AlphaRemovalMinAvgOpacity, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "JP2ImageQuality", 0, NULL, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "AllowLookChange", &AllowLookCh, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ConvertToGrayscale", &ConvertToGrayscale, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessJPG", &ProcessJPG, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessPNG", &ProcessPNG, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessGIF", &ProcessGIF, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "PreemptNameRes", &PreemptNameRes, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "PreemptNameResMax", &PreemptNameResMax, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "PreemptNameResBC", &PreemptNameResBC, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "TransparentProxy", &TransparentProxy, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ConventionalProxy", &ConventionalProxy, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "CustomError400", &CustomError400, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "CustomError403", &CustomError403, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "CustomError404", &CustomError404, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "CustomError407", &CustomError407, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "CustomError408", &CustomError408, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "CustomError409", &CustomError409, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "CustomError500", &CustomError500, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "CustomError503", &CustomError503, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "Address", &Address, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "AccessLogFileName", &AccessLogFileName, QP_FLAG_NONE);	// deprecated
	qp_getconf_bool (conf_handler, "ProcessHTML", &ProcessHTML, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessCSS", &ProcessCSS, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessJS", &ProcessJS, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessHTML_CSS", &ProcessHTML_CSS, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessHTML_JS", &ProcessHTML_JS, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessHTML_tags", &ProcessHTML_tags, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessHTML_text", &ProcessHTML_text, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessHTML_PRE", &ProcessHTML_PRE, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessHTML_NoComments", &ProcessHTML_NoComments, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessHTML_TEXTAREA", &ProcessHTML_TEXTAREA, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "AllowMethodCONNECT", &AllowMethodCONNECT, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "OverrideAcceptEncoding", &OverrideAcceptEncoding, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "MaxUncompressedGzipRatio", &MaxUncompressedGzipRatio, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "MaxUncompressedImageRatio", &MaxUncompressedImageRatio, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "RedefineUserAgent", &RedefineUserAgent, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "DecompressIncomingGzipData", &DecompressIncomingGzipData, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "WA_MSIE_FriendlyErrMsgs", &WA_MSIE_FriendlyErrMsgs, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "InterceptCrashes", &InterceptCrashes, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "MinUncompressedGzipStreamEval", &MinUncompressedGzipStreamEval, QP_FLAG_NONE);
	qp_getconf_array_str (conf_handler, "BindOutgoing", 0, NULL, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "BindOutgoingExAddr", &tmpBindOutgoingExAddr, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "BindOutgoingExList", &tmpBindOutgoingExList, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "URLNoProcessing", &URLNoProcessing, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "URLReplaceData", &URLReplaceData, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "URLReplaceDataCT", &URLReplaceDataCT, QP_FLAG_NONE);
	qp_getconf_array_str (conf_handler, "URLReplaceDataCTList", 0, NULL, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "URLReplaceDataCTListAlsoXST", &URLReplaceDataCTListAlsoXST, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "URLDeny", &URLDeny, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "RestrictOutPortHTTP", 0, NULL, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "RestrictOutPortCONNECT", 0, NULL, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "TOSMarking", &TOSMarking, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "TOSFlagsDefault", &TOSFlagsDefault_cfg, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "TOSFlagsDiff", &TOSFlagsDiff_cfg, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "TOSMarkAsDiffURL", &TOSMarkAsDiffURL_cfg, QP_FLAG_NONE);
	qp_getconf_array_str (conf_handler, "TOSMarkAsDiffCT", 0, NULL, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "TOSMarkAsDiffCTAlsoXST", &TOSMarkAsDiffCTAlsoXST, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "TOSMarkAsDiffSizeBT", &TOSMarkAsDiffSizeBT_cfg, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "MaxActiveUserConnections", &MaxActiveUserConnections, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "PIDFile", &PIDFile, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "RunAsUser", &RunAsUser, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "RunAsGroup", &RunAsGroup, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "AuthMode", &AuthMode, QP_FLAG_NONE);
	qp_getconf_str (conf_handler, "AuthPasswdFile", &AuthPasswdFile, QP_FLAG_NONE);
#ifdef SASL
	qp_getconf_str (conf_handler, "AuthSASLConfPath", &AuthSASLConfPath, QP_FLAG_NONE);
#else
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "AuthSASLConfPath"); /* no SASL support */
#endif
#ifdef JP2K
	qp_getconf_bool (conf_handler, "ProcessJP2", &ProcessJP2, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ForceOutputNoJP2", &ForceOutputNoJP2, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "ProcessToJP2", &ProcessToJP2, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "AnnounceJP2Capability", &AnnounceJP2Capability, QP_FLAG_NONE);
	qp_getconf_bool (conf_handler, "JP2OutRequiresExpCap", &JP2OutRequiresExpCap, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "JP2Colorspace", &JP2Colorspace_cfg, QP_FLAG_NONE);
	qp_getconf_int (conf_handler, "JP2Upsampler", &JP2Upsampler_cfg, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "JP2BitResYA", 0, NULL, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "JP2BitResRGBA", 0, NULL, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "JP2BitResYUVA", 0, NULL, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "JP2CSamplingYA", 0, NULL, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "JP2CSamplingRGBA", 0, NULL, QP_FLAG_NONE);
	qp_getconf_array_int (conf_handler, "JP2CSamplingYUVA", 0, NULL, QP_FLAG_NONE);
#else
	/* JP2K not enabled, but we still recognize those parameters
           in order to provide a more informative error message */
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "ProcessJP2");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "ForceOutputNoJP2");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "ProcessToJP2");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "AnnounceJP2Capability");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "JP2OutRequiresExpCap");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "JP2Colorspace");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "JP2Upsampler");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "JP2BitResYA");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "JP2BitResRGBA");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "JP2BitResYUVA");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "JP2CSamplingYA");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "JP2CSamplingRGBA");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "JP2CSamplingYUVA");
#endif
#ifdef EN_NAMESERVERS
	qp_getconf_array_str (conf_handler, "Nameservers", 0, NULL, QP_FLAG_NONE);
#else
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_NOT_SUPPORTED, "Nameservers");
#endif

	/* list of OBSOLETE parameters, but we still recognize those parameters
	   in order to provide a more informative error message */
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "WhereZiproxy");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "JP2Rate");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "MinTextStream");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "ModifySuffixes");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "LogPipe");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "LogFile");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "MSIETest");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "AccessLogUserPOV");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "NetdTimeout");
	qp_set_parameter_status (conf_handler, QP_PARM_STATUS_OBSOLETE, "PasswdFile");

	/* init error_log as soon as possible! */
	if (error_log_init (ErrorLog) != 0) {
		error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
			"Unable to open ErrorLog file for writing.\n"
			"ErrorLog filename: '%s'\n"
			"Please correct this or leave ErrorLog undefined (for stderr output instead).\n", ErrorLog);
		return (1);
	}

	qp_fail_unrecognized_conf (conf_handler);
	if (dump_errors (conf_handler))
		return (1);

	/* check conflicts */
	if (qp_check_parameter_existence (conf_handler, "LosslessCompressCT") && \
		qp_check_parameter_existence (conf_handler, "Compressible")) {
		error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
			"Parameter conflict. \"%s\" cannot be used with \"%s\".\n",
			"LosslessCompressCT", "Compressible");
		return (1);
	}
	if (qp_check_parameter_existence (conf_handler, "AccessLog") && \
		qp_check_parameter_existence (conf_handler, "AccessLogFileName")) {
		error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
			"Parameter conflict. \"%s\" cannot be used with \"%s\".\n",
			"AccessLog", "AccessLogFileName");
		return (1);
	}
	if (qp_check_parameter_existence (conf_handler, "ConnTimeout") && \
		qp_check_parameter_existence (conf_handler, "ZiproxyTimeout")) {
		error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
			"Parameter conflict. \"%s\" cannot be used with \"%s\".\n",
			"ConnTimeout", "ZiproxyTimeout");
		return (1);
	}

	/* check deprecations based on parameter renaming  */
	if (qp_check_parameter_existence (conf_handler, "AccessLogFileName")) {
		AccessLog = AccessLogFileName;
		error_log_printf (LOGMT_WARN, LOGSS_CONFIG, "\"AccessLogFileName\" option is DEPRECATED.\n"
			"\tIt still works, but it will be removed in the future.\n"
			"\tPlease rename this option to \"AccessLog\" (see documentation for details).\n");
	}
	if (qp_check_parameter_existence (conf_handler, "ZiproxyTimeout")) {
		ConnTimeout = ZiproxyTimeout;
		error_log_printf (LOGMT_WARN, LOGSS_CONFIG, "\"ZiproxyTimeout\" option is DEPRECATED.\n"
			"\tIt still works, but it will be removed in the future.\n"
			"\tPlease rename this option to \"ConnTimeout\" (see documentation for details).\n");
	}

	/* warn if user/group/PIDFile is specified in both CLI and config parameter */
	if ((cli_RunAsUser != NULL) && (qp_check_parameter_existence (conf_handler, "RunAsUser"))) {
		error_log_printf (LOGMT_WARN, LOGSS_CONFIG,
			"User specified in both: CLI parameter and RunAsUser. "
			"Using RunAsUser (%s) instead of CLI parameter (%s).\n", RunAsUser, cli_RunAsUser);
	}
	if ((cli_RunAsGroup != NULL) && (qp_check_parameter_existence (conf_handler, "RunAsGroup"))) {
		error_log_printf (LOGMT_WARN, LOGSS_CONFIG,
			"Group specified in both: CLI parameter and RunAsGroup. "
			"Using RunAsGroup (%s) instead of CLI parameter (%s).\n", RunAsGroup, cli_RunAsGroup);
	}
	if ((cli_PIDFile != NULL) && (qp_check_parameter_existence (conf_handler, "PIDFile"))) {
		error_log_printf (LOGMT_WARN, LOGSS_CONFIG,
			"PID file specified in both: CLI parameter and PIDFile. "
			"Using PIDFile (%s) instead of CLI parameter (%s).\n", PIDFile, cli_PIDFile);
	}

	/* check if user/group exist
	   NOTE: at this point we're NOT changing to user/group, it's just a test */
	if (switch_to_user_group (RunAsUser, RunAsGroup, 1) != 0)
		return (1);

	if (URLNoProcessing != NULL) {
		if ((urltable_noprocessing = load_URLtable (URLNoProcessing)) == NULL)
			return (1);
	}

	if (URLReplaceData != NULL) {
		if ((urltable_replacedata = load_URLtable (URLReplaceData)) == NULL)
			return (1);
	}

	if (URLReplaceDataCT != NULL) {
		if (qp_get_array_size (conf_handler, "URLReplaceDataCTList") <= 0) {
			error_log_puts (LOGMT_FATALERROR, LOGSS_CONFIG,
				"URLReplaceDataCT requires non-empty URLReplaceDataCTList.");
			return (1);
		}
		if ((urltable_replacedatact = load_URLtable (URLReplaceDataCT)) == NULL)
			return (1);

		urltable_replacedatactlist = LoadParmMatrixCT (conf_handler,
		                               "URLReplaceDataCTList", NULL, 0, 1, -1, URLReplaceDataCTListAlsoXST);

		if (urltable_replacedatactlist == NULL)
			return (1);
	}

	if (URLDeny != NULL) {
		if ((urltable_deny = load_URLtable (URLDeny)) == NULL)
			return (1);
	}

	/* authentication settings */
	if (check_int_ranges ("AuthMode", AuthMode, AUTH_MIN, AUTH_MAX))
		return (1);
	if (AuthMode == AUTH_PASSWD_FILE) {
		if (AuthPasswdFile == NULL) {
			error_log_puts (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Specified AuthMode requires defined AuthPasswdFile.");
			return (1);
		}

		// FIXME: this table is not manually deallocated later, thus it is not zero'ed
		// when ziproxy daemon/process finishes. That's a security vulnerability.
		if (auth_passwdfile_init (AuthPasswdFile)) {
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Error while loading AuthPasswdFile from: %s\n", AuthPasswdFile);
			return (1);
		}
	} else if (AuthMode == AUTH_SASL) {
#ifdef SASL
		if (AuthSASLConfPath != NULL) {
			if (check_directory ("AuthSASLConfPath", AuthSASLConfPath))
				return (1);
		}

		if (auth_sasl_init (AuthSASLConfPath)) {
			error_log_puts (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Error while initializing SASL.");
			return (1);
		}
#else
		error_log_puts (LOGMT_FATALERROR, LOGSS_CONFIG,
			"Specified AuthMode requires SASL, which is not supported.");
		return (1);
#endif

	}
	auth_set_mode (AuthMode);

	if (TOSMarkAsDiffURL_cfg != NULL) {
		if((tos_markasdiff_url_cfg = load_URLtable (TOSMarkAsDiffURL_cfg)) == NULL)
			return (1);
	}

	/* TODO: make the 'x-' preffix addition optional (see last parameter of LoadParmMatrixCT()
		it is currently hardcoded to ENABLED */
	if (qp_get_array_size (conf_handler, "TOSMarkAsDiffCT") > 0)
		tos_maskasdiff_ct_cfg = LoadParmMatrixCT (conf_handler, "TOSMarkAsDiffCT", NULL, 0, 1, -1, TOSMarkAsDiffCTAlsoXST);

	if (check_int_ranges ("TOSFlagsDefault", TOSFlagsDefault_cfg, -1, 255))
		return (1);

	if (check_int_ranges ("TOSFlagsDiff", TOSFlagsDiff_cfg, -1, 255))
		return (1);

	if (check_int_minimum ("TOSMarkAsDiffSizeBT", TOSMarkAsDiffSizeBT_cfg, -1))
		return (1);

	if (check_int_minimum ("MaxActiveUserConnections", MaxActiveUserConnections, 0))
		return (1);

	if (check_int_ranges ("AlphaRemovalMinAvgOpacity", AlphaRemovalMinAvgOpacity, 0, 1000000))
		return (1);

	n = qp_get_array_size (conf_handler, "BindOutgoing");
	if(n){
		char *ip_str;
		
		BindOutgoing_entries = n;
		BindOutgoing = calloc (n, sizeof (in_addr_t));
		while (n--) { 
			qp_getconf_array_str (conf_handler, "BindOutgoing", n, &ip_str, QP_FLAG_NONE);
			BindOutgoing [n] = inet_addr (ip_str);
			free (ip_str);
		}
	}

	if (tmpBindOutgoingExList != NULL) {
		if ((BindOutgoingExList = slist_create (tmpBindOutgoingExList)) == NULL) {
			error_log_puts (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Problem while processing BindOutgoingExList.");
			return (1);
		}
	}

	if (tmpBindOutgoingExAddr != NULL) {
		BindOutgoingExAddr = calloc (1, sizeof (in_addr_t));
		*BindOutgoingExAddr = inet_addr (tmpBindOutgoingExAddr);
	} else {
		if (BindOutgoing_entries > 0) {
			BindOutgoingExAddr = &(BindOutgoing [0]);
		}
	}

	/* TODO: make the 'x-' preffix addition optional (see last parameter of LoadParmMatrixCT()
		it is currently hardcoded to ENABLED */
	if (qp_get_array_size (conf_handler, "LosslessCompressCT") > 0) {
		lossless_compress_ct = LoadParmMatrixCT (conf_handler, "LosslessCompressCT", NULL, 0, 1, -1, LosslessCompressCTAlsoXST);
	} else if (qp_get_array_size (conf_handler, "Compressible") == 0) {
		t_ct_cttable *tmp_lossless_compress_ct;

		/* empty table and no (deprecated) "Compressible" either.
		   load defaults instead. */
		if ((tmp_lossless_compress_ct = ct_create (1)) != NULL) {
			/* defaults' list - includes x-prefixed subtypes (aaaa/x-bbbb) */
			ct_insert (tmp_lossless_compress_ct, "text/*");
			ct_insert (tmp_lossless_compress_ct, "application/shockwave");
			ct_insert (tmp_lossless_compress_ct, "application/msword");
			ct_insert (tmp_lossless_compress_ct, "application/msexcel");
			ct_insert (tmp_lossless_compress_ct, "application/mspowerpoint");
			ct_insert (tmp_lossless_compress_ct, "application/rtf");
			ct_insert (tmp_lossless_compress_ct, "application/postscript");
			ct_insert (tmp_lossless_compress_ct, "application/java");
			ct_insert (tmp_lossless_compress_ct, "application/javascript");
			ct_insert (tmp_lossless_compress_ct, "application/staroffice");
			ct_insert (tmp_lossless_compress_ct, "application/vnd.*");
			ct_insert (tmp_lossless_compress_ct, "application/futuresplash");
			ct_insert (tmp_lossless_compress_ct, "application/asp");
			ct_insert (tmp_lossless_compress_ct, "application/class");
			ct_insert (tmp_lossless_compress_ct, "application/font");
			ct_insert (tmp_lossless_compress_ct, "application/truetype-font");
			ct_insert (tmp_lossless_compress_ct, "application/php");
			ct_insert (tmp_lossless_compress_ct, "application/cgi");
			ct_insert (tmp_lossless_compress_ct, "application/executable");
			ct_insert (tmp_lossless_compress_ct, "application/shellscript");
			ct_insert (tmp_lossless_compress_ct, "application/perl");
			ct_insert (tmp_lossless_compress_ct, "application/python");
			ct_insert (tmp_lossless_compress_ct, "application/awk");
			ct_insert (tmp_lossless_compress_ct, "application/dvi");
			ct_insert (tmp_lossless_compress_ct, "application/css");
			ct_insert (tmp_lossless_compress_ct, "application/*+xml");
			ct_insert (tmp_lossless_compress_ct, "application/xml");
			ct_insert (tmp_lossless_compress_ct, "application/pdf");
			ct_insert (tmp_lossless_compress_ct, "application/tar");
			ct_insert (tmp_lossless_compress_ct, "application/json");
			ct_insert (tmp_lossless_compress_ct, "application/xml-dtd");
			ct_insert (tmp_lossless_compress_ct, "application/iso9660-image");
			ct_insert (tmp_lossless_compress_ct, "image/svg+xml");

			lossless_compress_ct = tmp_lossless_compress_ct;
		}
	}

	/* in case the configuration file is still using
	   the DEPRECATED "Compressible" option... */
	n = qp_get_array_size (conf_handler, "Compressible");
	if (n) {
		t_ct_cttable *tmp_lossless_compress_ct;

		error_log_printf (LOGMT_WARN, LOGSS_CONFIG, "\"Compressible\" option is DEPRECATED.\n"
			"\tIt still works, but it will be removed in the future.\n"
			"\tPlease migrate to \"LosslessCompressCT\" (see documentation for details).\n");

		/* includes x-prefixed subtypes (aaaa/x-bbbb) */
		if ((tmp_lossless_compress_ct = ct_create (1)) != NULL) {
			/* those content-types were automatically included as compressible
			   regardless the contents of "Compressible" array */
			ct_insert (tmp_lossless_compress_ct, "text/*");
			ct_insert (tmp_lossless_compress_ct, "application/javascript");

			/* populate Compressible array (like previous behavior) */
	                Compressible = malloc ((n + 1) * sizeof (char *));
	                for(i = 0; i < n; i++)
	                        qp_getconf_array_str (conf_handler, "Compressible", i, &Compressible [i], QP_FLAG_NONE);
	                Compressible [i] = NULL;

			/* migrate data from deprecated "Compressible" array to new one */
			for (i = 0; i < n; i++) {
				char full_content_type [strlen (Compressible [i]) + 12 + 1];

				sprintf (full_content_type, "application/%s", Compressible [i]);
				ct_insert (tmp_lossless_compress_ct, full_content_type);
			}

			/* no need for "Compressible" array anymore */
			free (Compressible);
			Compressible = NULL;

			lossless_compress_ct = tmp_lossless_compress_ct;
    		}
	}

	RestrictOutPortHTTP_len = LoadParmMatrix (RestrictOutPortHTTP, conf_handler, "RestrictOutPortHTTP", NULL, 1, MAX_RESTRICTOUTPORTHTTP_LEN, 1, 65535);
	if (RestrictOutPortHTTP_len < 0)
		return (1);

	RestrictOutPortCONNECT_len = LoadParmMatrix (RestrictOutPortCONNECT, conf_handler, "RestrictOutPortCONNECT", NULL, 1, MAX_RESTRICTOUTPORTCONNECT_LEN, 1, 65535);
	if (RestrictOutPortCONNECT_len < 0)
		return (1);

	if (LoadParmMatrix(ImageQuality, conf_handler, "ImageQuality", DefaultImageQuality, 4, 4, 0, 100) < 0)
			return (1);

#ifdef EN_NAMESERVERS		
	n = qp_get_array_size (conf_handler, "Nameservers");
	if (n) {
		struct sockaddr_in nsaddr_new;

		if (n > MAXNS) {
			error_log_printf (LOGMT_ERROR, LOGSS_CONFIG, "You can not specify more than %d Nameservers.\n", MAXNS);
			error_log_printf (LOGMT_INFO, LOGSS_CONFIG, "Using nameserver 1 to %d and ignoring the rest.\n", MAXNS);
		}

		nsaddr_new.sin_family = AF_INET;
		nsaddr_new.sin_port = htons(53);

		res_init();
		_res.nscount = 0;

		for (i = 0; i < n && i < MAXNS; i++) {
			char *nserver_str;
			
			qp_getconf_array_str (conf_handler, "Nameservers", i, &nserver_str, QP_FLAG_NONE);
			inet_aton(nserver_str, &nsaddr_new.sin_addr);
			_res.nsaddr_list[_res.nscount++] = nsaddr_new;
		}
	}
#endif

	if (check_int_ranges ("MaxUncompressedImageRatio", MaxUncompressedImageRatio, 0, 100000))
		return (1);

#ifdef JP2K
	switch (JP2Colorspace_cfg) {
		case 1:
			JP2Colorspace = CENC_YUV;
			break;
		case 0:
			JP2Colorspace = CENC_RGB;
			break;
		default:
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Configuration error: Invalid JP2Colorspace value: %d\n", JP2Colorspace_cfg);
			return (1);
			break;
	}

	switch (JP2Upsampler_cfg) {
		case 0:
			JP2Upsampler = UPS_LINEAR;
			break;
		case 1:
			JP2Upsampler = UPS_LANCZOS;
			break;
		default:
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Configuration error: Invalid JP2Upsampler value: %d\n", JP2Upsampler_cfg);
			break;
	}

	if (LoadParmMatrix(JP2ImageQuality, conf_handler, "JP2ImageQuality", DefaultJP2ImageQuality, 4, 4, 1, 100) < 0)
		return (1);
	
	if (LoadParmMatrix(JP2BitResYA, conf_handler, "JP2BitResYA", DefaultJP2BitResYA, 8, 8, 1, 8) < 0)
		return (1);
	
	if (LoadParmMatrix(JP2BitResRGBA, conf_handler, "JP2BitResRGBA", DefaultJP2BitResRGBA, 16, 16, 1, 8) < 0)
		return (1);

	if (LoadParmMatrix(JP2BitResYUVA, conf_handler, "JP2BitResYUVA", DefaultJP2BitResYUVA, 16, 16, 1, 8) < 0)
		return (1);

	if (LoadParmMatrix(JP2CSamplingYA, conf_handler, "JP2CSamplingYA", DefaultJP2CSamplingYA, 32, 32, 0, 256) < 0)
		return (1);

	if (LoadParmMatrix(JP2CSamplingRGBA, conf_handler, "JP2CSamplingRGBA", DefaultJP2CSamplingRGBA, 64, 64, 0, 256) < 0)
		return (1);

	if (LoadParmMatrix(JP2CSamplingYUVA, conf_handler, "JP2CSamplingYUVA", DefaultJP2CSamplingYUVA, 64, 64, 0, 256) < 0)
		return (1);
#endif
	
	qp_end (conf_handler);
	return (0);
}

/* create strtable from parameters
 * dvalues = array of strings with default values (if any)
 * dvalues_len = size of dvalues (in elements)
 * returns: <pointer to t_st_strtable structure>, or NULL (fail) -- array length may be collected using strtable functions */
const t_st_strtable *LoadParmMatrixStr(t_qp_configfile *conf_handler, const char *conf_key, const char **dvalues, int dvalues_len, const int min_entries, const int max_entries)
{
	int entries;
	int position;
	char *indata;
	t_st_strtable *outdata;

	if ((outdata = st_create ()) == NULL)
		return (NULL);

	entries = qp_get_array_size (conf_handler, conf_key);

	// checks whether array is defined or not (an empty array will be considered 'undefined')
	// fill with default values if undefined
	if (entries == 0) {
		if (dvalues != NULL) {
			for (position = 0; position < dvalues_len; position++)
				st_insert (outdata, dvalues [position]);
			return (outdata);
		} else {
			st_destroy (outdata);
			return (NULL);
		}
	}
	
	if (((entries > max_entries) && (max_entries >= 0)) || ((entries < min_entries) && (min_entries >= 0))) {
		if (min_entries == max_entries) {
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Invalid number of entries in %s\n",
				"\tThe number of entries must be exactly: %d\n",
				conf_key, min_entries);
		} else {
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Invalid number of entries in %s\n",
				"\tThe number of entries must be between: %d - %d\n",
				conf_key, min_entries, max_entries);
		}
		st_destroy (outdata);

		return (NULL);
	}
	
	for (position = 0; position < entries; position++) {
		qp_getconf_array_str (conf_handler, conf_key, position, &indata, QP_FLAG_NONE);
		st_insert (outdata, indata);
	}

	return (outdata);
}

/* load url table from file
 * returns: <pointer to t_ut_urltable structure>, or NULL (table not loaded) */
const t_ut_urltable *load_URLtable (const char *urltable_filename)
{
	t_ut_urltable *urltable;
	
	if ((urltable = ut_create_populate_from_file (urltable_filename)) == NULL) {
		error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
			"Unable to load the following file: %s\n", urltable_filename);
		return (NULL);
	}

	return (urltable);
}

/* create cttable from parameters
 * dvalues = array of strings with default values (if any)
 * dvalues_len = size of dvalues (in elements)
 * returns: <pointer to t_ct_cttable structure>, or NULL (fail) -- array length may be collected using cttable functions */
const t_ct_cttable *LoadParmMatrixCT (t_qp_configfile *conf_handler, const char *conf_key, const char **dvalues, int dvalues_len, const int min_entries, const int max_entries, const int subtype_add_x_prefix)
{
	int entries;
	int position;
	char *indata;
	t_ct_cttable *outdata;

	if ((outdata = ct_create (subtype_add_x_prefix)) == NULL)
		return (NULL);

	entries = qp_get_array_size (conf_handler, conf_key);

	// checks whether array is defined or not (an empty array will be considered 'undefined')
	// fill with default values if undefined
	if (entries == 0) {
		if (dvalues != NULL) {
			for (position = 0; position < dvalues_len; position++)
				ct_insert (outdata, dvalues [position]);
			return (outdata);
		} else {
			ct_destroy (outdata);
			return (NULL);
		}
	}
	
	if (((entries > max_entries) && (max_entries >= 0)) || ((entries < min_entries) && (min_entries >= 0))) {
		if (min_entries == max_entries) {
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Invalid number of entries in %s\n"
				"The number of entries must be exactly: %d\n",
				conf_key, min_entries);
		} else {
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Invalid number of entries in %s\n"
				"The number of entries must be between: %d - %d\n",
				conf_key, min_entries, max_entries);
		}
		ct_destroy (outdata);

		return (NULL);
	}

	for (position = 0; position < entries; position++) {
		qp_getconf_array_str (conf_handler, conf_key, position, &indata, QP_FLAG_NONE);
		ct_insert (outdata, indata);
	}

	return (outdata);
}

/* get position in quality tables according to the image size */
int getImgSizeCategory (int width, int height)
{
	int imgcat;
	long i = width * height;
	
	if (i < 5000)
		imgcat = 0;
	else if ((i < 50000)|| (width < 150) || (height < 150))
		imgcat = 1;
	else if (i < 250000)
		imgcat = 2;
	else imgcat = 3;

	return (imgcat);
}

#ifdef JP2K

/* return a pointer to a array[4] with the bit lenght for JP2K YA components */
const int *getJP2KBitLenYA (int width, int height)
{
	int imgcat;

	imgcat = getImgSizeCategory (width, height);

	return (JP2BitResYA + (2 * imgcat));
}

/* return a pointer to a array[4] with the bit lenght for JP2K RGBA components */
const int *getJP2KBitLenRGBA (int width, int height)
{
	int imgcat;

	imgcat = getImgSizeCategory (width, height);

	return (JP2BitResRGBA + (4 * imgcat));
}

/* return a pointer to a array[4] with the bit lenght for JP2K YUVA components */
const int *getJP2KBitLenYUVA (int width, int height)
{
	int imgcat;

	imgcat = getImgSizeCategory (width, height);

	return (JP2BitResYUVA + (4 * imgcat));
}

/* return a pointer to a array[8] with the components' sampling parameters JP2K YA components */
const int *getJP2KCSamplingYA (int width, int height)
{
	int imgcat;

	imgcat = getImgSizeCategory (width, height);

	return (JP2CSamplingYA + (8 * imgcat));
}

/* return a pointer to a array[16] with the components' sampling parameters JP2K RGBA components */
const int *getJP2KCSamplingRGBA (int width, int height)
{
	int imgcat;

	imgcat = getImgSizeCategory (width, height);

	return (JP2CSamplingRGBA + (16 * imgcat));
}

/* return a pointer to a array[16] with the components' sampling parameters JP2K YUVA components */
const int *getJP2KCSamplingYUVA (int width, int height)
{
	int imgcat;

	imgcat = getImgSizeCategory (width, height);

	return (JP2CSamplingYUVA + (16 * imgcat));
}

/*
 * Fills in the values of JP2ImageQuality according to image dimensions. 
 * Returns index used to get the values ranging from 0 (smallest images)
 * to 3 (largest images).
 */
int getJP2ImageQuality (int width, int height)
{
	int imgcat;
	
	imgcat = getImgSizeCategory (width, height);
	return (JP2ImageQuality [imgcat]);
}

#endif

/*
 * Fills in the values of ImageQuality according to image dimensions. 
 * Returns index used to get the values ranging from 0 (smallest images)
 * to 3 (largest images).
 */
int getImageQuality (int width, int height)
{
	int imgcat;
	
	imgcat = getImgSizeCategory (width, height);
	return (ImageQuality [imgcat]);
}

/* returns:
 * lenght of array OR
 * < 0 if error */
int LoadParmMatrix(int *destarray, t_qp_configfile *conf_handler, const char *conf_key, const int *dvalues, const int min_entries, const int max_entries, const int min_value, const int max_value)
{
	int entries;
	int position;
	int indata;

	entries = qp_get_array_size (conf_handler, conf_key);

	// checks whether array is defined or not (an empty array will be considered 'undefined')
	// if no data available, use internal default values instead.
	if (entries == 0) {
		if (dvalues != NULL) {
			for (position = 0; position < max_entries; position++)
				destarray [position] = dvalues [position];
			return (max_entries);
		} else {
			return (0);
		}
	}

	if ((entries > max_entries) || (entries < min_entries)) {
		if (min_entries == max_entries) {
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Invalid number of entries in %s\n"
				"The number of entries must be exactly: %d\n",
				conf_key, min_entries);
		} else {
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Invalid number of entries in %s\n"
				"The number of entries must be between: %d - %d\n",
				conf_key, min_entries, max_entries);
		}

		return (-1);
	}
	
	for (position = 0; position < entries; position++) {
		qp_getconf_array_int (conf_handler, conf_key, position, &indata, QP_FLAG_NONE);
		if ((indata < min_value) || (indata > max_value)) {
			error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
				"Out of range values in %s\n", conf_key);
			return (-1);
		}
		destarray [position] = indata;
	}

	return (entries);
}

int check_int_ranges (const char *conf_key, const int inval, const int vlow, const int vhigh)
{
	if ((inval < vlow) || (inval > vhigh)) {
		error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
			"Out of range value for %s (Acceptable: %d to %d)\n", conf_key, vlow, vhigh);
		return (1);
	}
	return (0);
}

int check_int_minimum (const char *conf_key, const int inval, const int vlow)
{
	if (inval < vlow) {
		error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
			"Out of range value for %s (Minimum acceptable: %d)\n", conf_key, vlow);
		return (1);
	}
	return (0);
}

/* returns: ==0 if dirname is a directory,
   or !=0 (error) if does not exist or not a directory */
int check_directory (const char *conf_key, const char *dirname)
{
	struct stat f_stat;

	if (stat (dirname, &f_stat) == 0) {
		if (S_ISDIR (f_stat.st_mode))
			return (0);
	}

	error_log_printf (LOGMT_FATALERROR, LOGSS_CONFIG,
		"%s specifies an invalid directory path: %s\n", conf_key, dirname);
	return (1);
}

/* try to switch process to specified user and group.
   if group is unspecified (NULL), use the user's group.
   if both user_name and group_name == NULL, then do nothing and return 0.
   if check_only != 0, then just check things, but do not attempt to change to user/group.
   returns: ==0, ok; !=0 error (and software is expected to abort soon afterwards) */
int switch_to_user_group (const char *user_name, const char *group_name, int check_only)
{
	const char *effective_group_name = group_name;
	struct passwd *pw_data = NULL;
	struct group *gr_data = NULL;

	if ((user_name == NULL) && (effective_group_name == NULL))
		return (0);	/* nothing to do */

	/* get pw_data */
	if (user_name != NULL) {
		if ((pw_data = getpwnam (user_name)) == NULL) {
			error_log_printf (LOGMT_FATALERROR, LOGSS_UNSPECIFIED, "User not found: %s\n", user_name);
			return (1);
		}
	}

	/* get gr_data */
	if (effective_group_name == NULL) {
		if (user_name != NULL) {
			/* group is unspecified, collect user's group and use that instead */
			if ((gr_data = getgrgid (pw_data->pw_gid)) == NULL) {
				error_log_printf (LOGMT_FATALERROR, LOGSS_UNSPECIFIED,
					"Group not specified and user's group not found. User: %s\n", user_name);
				return (1);
			}
			effective_group_name = gr_data->gr_name;
		}
	} else {
		if ((gr_data = getgrnam (effective_group_name)) == NULL) {
			error_log_printf (LOGMT_FATALERROR, LOGSS_UNSPECIFIED,
				"Group not found: %s\n", effective_group_name);
			return (1);
		}
	}

	if (check_only != 0)
		return (0);

	/* change group (at this point certainly gr_data != NULL*/
	if (setgid (gr_data->gr_gid) != 0) {
		error_log_printf (LOGMT_FATALERROR, LOGSS_UNSPECIFIED, "Unable to switch to group: %s\n", effective_group_name);
		return (1);
	}

	/* change user */
	if (pw_data != NULL) {
		if (setuid (pw_data->pw_uid) != 0) {
			error_log_printf (LOGMT_FATALERROR, LOGSS_UNSPECIFIED, "Unable to switch to user: %s\n", user_name);
			return (1);
		}
	}

	return (0);
}

