/* Distributed Checksum Clearinghouse * * client-ID and password parsing * * --C-LICENSE-- * $Revision: 1.65 $ */ #include "dcc_clnt.h" #include "dcc_ids.h" #include "dcc_xhdr.h" #include "dcc_heap_debug.h" DCC_PATH ids_path; time_t ids_mtime; static ID_TBL **id_tbl_hash; static u_int id_tbl_bins, id_tbl_blocks; static ID_TBL *id_tbl_free; #define ID_HASH_ENTRY(id) id_tbl_hash[(id) % id_tbl_bins] static u_int id_probes, id_searches; /* Make more ID blocks. * IDs come from either the local /var/dcc/ids file or * server IDs in floods. There are fewer than 65536 possible * server-IDs. It would be bad but should not crash a system * to see 65536 server-IDs. */ static void id_make_blocks(u_char debug) { ID_TBL *tp; double rate; int i; i = 128; if (debug && id_searches != 0) { rate = id_probes / id_searches; dcc_trace_msg("increase from %d to %d ID entries with" " %d bins; average search length %.1f", id_tbl_blocks, id_tbl_blocks+i, id_tbl_bins, rate); } id_tbl_blocks += i; tp = dcc_malloc(i*sizeof(*tp)); do { tp->hfwd = id_tbl_free; id_tbl_free = tp; } while (++tp, --i > 0); } /* link an ID block into the head of its hash bin */ static void id_tbl_link(ID_TBL *tp, ID_TBL **bin) { if (*bin == tp) return; if (tp->hbak != 0) tp->hbak->hfwd = tp->hfwd; if (tp->hfwd != 0) tp->hfwd->hbak = tp->hbak; tp->hbak = 0; tp->hfwd = *bin; if (tp->hfwd != 0) tp->hfwd->hbak = tp; *bin = tp; } static void id_tbl_expand_hash(u_char debug) { u_int old_bins; ID_TBL *tp, **old_bin, **old_hash; double rate; int i; old_hash = id_tbl_hash; old_bins = id_tbl_bins; i = (max(id_tbl_blocks, id_tbl_bins)*3)/2; id_tbl_bins = hash_divisor(i, 0); if (id_tbl_bins != old_bins) { id_tbl_hash = dcc_malloc(id_tbl_bins*sizeof(*id_tbl_hash)); if (!id_tbl_hash) dcc_logbad(EX_OSERR, "malloc(%d ID hash table) failed", id_tbl_bins); memset(id_tbl_hash, 0, id_tbl_bins*sizeof(ID_TBL *)); if (old_hash) { if (debug) { rate = id_probes; if (id_searches != 0) rate /= id_searches; dcc_trace_msg("increase from %d to %d" " ID bins for %d blocks;" " average search length %.1f", old_bins, id_tbl_bins, id_tbl_blocks, rate); } /* copy the blocks to the new hash table of bins */ for (old_bin = &old_hash[0]; old_bin < &old_hash[old_bins]; ++old_bin) { while ((tp = *old_bin) != 0) { *old_bin = tp->hfwd; tp->hbak = 0; tp->hfwd = 0; id_tbl_link(tp, &ID_HASH_ENTRY(tp->id)); } } dcc_free(old_hash); } } id_probes = 0; id_searches = 0; } /* add an ID_TBL entry that is known to be absent */ ID_TBL * add_id_tbl(DCC_CLNT_ID id, ID_TBL ***binp, u_char debug) { ID_TBL *tp, **bin; if (binp) bin = *binp; else bin = &ID_HASH_ENTRY(id); tp = id_tbl_free; if (!tp) { id_make_blocks(debug); bin = &ID_HASH_ENTRY(id); if (binp) *binp = bin; tp = id_tbl_free; } id_tbl_free = tp->hfwd; memset(tp, 0, sizeof(*tp)); tp->id = id; id_tbl_link(tp, bin); return tp; } /* find an ID_TBL entry * on success, expand the hash table if searching has been slow */ ID_TBL * find_id_tbl(DCC_CLNT_ID id, u_char debug) { ID_TBL *tp, **bin; ++id_searches; bin = &ID_HASH_ENTRY(id); for (tp = *bin; tp != 0; tp = tp->hfwd) { ++id_probes; if (tp->id == id) { if ((id_probes*10)/id_searches > 11) { if (id_searches > 100) { id_tbl_expand_hash(debug); } else { /* move hot IDs to head of bins */ id_tbl_link(tp, bin); } } else if (id_searches > 10000) { /* prevent overflow */ id_probes /= 2; id_searches /= 2; } return tp; } } return 0; } /* enumerate ID_TBL entries */ ID_TBL * enum_ids(ID_TBL *tp) { ID_TBL **tpp; if (!tp) { /* start at the beginning if we have not already started */ tpp = &id_tbl_hash[0]; } else { /* return the next entry in the current chain if there is one */ if (tp->hfwd) return tp->hfwd; /* otherwise find the next chain */ tpp = &ID_HASH_ENTRY(tp->id)+1; } while (tpp < &id_tbl_hash[id_tbl_bins]) { tp = *tpp; if (tp) return tp; ++tpp; } return 0; } u_char /* 0=bad 1=ok 2=forever */ parse_dccd_delay(DCC_EMSG *emsg, u_int32_t *delay_usp, u_int *delay_inflatep, const char *val, const char *fnm, int lno) { DCC_FNM_LNO_BUF fnm_buf; u_int32_t delay_ms; u_long l; char *p1, *p2; *delay_usp = DCC_ANON_DELAY_US_BLACKLIST; *delay_inflatep = DCC_ANON_INFLATE_OFF; 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, dcc_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, dcc_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, dcc_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 (!dcc_fnm2rel(&ids_path, ids_nm, 0)) { dcc_pemsg(EX_DATAERR, emsg, "\"%s\" is a bad ids file name", ids_nm); return 0; } return 1; } static u_char /* 1=id is an acceptable ID */ client_server(DCC_EMSG *emsg, u_char srvrs_only, /* 1=no client-IDs */ DCC_CLNT_ID id, /* target number */ const char *str, int lno) { DCC_FNM_LNO_BUF fnm_buf; if (id >= DCC_CLNT_ID_MIN && id <= DCC_CLNT_ID_MAX) { /* we have a client-ID */ if (!srvrs_only) return 1; dcc_pemsg(EX_DATAERR, emsg, "\"%s\" only for server-IDs%s", str, dcc_fnm_lno(&fnm_buf, ids_path.c, lno)); return 0; } /* we have a server-ID */ if (srvrs_only) return 1; dcc_pemsg(EX_DATAERR, emsg, "%s only for client-IDs%s", str, dcc_fnm_lno(&fnm_buf, ids_path.c, lno)); return 0; } /* (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, u_char debug) { DCC_PATH tmp; DCC_FNM_LNO_BUF fnm_buf; ID_TBL t, *tp, **tpp, **bin; FILE *f; int lno, passwords; u_char found_it; int result; char buf[sizeof(ID_TBL)*2+1]; const char *bufp, *passbuf; char id_buf[30]; struct stat sb; char *p, *p1; if (!id_tbl_blocks) { id_make_blocks(debug); id_tbl_expand_hash(debug); } if (tgt_tbl) *tgt_tbl = 0; if (!set_ids_path(emsg, 0)) return -1; if (!force) { if (!dcc_ck_private(emsg, &sb, ids_path.c, -1)) { ids_mtime = 0; return -1; } if (ids_mtime == sb.st_mtime) return 2; } f = fopen(ids_path.c, "r"); if (!f) { dcc_pemsg(EX_NOINPUT, emsg, "fopen(%s): %s", dcc_fnm2abs_msg(&tmp, ids_path.c), 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.c, fileno(f))) { fclose(f); ids_mtime = 0; return -1; } /* empty the table by making all entries into placeholders */ memset(&t, 0, sizeof(t)); for (tpp = &id_tbl_hash[0]; tpp < &id_tbl_hash[id_tbl_bins]; ++tpp) { for (tp = *tpp; tp; tp = tp->hfwd) { t.hfwd = tp->hfwd; t.hbak = tp->hbak; t.id = tp->id; *tp = t; } } 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) && result > 0) { dcc_pemsg(EX_IOERR, emsg, "fgets(%s): %s", dcc_fnm2abs_msg(&tmp, ids_path.c), ERROR_STR()); result = 0; } 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[,trace][,rpt-ok][,no-reps] * [,delay=ms[*inflate]] passwd1 passwd2 * ID,delay=forever * * Placeholders have the from * ID * or for servers on masters * ID,simple|ignore|rogue|commerical * * 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. */ passbuf = dcc_parse_word(emsg, id_buf, sizeof(id_buf), bufp, "ID", ids_path.c, lno); if (!passbuf) { result = 0; /* line too long */ continue; } /* stop parsing the line if the server-ID is bad */ p = strchr(id_buf, ','); if (p) *p++ = '\0'; t.id = dcc_get_id(emsg, id_buf, ids_path.c, lno); if (t.id == DCC_ID_INVALID) { result = 0; continue; } if (t.id == DCC_ID_ANON) { dcc_pemsg(EX_DATAERR, emsg, "invalid ID \"%s\"%s", id_buf, dcc_fnm_lno(&fnm_buf, ids_path.c, lno)); result = 0; continue; } /* parse the options */ for (; p; p = p1) { p1 = strchr(p, ','); if (p1) *p1++ = '\0'; if (!strcasecmp(p, "trace")) { if (!client_server(emsg, 0, t.id, p, lno)) { result = 0; } else { t.flags |= ID_FLG_TRACE; } continue; } if (!strcasecmp(p, "rpt-ok") || !strcasecmp(p, "rpt_ok")) { if (!client_server(emsg, 0, t.id, p, lno)) { result = 0; } else { t.flags |= ID_FLG_RPT_OK; } continue; } if (!strcasecmp(p, "no-reps") || !strcasecmp(p, "no_reps")) { if (!client_server(emsg, 0, t.id, p, lno)) { result = 0; } else { t.flags |= ID_FLG_NO_REPS; } continue; } if (!CLITCMP(p, "delay=")) { if (!client_server(emsg, 0, t.id, p, lno)) { result = 0; } else if (!parse_dccd_delay(emsg, &t.delay_us, &t.delay_inflate, p+LITZ("delay="), ids_path.c, lno)) { result = 0; } else { t.flags |= ID_FLG_DELAY_SET; } continue; } if (!strcasecmp(p, DCC_XHDR_ID_SIMPLE)) { if (!client_server(emsg, 1, t.id, p, lno)) { result = 0; } else { } continue; } if (!strcasecmp(p, DCC_XHDR_ID_IGNORE)) { if (!client_server(emsg, 1, t.id, p, lno)) { result = 0; } continue; } if (!strcasecmp(p, DCC_XHDR_ID_ROGUE)) { if (!client_server(emsg, 1, t.id, p, lno)) { result = 0; } continue; } dcc_pemsg(EX_DATAERR, emsg, "invalid option \"%s\"%s", p, dcc_fnm_lno(&fnm_buf, ids_path.c, lno)); result = 0; } if (*passbuf != '\0') { passbuf = parse_passwd(emsg, t.cur_passwd, passbuf, "current password", ids_path.c, lno); if (!passbuf) { result = 0; /* line too long */ continue; } passbuf = parse_passwd(emsg, t.next_passwd, passbuf, "next password", ids_path.c, lno); if (!passbuf) { result = 0; /* line too long */ continue; } if (*passbuf != '\0') { dcc_pemsg(EX_DATAERR, emsg, "invalid next password%s", dcc_fnm_lno(&fnm_buf, ids_path.c, lno)); result = 0; continue; } } /* put the entry into the hash table if not already present */ bin = &ID_HASH_ENTRY(t.id); tp = *bin; for (;;) { if (tp == 0) { tp = add_id_tbl(t.id, &bin, debug); break; } else if (tp->id == t.id) { break; } tp = tp->hfwd; } /* 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. * Merge from the old entry to the new entry. */ if (!(t.flags & ID_FLG_DELAY_SET)) { t.delay_us = tp->delay_us; t.delay_inflate = tp->delay_inflate; if (tp->flags & ID_FLG_DELAY_SET) t.flags |= ID_FLG_DELAY_SET; } else if ((tp->flags & ID_FLG_DELAY_SET) && (t.delay_us != tp->delay_us || t.delay_inflate != tp->delay_inflate)) { dcc_pemsg(EX_DATAERR, emsg, "conflicting delays for ID %d%s", t.id, dcc_fnm_lno(&fnm_buf, ids_path.c, lno)); result = 0; continue; } if (t.cur_passwd[0] == '\0') { memcpy(t.cur_passwd, tp->cur_passwd, sizeof(t.cur_passwd)); memcpy(t.next_passwd, tp->next_passwd, sizeof(t.next_passwd)); } else { if (tp->cur_passwd[0] != '\0' && (memcmp(t.cur_passwd, tp->cur_passwd, sizeof(t.cur_passwd)) || memcmp(t.next_passwd, tp->next_passwd, sizeof(t.next_passwd)))) { dcc_pemsg(EX_DATAERR, emsg, "conflicting passwords for ID %d%s", t.id, dcc_fnm_lno(&fnm_buf, ids_path.c, lno)); result = 0; continue; } ++passwords; /* remember the target */ if (t.id == tgt_id) { found_it = 1; if (tgt_tbl) *tgt_tbl = tp; } } t.flags |= (tp->flags & (ID_FLG_TRACE | ID_FLG_RPT_OK)); t.hfwd = tp->hfwd; t.hbak = tp->hbak; *tp = t; } fclose(f); if (!passwords) { if (result > 0) dcc_pemsg(EX_DATAERR, emsg, "%s contains no passwords", dcc_fnm2abs_msg(&tmp, ids_path.c)); result = -1; } if (!found_it) { if (result > 0) dcc_pemsg(EX_DATAERR, emsg, "%s does not contain the password for ID %d", dcc_fnm2abs_msg(&tmp, ids_path.c), tgt_id); result = -1; } id_probes = 0; id_searches = 0; return result; }