view dcclib/load_ids.c @ 0:c7f6b056b673

First import of vendor version
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 13:49:58 +0100
parents
children
line wrap: on
line source

/* Distributed Checksum Clearinghouse
 *
 * client-ID and password parsing
 *
 * Copyright (c) 2008 by Rhyolite Software, LLC
 *
 * This agreement is not applicable to any entity which sells anti-spam
 * solutions to others or provides an anti-spam solution as part of a
 * security solution sold to other entities, or to a private network
 * which employs the DCC or uses data provided by operation of the DCC
 * but does not provide corresponding data to other users.
 *
 * Permission to use, copy, modify, and distribute this software without
 * changes for any purpose with or without fee is hereby granted, provided
 * that the above copyright notice and this permission notice appear in all
 * copies and any distributed versions or copies are either unchanged
 * or not called anything similar to "DCC" or "Distributed Checksum
 * Clearinghouse".
 *
 * Parties not eligible to receive a license under this agreement can
 * obtain a commercial license to use DCC by contacting Rhyolite Software
 * at sales@rhyolite.com.
 *
 * A commercial license would be for Distributed Checksum and Reputation
 * Clearinghouse software.  That software includes additional features.  This
 * free license for Distributed ChecksumClearinghouse Software does not in any
 * way grant permision to use Distributed Checksum and Reputation Clearinghouse
 * software
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
 * 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.3.103-1.50 $Revision$
 */

#include "dcc_clnt.h"
#include "dcc_ids.h"
#include "dcc_heap_debug.h"

/* must be in dcclib for WIN32 */

DCC_PATH ids_path;

time_t ids_mtime;


/* authenticated client database
 * assume there will be at most 500 clients and servers known to each server */
#define ID_TBL_LEN 509			/* must be prime */
#define ID_TBL_MAX (ID_TBL_LEN*4)
#define ID_HASH(id) (id % ID_TBL_LEN)
#define ID_HASH_ENTRY(id) id_tbl_hash[ID_HASH(id)]
static ID_TBL *id_tbl_hash[ID_TBL_LEN];
static int id_tbl_len = 0;
static ID_TBL *id_tbl_free;


/* find an ID_TBL entry	*/
ID_TBL *
find_id_tbl(DCC_CLNT_ID id)
{
	ID_TBL *tp;

	for (tp = ID_HASH_ENTRY(id); tp != 0; tp = tp->fwd) {
		if (tp->id == id)
			return tp;
	}
	return 0;
}



/* add an ID_TBL entry that is known to be absent */
ID_TBL *
add_id_tbl(DCC_CLNT_ID id)
{
	ID_TBL *tp, **tpp;
	int i;

	/* make more entries if necessary */
	if (!id_tbl_free) {
		i = 16;
		if (id_tbl_len <= ID_TBL_MAX
		    && id_tbl_len+i > ID_TBL_MAX)
			dcc_error_msg("ID table overflow");
		id_tbl_len += i;
		tp = dcc_malloc(i*sizeof(*tp));
		do {
			tp->fwd = id_tbl_free;
			id_tbl_free = tp;
		} while (++tp, --i > 0);
	}

	tp = id_tbl_free;
	id_tbl_free = tp->fwd;

	memset(tp, 0, sizeof(*tp));
	tp->id = id;
	tpp = &ID_HASH_ENTRY(id);
	tp->fwd = *tpp;
	*tpp = tp;

	return tp;
}



ID_TBL *
enum_ids(ID_TBL *tp)
{
	ID_TBL **tpp;

	if (!tp) {
		tpp = id_tbl_hash;
	} else {
		if (tp->fwd)
			return tp->fwd;
		tpp = &ID_HASH_ENTRY(tp->id)+1;
	}
	while (tpp <= LAST(id_tbl_hash)) {
		tp = *tpp;
		if (tp)
			return tp;
		++tpp;
	}
	return 0;
}



