/* Distributed Checksum Clearinghouse
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.2.74-1.31 $Revision$
 */

#include "dcc_ck.h"


static char *
ck_word(char *p,
	const char *word,
	int word_len)
{
	int sps;

	if (strncasecmp(p, word, word_len))
		return 0;

	p += word_len;
	sps = strspn(p, DCC_WHITESPACE);
	if (sps == 0)
		return 0;

	return p+sps;
}



u_char					/* 0=failed, 1=ok, 2=resolved names */
dcc_parse_whitefile(DCC_EMSG emsg,
		    DCC_WF *wf,
		    const DCC_PATH white_nm_base,
		    FILE *white_file,	/* stdio for white_nm_base */
		    DCC_WHITE_TBL **winfo,
		    DCC_PARSED_CK_FNC fnc,
		    DCC_PARSED_CIDR_CK_FNC cidr_fnc,
		    DCC_PARSED_CK_LOCK unlock)	/* unlock if doing DNS */
{
#   define FG_ON(nm) (wf->new_info_flags |= (nm))
#   define FG_OFF(nm) (wf->new_info_flags &= ~(nm))
	char buf[200];
	char *type, *ck, *p;
	DCC_TGTS new_tgts;
	const char *white_nm;
	u_int white_nm_num = 0;
	DCC_PATH cur_white_nm;
	FILE *cur_white_file, *new_white_file;
	int main_lineno, lineno;
	u_char result, hex;
	int i;

	result = 1;
	cur_white_file = white_file;
	white_nm = white_nm_base;
	main_lineno = lineno = 0;
	new_tgts = DCC_TGTS_INVALID;
	for (;;) {
		/* Each substantive line has one of the forms:
		 *	tgts	[hex] type	string
		 *		[hex] type	string
		 *	include	pathname
		 *	option ...
		 * A missing number of targets means the line has the
		 * same number of targets as the previous line */

		if (!fgets(buf, sizeof(buf), cur_white_file)) {
			if (ferror(cur_white_file)) {
				dcc_pemsg(EX_IOERR, emsg,
					  "fgets(whitelist %s): %s",
					  DCC_NM2PATH(white_nm), ERROR_STR());
				break;
			}
			/* at the end of an included file,
			 * return to the main file */
			if (cur_white_file != white_file) {
				fclose(cur_white_file);
				cur_white_file = white_file;
				white_nm = white_nm_base;
				lineno = main_lineno;
				continue;
			}
			return result;
		}

		++lineno;

		/* Ignore blank lines and lines starting with '#' */
		type = buf+strspn(buf, DCC_WHITESPACE);
		if (*type == '\0' || *type == '#')
			continue;

		/* trim trailing blanks */
		p = type+strlen(type);
		while (--p > type) {
			if (*p != ' ' && *p != '\t'
			    && *p != '\n' && *p != '\r')
				break;
			*p = '\0';
		}

		/* parse
		 *	include	pathname */
		p = ck_word(type, "include", STRZ("include"));
		if (p) {
			if (cur_white_file != white_file) {
				dcc_pemsg(EX_DATAERR, emsg,
					  "nested \"include\" not allowed%s",
					  fnm_lineno(white_nm_base, lineno));
				result = 0;
				continue;
			}

			/* trim quotes if present from the file name */
			i = strlen(p);
			if (i > 1
			    && ((p[0] == '"' && p[i-1] == '"')
				|| (p[0] == '<' && i > 1 && p[i-1] == '>'))) {
				p[i-1] = '\0';
				++p;
				i -= 2;
			}

			if (i == 0 || i >= ISZ(cur_white_nm)) {
				dcc_pemsg(EX_DATAERR, emsg,
					  "unrecognized \"include %s\"%s",
					  p, fnm_lineno(white_nm, lineno));
				result = 0;
				continue;
			}
			if (white_nm_num >= DIM((*winfo)->hdr.white_incs)) {
				dcc_pemsg(EX_DATAERR, emsg,
					  "too many \"include\" files%s",
					  fnm_lineno(white_nm_base, lineno));
				result = 0;
				continue;
			}
			new_white_file = fopen(p, "r");
			if (!new_white_file) {
				dcc_pemsg(EX_DATAERR, emsg,
					  "\"include %s\": %s%s",
					  p, ERROR_STR(),
					  fnm_lineno(white_nm_base, lineno));
				result = 0;
				continue;
			}

			/* Adding entries to the hash table can
			 * cause it to move, invalidating any
			 * pointers into it. */
			if (winfo)
				BUFCPY((*winfo)->hdr.white_incs[white_nm_num],
				       p);
			++white_nm_num;
			strcpy(cur_white_nm, p);
			white_nm = cur_white_nm;
			cur_white_file = new_white_file;
			main_lineno = lineno;
			lineno = 0;
			continue;
		}

		/* honor greylist controls in client whitelists of the forms:
		 *	option log- {all,normal}
		 *	option greylist-{on,off,log-on,log-off}
		 *	option dcc-{on,off}
		 *	option dnsbl-{on,off}
		 */
		p = ck_word(type, "option", STRZ("option"));
		if (p) {
			if (!wf->info) {
				dcc_pemsg(EX_DATAERR, emsg, "\"option\""
					  " not legal in server whitelist%s",
					  fnm_lineno(white_nm, lineno));
				result = 0;
				continue;
			}
			if (!CSTRCMP(p, "log-")) {
				p += STRZ("log-");
				if (!strcasecmp(p, "all")) {
					FG_ON(DCC_WHITE_FG_LOG_ALL);
					continue;
				}
				if (!strcasecmp(p, "normal")) {
					FG_ON(DCC_WHITE_FG_LOG_NORMAL);
					continue;
				}
			} else if (!CSTRCMP(p, "greylist-")) {
				p +=  STRZ("greylist-");
				if (!strcasecmp(p, "on")) {
					FG_ON(DCC_WHITE_FG_GREY_ON);
					FG_OFF(DCC_WHITE_FG_GREY_OFF);
					continue;
				}
				if (!strcasecmp(p, "off")) {
					FG_ON(DCC_WHITE_FG_GREY_OFF);
					FG_OFF(DCC_WHITE_FG_GREY_ON);
					continue;
				}
				if (!strcasecmp(p, "log-on")) {
					FG_ON(DCC_WHITE_FG_GREY_LOG_ON);
					FG_OFF(DCC_WHITE_FG_GREY_LOG_OFF);
					continue;
				}
				if (!strcasecmp(p, "log-off")) {
					FG_ON(DCC_WHITE_FG_GREY_LOG_OFF);
					FG_OFF(DCC_WHITE_FG_GREY_LOG_ON);
					continue;
				}
			} else if (!CSTRCMP(p, "dcc-")) {
				p += STRZ("dcc-");
				if (!strcasecmp(p, "on")) {
					FG_ON(DCC_WHITE_FG_DCC_ON);
					FG_OFF(DCC_WHITE_FG_DCC_OFF);
					continue;
				}
				if (!strcasecmp(p, "off")) {
					FG_ON(DCC_WHITE_FG_DCC_OFF);
					FG_OFF(DCC_WHITE_FG_DCC_ON);
					continue;
				}
			} else if (!CSTRCMP(p, "DNSBL-")) {
				p += STRZ("DNSBL-");
				if (!strcasecmp(p, "on")) {
					FG_ON(DCC_WHITE_FG_DNSBL_ON);
					FG_OFF(DCC_WHITE_FG_DNSBL_OFF);
					continue;
				}
				if (!strcasecmp(p, "off")) {
					FG_ON(DCC_WHITE_FG_DNSBL_OFF);
					FG_OFF(DCC_WHITE_FG_DNSBL_ON);
					continue;
				}
			}
			dcc_pemsg(EX_DATAERR, emsg,
				  "unrecognized option%s",
				  fnm_lineno(white_nm, lineno));
			result = 0;
			continue;
		}

		/* honor logging controls in the obsolete form
		 *	log {all-grey,no-grey}
		 */
		p = ck_word(type, "log", STRZ("log"));
		if (p) {
			if (!wf->info) {
				dcc_pemsg(EX_DATAERR, emsg, "\"log\""
					  " not legal in server whitelist%s",
					  fnm_lineno(white_nm, lineno));
				result = 0;
				continue;
			}
			if (!strcasecmp(p, "all-grey")) {
				FG_ON(DCC_WHITE_FG_GREY_LOG_ON);
			} else if (!strcasecmp(p, "no-grey")) {
				FG_ON(DCC_WHITE_FG_GREY_LOG_OFF);
			} else {
				dcc_pemsg(EX_DATAERR, emsg, "syntax error%s",
					  fnm_lineno(white_nm, lineno));
				result = 0;
			}
			continue;
		}


		/* Look for the number of targets in a simple line */
		if (type != buf) {
			/* If the line started with white space, the number
			 * of targets is the same as the previous line. */
			buf[0] = '\0';
		} else {
			type += strcspn(type, DCC_WHITESPACE);
			if (*type == '\0') {
				dcc_pemsg(EX_DATAERR, emsg,
					  "missing type in %s%s",
					  buf, fnm_lineno(white_nm, lineno));
				result = 0;
				continue;
			}
			*type++ = '\0';
			/* buf now starts with null-terminated
			 * number of targets, "include", or "log" */
			type += strspn(type, DCC_WHITESPACE);
		}

		ck = type+strcspn(type, DCC_WHITESPACE);

		if (*ck != '\0') {
			/* null terminate the type */
			*ck++ = '\0';
			ck += strspn(ck, DCC_WHITESPACE);
		}

		if (strcasecmp(type, "hex")) {
			hex = 0;
		} else {
			hex = 1;
			type = ck;
			ck = type+strcspn(type, DCC_WHITESPACE);
			if (*ck != '\0') {
				*ck++ = '\0';
				ck += strspn(ck, DCC_WHITESPACE);
			}
		}

		/* parse the target count if it is present instead of blank */
		if (buf[0] != '\0')
			new_tgts = dcc_str2cnt(buf);
		if (new_tgts == 0 || new_tgts == DCC_TGTS_INVALID) {
			dcc_pemsg(EX_DATAERR, emsg,
				  "missing or invalid # of targets \"%s\"%s",
				  buf, fnm_lineno(white_nm, lineno));
			new_tgts = DCC_TGTS_INVALID;
			result = 0;
			continue;
		}

		if (*ck == '\0') {
			dcc_pemsg(EX_DATAERR, emsg, "missing value%s",
				  fnm_lineno(white_nm, lineno));
			result = 0;
			continue;
		}

		/* Look for the name of the checksum, compute the checksum,
		 * and write the checksum.  If it is a host name, write
		 * all of its aliases to the hash table. */
		if (hex) {
			i = dcc_parse_hex_ck(emsg, wf, white_nm, lineno,
					     type, ck, new_tgts,
					     fnc);
		} else {
			i = dcc_parse_ck(emsg, wf, white_nm, lineno,
					 type, ck, new_tgts,
					 fnc, cidr_fnc, unlock);
		}
		if (i <= 0) {
			result = 0;
			if (i < 0)
				break;
		} else if (i == 2 && result == 1) {
			result = 2;
		}
	}

	/* failed */
	if (cur_white_file && cur_white_file != white_file)
		fclose(cur_white_file);
	return 0;

#undef FG_ON
#undef FG_OFF
}
