/* Distributed Checksum Clearinghouse * * client-ID and password parsing * * Copyright (c) 2006 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 for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * Parties not eligible to receive a license under this agreement can * obtain a commercial license to use DCC and permission to use * U.S. Patent 6,330,590 by contacting Commtouch at http://www.commtouch.com/ * or by email to nospam@commtouch.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.50-1.35 $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 about 8000 clients and servers known to each server */ #define ID_TBL_LEN 4001 /* must be prime */ #define ID_TBL_MAX (ID_TBL_LEN*4) #define ID_HASH(id) id_tbl_hash[id % ID_TBL_LEN] 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(id); tp != 0; tp = tp->fwd) { if (tp->id == id) return tp; } return 0; } u_char 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_ms = strtoul(val, &p1, 0); if (delay_ms > DCC_ANON_DELAY_MAX/1000) { dcc_pemsg(EX_DATAERR, emsg, "invalid delay \"%s\"%s", val, fnm_lno(fnm_buf, fnm, lno)); 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, 0); 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_path[0] != '\0' && (!ids_nm || ids_nm[0] == '\0')) return 1; if (!ids_nm || ids_nm[0] == '\0') ids_nm = IDS_NM_DEF; if (!fnm2path(ids_path, ids_nm, 0)) { dcc_pemsg(EX_DATAERR, emsg, "\"%s\" is too long", ids_nm); return 0; } return 1; } /* (re)load the client-ID and password database */ int /* -1=failed, 0=sick file, 1=ok */ load_ids(DCC_EMSG emsg, ID_TBL **tgt_tbl, DCC_CLNT_ID tgt_id) { DCC_FNM_LNO_BUF fnm_buf; ID_TBL t, *tp, **tpp; FILE *f; int lno, total; u_char found_it; int status; char buf[sizeof(ID_TBL)*2+1]; const char *bufp; char id_buf[30]; struct stat sb; char *p; int i; if (!set_ids_path(emsg, 0)) return 0; if (tgt_tbl) *tgt_tbl = 0; f = fopen(ids_path, "r"); if (!f) { dcc_pemsg(EX_NOINPUT, emsg, "fopen(%s): %s", 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; } ids_mtime = sb.st_mtime; /* Mark the existing IDs so we can delete them if they've * disappeared from the ASCII file. This is more complicated * than deleting and recreating the table from scratch, but the * results are better when a bad entry is added to the file. */ for (i = 0; i < DIM(id_tbl_hash); i++) { for (tpp = &id_tbl_hash[i]; (tp = *tpp) != 0; tpp = &tp->fwd) tp->flags &= ~ID_FLG_MARKED; } total = 0; lno = 0; status = 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(ID file %s): %s", 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] password1 password2 * * 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) { if (status > 0) status = 0; continue; } p = strchr(id_buf, ','); if (p) *p++ = '\0'; t.id = dcc_get_id(emsg, id_buf, ids_path, lno); if (t.id == DCC_ID_INVALID) { if (status > 0) status = 0; continue; } if (t.id == DCC_ID_ANON) { dcc_pemsg(EX_DATAERR, emsg, "invalid ID \"%s\"%s", id_buf, fnm_lno(fnm_buf, ids_path, lno)); if (status > 0) status = 0; continue; } while (p) { char *p1 = strchr(p, ','); if (p1) *p1++ = '\0'; if (t.id >= DCC_CLNT_ID_MIN && t.id <= DCC_CLNT_ID_MAX && (!strcasecmp(p, "rpt-ok") || !strcasecmp(p, "rpt_ok"))) { t.flags |= ID_FLG_RPT_OK; } else if (t.id >= DCC_CLNT_ID_MIN && t.id <= DCC_CLNT_ID_MAX && !CSTRCMP(p, "delay=")) { if (!parse_dccd_delay(emsg, &t.delay_us, &t.delay_inflate, p+STRZ("delay="), ids_path, lno)) if (status > 0) status = 0; } else if (status > 0) { dcc_pemsg(EX_DATAERR, emsg, "invalid option \"%s\"%s", p, fnm_lno(fnm_buf, ids_path, lno)); status = 0; } p = p1; } bufp = parse_passwd(emsg, t.cur_passwd, bufp, "current password", ids_path, lno); if (!bufp) { if (status > 0) status = 0; continue; } bufp = parse_passwd(emsg, t.next_passwd, bufp, "next password", ids_path, lno); if (!bufp) { if (status > 0) status = 0; continue; } if (*bufp != '\0') { dcc_pemsg(EX_DATAERR, emsg, "invalid next password for ID %d%s", t.id, fnm_lno(fnm_buf, ids_path, lno)); if (status > 0) status = 0; continue; } /* put the entry into the hash table if it is not already * present and update it if it is */ for (tpp = &ID_HASH(t.id); ; tpp = &tp->fwd) { tp = *tpp; if (!tp) { /* it was not present, so add it */ if (!id_tbl_free) { /* make more entries if necessary */ 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)); if (!tp) { dcc_pemsg(EX_OSERR, emsg, "malloc(%d IDs) failed", i); if (status > 0) status = 0; goto bad_id; } do { tp->fwd = id_tbl_free; id_tbl_free = tp; } while (++tp, --i > 0); } /* use the next free entry */ tp = id_tbl_free; id_tbl_free = tp->fwd; memset(tp, 0, sizeof(*tp)); tp->id = t.id; *tpp = tp; break; } if (tp->id == t.id) { /* If the ID is already present, * the file is bad unless the previous * or current line is a mere marker. */ if ((tp->flags & ID_FLG_MARKED) && tp->cur_passwd[0] != '\0' && t.cur_passwd[0] != '\0') { dcc_pemsg(EX_DATAERR, emsg, "duplicate ID %d%s", t.id, fnm_lno(fnm_buf,ids_path, lno)); if (status > 0) status = 0; } break; } } if (tp->flags & ID_FLG_MARKED) { if (t.flags & ID_FLG_RPT_OK) tp->flags |= ID_FLG_RPT_OK; if (t.delay_us != 0) { tp->delay_us = t.delay_us; tp->delay_inflate = t.delay_inflate; } } else { tp->flags |= ID_FLG_MARKED; tp->flags &= ~ID_FLG_RPT_OK; if (t.flags & ID_FLG_RPT_OK) tp->flags |= ID_FLG_RPT_OK; tp->delay_us = t.delay_us; tp->delay_inflate = t.delay_inflate; } if (t.cur_passwd[0] != '\0') { ++total; memcpy(tp->cur_passwd, t.cur_passwd, sizeof(tp->cur_passwd)); memcpy(tp->next_passwd, t.next_passwd, sizeof(tp->next_passwd)); /* remember special password */ if (tp->id == tgt_id && t.cur_passwd[0] != '\0') { found_it = 1; if (tgt_tbl) *tgt_tbl = tp; } } bad_id:; } fclose(f); if (status > 0 && !total) { dcc_pemsg(EX_DATAERR, emsg, "%s contains no passwords", ids_path); status = 0; } if (!found_it) { dcc_pemsg(EX_DATAERR, emsg, "%s does not contain the password for ID %d", ids_path, tgt_id); status = -1; } /* If things are ok so far, forget old entries. * This ensures that we do not forget the server * password when we are given a sick database */ if (status > 0) { for (i = 0; i < DIM(id_tbl_hash); i++) { tpp = &id_tbl_hash[i]; while ((tp = *tpp) != 0) { /* skip entries seen in the file */ if (tp->flags & ID_FLG_MARKED) { tpp = &tp->fwd; continue; } if (tp->id == tgt_id) { dcc_pemsg(EX_DATAERR, emsg, "missing server passwd in %s", ids_path); tpp = &tp->fwd; status = 0; } else { /* delete other entries */ *tpp = tp->fwd; memset(tp, 0, sizeof(*tp)); tp->fwd = id_tbl_free; id_tbl_free = tp; } } } } return status; } /* load the ids file if it has changed */ u_char /* 0=failed, 1=reloaded, 2=up to date */ check_load_ids(DCC_EMSG emsg, DCC_CLNT_ID tgt_id) { struct stat sb; if (!set_ids_path(emsg, 0)) return 0; if (!dcc_ck_private(emsg, &sb, ids_path, -1)) { ids_mtime = 0; return 0; } if (ids_mtime == sb.st_mtime) return 2; return load_ids(emsg, 0, tgt_id) > 0; }