u_char					/* 0=bad 1=ok 2=forever */
parse_dccd_delay(DCC_EMSG emsg, time_t *delay_usp, u_int *delay_inflatep,
		 const char *val,
		 const char *fnm, int lno)
{
	DCC_FNM_LNO_BUF fnm_buf;
	time_t delay_ms;
	u_long l;
	char *p1, *p2;

	*delay_inflatep = DCC_ANON_INFLATE_OFF;
	*delay_usp = DCC_ANON_DELAY_US_BLACKLIST;
	if (!strcasecmp(val, "forever"))
		return 2;

	delay_ms = strtoul(val, &p1, 10);

	if (delay_ms > DCC_ANON_DELAY_US_BLACKLIST/1000) {
		dcc_pemsg(EX_DATAERR, emsg, "invalid delay \"%s\"%s > %d", val,
			  fnm_lno(&fnm_buf, fnm, lno),
			  DCC_ANON_DELAY_US_BLACKLIST/1000);
		return 0;
	} else if (*p1 == '\0') {
		*delay_inflatep = DCC_ANON_INFLATE_OFF;
	} else if (*p1 != ',' && *p1 != '*') {
		dcc_pemsg(EX_DATAERR, emsg, "unrecognized delay \"%s\"%s", val,
			  fnm_lno(&fnm_buf, fnm, lno));
		return 0;
	} else {
		l = strtoul(++p1, &p2, 10);
		if (*p2 != '\0') {
			dcc_pemsg(EX_DATAERR, emsg,
				  "unrecognized delay inflation \"%s\"%s",
				  p1,
				  fnm_lno(&fnm_buf, fnm, lno));
			return 0;
		}
		if (l == 0)
			l = DCC_ANON_INFLATE_OFF;
		*delay_inflatep = l;
	}

	*delay_usp = delay_ms*1000;
	return 1;
}



u_char
set_ids_path(DCC_EMSG emsg, const char *ids_nm)
{
	if (!ids_nm || ids_nm[0] == '\0')
		ids_nm = IDS_NM_DEF;
	if (!fnm2rel(ids_path, ids_nm, 0)) {
		dcc_pemsg(EX_DATAERR, emsg, "\"%s\" is a bad ids file name",
			  ids_nm);
		return 0;
	}
	return 1;
}



/* (re)load the client-ID and password database
 * -1=failed to find target,  0=sick file,  1=ok,  2=file unchanged */
int
load_ids(DCC_EMSG emsg,
	 DCC_CLNT_ID tgt_id,		/* DCC_ID_ANON or needed ID */
	 const ID_TBL **tgt_tbl,
	 u_char force)
{
	DCC_FNM_LNO_BUF fnm_buf;
	ID_TBL t, *tp, **tpp;
	FILE *f;
	int lno, passwords;
	u_char found_it;
	int result;
	char buf[sizeof(ID_TBL)*2+1];
	const char *bufp;
	char id_buf[30];
	struct stat sb;
	char *p, *p1;

	if (tgt_tbl)
		*tgt_tbl = 0;

	if (!set_ids_path(emsg, 0))
		return -1;

	if (!force) {
		if (!dcc_ck_private(emsg, &sb, ids_path, -1)) {
			ids_mtime = 0;
			return -1;
		}

		if (ids_mtime == sb.st_mtime)
			return 2;
	}

	f = fopen(ids_path, "r");
	if (!f) {
		dcc_pemsg(EX_NOINPUT, emsg, "fopen(%s): %s",
			  fnm2abs_err(0, ids_path), ERROR_STR());
		return -1;
	}

	/* the file contains passwords, so refuse to use it if anyone else
	 * can read it */
	if (!dcc_ck_private(emsg, &sb, ids_path, fileno(f))) {
		fclose(f);
		ids_mtime = 0;
		return -1;
	}

	/* empty the table */
	for (tpp = id_tbl_hash; tpp <= LAST(id_tbl_hash); ++tpp) {
		while ((tp = *tpp) != 0) {
			*tpp = tp->fwd;
			memset(tp, 0, sizeof(*tp));
			tp->fwd = id_tbl_free;
			id_tbl_free = tp;
		}
	}

	ids_mtime = sb.st_mtime;

	passwords = 0;
	lno = 0;
	result = 1;
	found_it = (tgt_id == DCC_ID_ANON);
	for (;;) {
		/* read and parse a line contain a client-ID and key(s) */
		bufp = fgets(buf, sizeof(buf), f);
		if (!bufp) {
			if (ferror(f))
				dcc_pemsg(EX_IOERR, emsg, "fgets(%s): %s",
					  fnm2abs_err(0, ids_path),
					  ERROR_STR());
			break;
		}
		++lno;

		/* Ignore blank lines and lines starting with '#'.
		 * Note that '#' flags a comment only at the start of
		 * the line to avoid dealing with the escaping hassles
		 * of allowing '#' in passwords. */
		bufp += strspn(bufp, DCC_WHITESPACE);
		if (*bufp == '\0' || *bufp == '#')
			continue;

		memset(&t, 0, sizeof(t));
		t.delay_inflate = DCC_ANON_INFLATE_OFF;

		/* Each substantive line has the form:
		 *
		 *	ID[,rpt-ok][,delay=ms[*inflate]] password1 password2
		 *	ID,delay=forever
		 *
		 *  Both passwords are always accepted.  They are intended
		 *  to be the previous and current or the current and
		 *  next to allow the password to be changed at both the
		 *  client and the server without loss of service. */

		bufp = dcc_parse_word(emsg, id_buf, sizeof(id_buf),
				      bufp, "ID", ids_path, lno);
		if (!bufp) {
			result = 0;
			continue;
		}

		if (*bufp)
			t.flags |= ID_FLG_SETTINGS;

		p = strchr(id_buf, ',');
		if (p) {
			*p++ = '\0';
			t.flags |= ID_FLG_SETTINGS;
		}
		t.id = dcc_get_id(emsg, id_buf, ids_path, lno);
		if (t.id == DCC_ID_INVALID) {
			result = 0;
			continue;
		}
		if (t.id == DCC_ID_ANON) {
			if (result) {
				dcc_pemsg(EX_DATAERR, emsg,
					  "invalid ID \"%s\"%s",
					  id_buf,
					  fnm_lno(&fnm_buf, ids_path, lno));
				result = 0;
			}
			continue;
		}

		for (; p; p = p1) {
			p1 = strchr(p, ',');
			if (p1)
				*p1++ = '\0';

			if (t.id >= DCC_CLNT_ID_MIN
			    && t.id <= DCC_CLNT_ID_MAX) {
				if (!strcasecmp(p, "rpt-ok")
				    || !strcasecmp(p, "rpt_ok")) {
					t.flags |= ID_FLG_RPT_OK;
					continue;
				}

				if (!CLITCMP(p, "delay=")) {
					if (!parse_dccd_delay(emsg, &t.delay_us,
							&t.delay_inflate,
							p+LITZ("delay="),
							ids_path, lno)) {
					    result = 0;
					}
					continue;
				}
			}
			if (t.id >= DCC_SRVR_ID_MIN
			    && t.id <= DCC_SRVR_ID_MAX
			    && t.srvr_type == 0) {
				if (!strcasecmp(p, "simple")) {
					continue;
				}
				if (!strcasecmp(p, "ignore")) {
					continue;
				}
				if (!strcasecmp(p, "rogue")) {
					continue;
				}
				if (!strcasecmp(p, "commercial")) {
					continue;
				}
			}
			if (result) {
				dcc_pemsg(EX_DATAERR, emsg,
					  "invalid option \"%s\"%s", p,
					  fnm_lno(&fnm_buf, ids_path, lno));
				result = 0;
			}
		}

		bufp = parse_passwd(emsg, t.cur_passwd,
				    bufp, "current password", ids_path, lno);
		if (!bufp) {
			result = 0;
			continue;
		}
		bufp = parse_passwd(emsg, t.next_passwd,
				    bufp, "next password", ids_path, lno);
		if (!bufp) {
			result = 0;
			continue;
		}
		if (*bufp != '\0') {
			if (result) {
				dcc_pemsg(EX_DATAERR, emsg,
					  "invalid next password for ID %d%s",
					  t.id,
					  fnm_lno(&fnm_buf, ids_path, lno));
				result = 0;
			}
			continue;
		}

		/* put the entry into the hash table if not already present */
		tp = find_id_tbl(t.id);
		if (!tp)
			tp = add_id_tbl(t.id);

		/* If the ID is already present, the file is bad unless
		 * the previous or current line is
		 * only a placeholder showing that the ID exists. */
		if ((tp->flags & ID_FLG_SETTINGS)
		    && (t.flags & ID_FLG_SETTINGS)) {
			if (result) {
				dcc_pemsg(EX_DATAERR, emsg, "duplicate ID %d%s",
					  t.id, fnm_lno(&fnm_buf,
							ids_path, lno));
				result = 0;
			}
		}

		/* copy settings to the hash table */
		if (t.flags & ID_FLG_SETTINGS) {
			tp->flags = t.flags;
			tp->srvr_type = t.srvr_type;
			tp->delay_us = t.delay_us;
			tp->delay_inflate = t.delay_inflate;

			if (t.cur_passwd[0] != '\0') {
				++passwords;
				memcpy(tp->cur_passwd, t.cur_passwd,
				       sizeof(tp->cur_passwd));
				memcpy(tp->next_passwd, t.next_passwd,
				       sizeof(tp->next_passwd));

				/* remember target password */
				if (t.id == tgt_id) {
					found_it = 1;
					if (tgt_tbl)
					    *tgt_tbl = tp;
				}
			}
		}
	}
	fclose(f);

	if (result && !passwords) {
		dcc_pemsg(EX_DATAERR, emsg, "%s contains no passwords",
			  fnm2abs_err(0, ids_path));
		result = -1;
	}
	if (!found_it && result >= 0) {
		dcc_pemsg(EX_DATAERR, emsg,
			  "%s does not contain the password for ID %d",
			  fnm2abs_err(0, ids_path), tgt_id);
		result = -1;
	}

	return result;
}