/* Distributed Checksum Clearinghouse * * check checksums in the local whitelist * * --C-LICENSE-- * $Revision: 1.148 $ */ #include "dcc_ck.h" static const WHITE_MAGIC white_magic = WHITE_MAGIC_B_STR WHITE_MAGIC_V_STR; #define EMPTY_WHITE_SIZE (sizeof(WHITE_TBL) - sizeof(WHITE_ENTRY)) #define MAX_WHITE_ENTRIES (WHITE_TBL_BINS*10) #define ENTRIES2SIZE0(_l) (sizeof(WHITE_TBL) - sizeof(WHITE_ENTRY) \ + ((_l) * sizeof(WHITE_ENTRY))) #ifdef DCC_WIN32 /* Make the hash table files maximum size on WIN32. * You cannot change the size of a WIN32 mapping object without * getting all processes using it to release it so that it can be * recreated. This may cause problems if the size of hash table * header changes. * Since the file does not change size, there is no need to remap it */ #define ENTRIES2SIZE(_l) ENTRIES2SIZE0(MAX_WHITE_ENTRIES) #else #define ENTRIES2SIZE(_l) ENTRIES2SIZE0(_l) #endif WF cmn_wf, cmn_tmp_wf; void wf_init(WF *wf, u_int wf_flags) /* WF_* */ { memset(wf, 0, sizeof(*wf)); wf->ht_fd = -1; wf->wf_flags = wf_flags & ~WF_NOFILE; } /* this is needed only on systems without coherent mmap()/read()/write() */ static void sync_white(WF *wf) { if (wf->wtbl && 0 > MSYNC(wf->wtbl, wf->wtbl_size, MS_SYNC)) dcc_error_msg("msync(%s): %s", wf->ht_nm.c, ERROR_STR()); } #define STILL_BROKE(wf,now) ((wf)->broken != 0 \ && !DCC_IS_TIME(now, (wf)->broken, \ WHITE_BROKEN_DELAY)) static void is_broken(WF *wf) { wf->broken = time(0) + WHITE_BROKEN_DELAY; /* This is racy, but the worst that can happen is that * another process races to set the retry timer */ if (!(wf->wf_flags & WF_RO) && wf->wtbl) { wf->wtbl->hdr.broken = wf->broken; sync_white(wf); } } static void unmap_white_ht(WF *wf) { if (!wf->wtbl) return; sync_white(wf); #ifdef DCC_WIN32 dcc_win32_unmap(&wf->ht_map, wf->wtbl, wf->ht_nm); #else if (0 > munmap((void *)wf->wtbl, wf->wtbl_size)) dcc_error_msg("munmap(%s,%d): %s", wf->ht_nm.c, wf->wtbl_size, ERROR_STR()); #endif wf->wtbl = 0; } static u_char /* 1=done, 0=failed */ map_white_ht(DCC_EMSG *emsg, WF *wf, WHITE_INX entries) { size_t size; #ifndef DCC_WIN32 void *p; #endif unmap_white_ht(wf); if (entries > MAX_WHITE_ENTRIES) { dcc_pemsg(EX_IOERR, emsg, "%s should not contain %d entries", wf->ht_nm.c, entries); return 0; } size = ENTRIES2SIZE(entries); #ifdef DCC_WIN32 if (!wf->wtbl) { wf->wtbl = dcc_win32_map(emsg, &wf->ht_map, wf->ht_nm.c, wf->ht_fd, size); if (!wf->wtbl) return 0; } #else p = mmap(0, size, (wf->wf_flags & WF_RO) ? PROT_READ : (PROT_READ|PROT_WRITE), MAP_SHARED, wf->ht_fd, 0); if (p == MAP_FAILED) { dcc_pemsg(EX_IOERR, emsg, "mmap(%s,%d): %s", wf->ht_nm.c, (int)size, ERROR_STR()); return 0; } wf->wtbl = p; #endif wf->wtbl_size = size; wf->wtbl_entries = entries; wf->wtbl_fgs = wf->wtbl->hdr.fgs; wf->wtbl_sws = wf->wtbl->hdr.sws; return 1; } static u_char close_white_ht(DCC_EMSG *emsg, WF *wf) { u_char result = 1; if (wf->ht_fd < 0) return result; wf->closed = 1; unmap_white_ht(wf); #ifdef DCC_WIN32 /* unlock the file before closing it to keep Win95 happy */ if (!dcc_unlock_fd(emsg, wf->ht_fd, DCC_LOCK_ALL_FILE, "whitelist ", wf->ht_nm.c)) result = 0; #endif if (0 > close(wf->ht_fd)) { dcc_pemsg(EX_IOERR, emsg, "close(%s): %s", wf->ht_nm.c, ERROR_STR()); result = 0; } wf->ht_fd = -1; memset(&wf->ht_sb, 0, sizeof(wf->ht_sb)); #ifndef DCC_WIN32 wf->ht_sb.st_dev = -1; wf->ht_sb.st_ino = -1; #endif return result; } int /* 0=ok -1=failed */ unlink_white_ht(DCC_EMSG *emsg, WF *wf, time_t now) /* 0=our own new file */ { int result; /* mark it bad if it is a brand new hash table */ if (!now && wf->wtbl && !(wf->wf_flags & WF_RO)) wf->wtbl->hdr.ascii_mtime = 0; #ifdef DCC_WIN32 /* racy but you cannot unlink an open file on WIN32 */ close_white_ht(0, wf); #endif result = -1; if (!(wf->wf_flags & WF_RO)) { if (0 <= unlink(wf->ht_nm.c) || errno == ENOENT) { result = 0; #ifndef DCC_WIN32 } else if ((errno == EACCES || errno == EPERM) && dcc_get_priv_home(wf->ht_nm.c)) { if (0 <= unlink(wf->ht_nm.c)) result = 0; dcc_rel_priv(); #endif } if (result < 0) dcc_pemsg(EX_IOERR, emsg,"unlink(%s): %s", wf->ht_nm.c, ERROR_STR()); } /* If we failed to unlink the old hash table, * remember in core that things are broken but do not touch the file * in case it is a link to /etc/passwd or something else dangerous */ if (result < 0 && now) wf->broken = now + WHITE_BROKEN_DELAY; #ifndef DCC_WIN32 close_white_ht(0, wf); #endif return result; } u_char new_ht_nm(DCC_EMSG *emsg, WF *wf, u_char new) { if (wf->ascii_nm_len >= ISZ(wf->ht_nm) - LITZ(WHITE_SUFFIX)) { dcc_pemsg(EX_NOINPUT, emsg, "bad whitelist file name \"%s\"", wf->ascii_nm.c); is_broken(wf); return 0; } memcpy(wf->ht_nm.c, wf->ascii_nm.c, wf->ascii_nm_len); strcpy(&wf->ht_nm.c[wf->ascii_nm_len], new ? WHITE_NEW_SUFFIX : WHITE_SUFFIX); return 1; } u_char dcc_new_white_nm(DCC_EMSG *emsg, WF *wf, const char *new_white_nm) { DCC_PATH new_path; const char *new; u_int len; int i; if (!strcmp(new_white_nm, wf->ascii_nm.c)) return 1; close_white_ht(0, wf); wf_init(wf, wf->wf_flags); if (!dcc_fnm2rel(&new_path, new_white_nm, 0)) { dcc_pemsg(EX_USAGE, emsg, "bad whitelist name \"%s\"", new_white_nm); return 0; } len = strlen(new_path.c); i = len - LITZ(WHITE_SUFFIX); if (i > 0 && (!strcmp(&new_path.c[i], WHITE_SUFFIX) || !strcmp(&new_path.c[i], WHITE_NEW_SUFFIX))) { len = i; new_path.c[len] = '\0'; } if (len > ISZ(wf->ht_nm) - ISZ(WHITE_SUFFIX)) { dcc_pemsg(EX_USAGE, emsg, "long whitelist name \"%s\"", new_white_nm); return 0; } new = dcc_path2fnm(new_path.c); len = strlen(new); memcpy(wf->ascii_nm.c, new, len+1); wf->ascii_nm_len = len; return 1; } /* open and shared-lock a hash table file */ static int /* -1=fatal 0=rebuild, 1=ok */ open_white_ht(DCC_EMSG *emsg, WF *wf) { int size; WHITE_INX entries; close_white_ht(0, wf); if (!new_ht_nm(emsg, wf, 0)) return -1; wf->ht_fd = dcc_lock_open(emsg, wf->ht_nm.c, (wf->wf_flags & WF_RO) ? O_RDONLY : O_RDWR, DCC_LOCK_OPEN_SHARE, DCC_LOCK_ALL_FILE, 0); if (wf->ht_fd < 0) { /* We failed to open the file. * If this is a `wlist` command and neither -P nor -Q * were specified, try to open the file read-only. */ if ((wf->wf_flags & WF_WLIST) && !(wf->wf_flags & (WF_WLIST_RO | WF_WLIST_RW))) { if (emsg) emsg->c[0] = '\0'; wf->wf_flags |= WF_RO; wf->ht_fd = dcc_lock_open(emsg, wf->ht_nm.c, O_RDONLY, DCC_LOCK_OPEN_SHARE, DCC_LOCK_ALL_FILE, 0); } if (wf->ht_fd < 0) return 0; } if (fstat(wf->ht_fd, &wf->ht_sb) < 0) { dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s", wf->ht_nm.c, ERROR_STR()); close_white_ht(0, wf); is_broken(wf); return -1; } size = wf->ht_sb.st_size - EMPTY_WHITE_SIZE; if (size < 0) { dcc_pemsg(EX_NOINPUT, emsg, "%s is too small to be a DCC whitelist hash table", wf->ht_nm.c); return unlink_white_ht(0, wf, time(0)); } else { entries = size / sizeof(WHITE_ENTRY); if (!map_white_ht(emsg, wf, entries)) return unlink_white_ht(0, wf, time(0)); } if (memcmp(&wf->wtbl->magic, &white_magic, sizeof(white_magic))) { /* rebuild old format files */ if (!memcmp(&wf->wtbl->magic, WHITE_MAGIC_B_STR, LITZ(WHITE_MAGIC_B_STR))) { if (dcc_clnt_debug) dcc_trace_msg("%s is obsolete %s", wf->ht_nm.c, wf->wtbl->magic); } else { dcc_pemsg(EX_NOINPUT, emsg, "%s is not a DCC whitelist hash file", wf->ht_nm.c); } return unlink_white_ht(0, wf, time(0)); } if ((size % sizeof(WHITE_ENTRY)) != 0 || entries > MAX_WHITE_ENTRIES || entries < wf->wtbl->hdr.entries) { dcc_pemsg(EX_NOINPUT, emsg, "invalid size of whitelist %s="OFF_DPAT, wf->ht_nm.c, wf->ht_sb.st_size); return unlink_white_ht(0, wf, time(0)); } if (wf->wtbl->hdr.ascii_mtime == 0) { close_white_ht(0, wf); return 0; /* broken hash table */ } /* we know the hash table is usable * but we might want to rebuild it */ wf->need_reopen = 0; wf->wtbl_fgs = wf->wtbl->hdr.fgs; wf->wtbl_sws = wf->wtbl->hdr.sws; /* wlist and dccproc work on both per-user and global whitelists, * so do not change the nature of the file if it is already known */ if (wf->wf_flags & WF_EITHER) { if (wf->wtbl_fgs & WHITE_FG_PER_USER) wf->wf_flags |= WF_PER_USER; else wf->wf_flags &= ~WF_PER_USER; } return 1; } static void create_white_ht_sub(DCC_EMSG *emsg, WF *new_wf, const WF *wf, u_char *busyp) { /* do not use O_EXCL because we want to wait for any other * process to finish * * wait if and only if we do not have a usable file open */ new_wf->ht_fd = dcc_lock_open(emsg, new_wf->ht_nm.c, O_RDWR | O_CREAT, wf->ht_fd >= 0 ? DCC_LOCK_OPEN_NOWAIT : 0, DCC_LOCK_ALL_FILE, busyp); if (new_wf->ht_fd < 0) return; /* a new hash table must be empty */ if (0 > fstat(new_wf->ht_fd, &new_wf->ht_sb)) { dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s", new_wf->ht_nm.c, ERROR_STR()); close_white_ht(emsg, new_wf); return; } if (new_wf->ht_sb.st_size != 0) { dcc_pemsg(EX_IOERR, emsg, "%s has non-zero size %d", new_wf->ht_nm.c, (int)new_wf->ht_sb.st_size); close_white_ht(emsg, new_wf); return; } } /* create and write-lock a new hash table file * wait for lock if we don't have existing file open */ static int /* 1=done, 0=file busy, -1=fatal */ create_white_ht(DCC_EMSG *emsg, WF *tmp_wf, /* build with this */ const WF *wf) /* from this */ { u_char busy = 0; tmp_wf->ascii_nm_len = wf->ascii_nm_len; memcpy(tmp_wf->ascii_nm.c, wf->ascii_nm.c, wf->ascii_nm_len+1); if (!new_ht_nm(emsg, tmp_wf, 1)) { tmp_wf->ht_nm.c[0] = '\0'; return -1; } if (tmp_wf->wf_flags & WF_RO) { dcc_pemsg(EX_IOERR, emsg, "read only access; cannot create or update %s", tmp_wf->ht_nm.c); tmp_wf->ht_nm.c[0] = '\0'; return -1; } #ifndef DCC_WIN32 /* We want to create a private hash table if the ASCII file * is private, but a hash table owned by the DCC user if the * ASCII file is public */ if (0 > access(tmp_wf->ascii_nm.c, R_OK | W_OK) && dcc_get_priv_home(tmp_wf->ht_nm.c)) { /* first try to open a public hash table */ create_white_ht_sub(emsg, tmp_wf, wf, &busy); if (tmp_wf->ht_fd < 0 && !busy) { if (emsg && dcc_clnt_debug > 2) dcc_error_msg("%s", emsg->c); unlink(tmp_wf->ht_nm.c); create_white_ht_sub(emsg, tmp_wf, wf, &busy); } dcc_rel_priv(); } #endif if (tmp_wf->ht_fd < 0 && !busy) { /* try to open or create a private hash table */ create_white_ht_sub(emsg, tmp_wf, wf, &busy); if (tmp_wf->ht_fd < 0 && !busy) { if (emsg && dcc_clnt_debug > 2) dcc_error_msg("%s", emsg->c); unlink(tmp_wf->ht_nm.c); create_white_ht_sub(emsg, tmp_wf, wf, &busy); } } #ifndef DCC_WIN32 /* try one last time with privileges in case the ASCII file has * mode 666 but the directory does not */ if (tmp_wf->ht_fd < 0 && !busy) { if (dcc_get_priv_home(tmp_wf->ht_nm.c)) { if (emsg && dcc_clnt_debug > 2) dcc_error_msg("%s", emsg->c); unlink(tmp_wf->ht_nm.c); create_white_ht_sub(emsg, tmp_wf, wf, &busy); dcc_rel_priv(); } } #endif if (tmp_wf->ht_fd < 0) { tmp_wf->ht_nm.c[0] = '\0'; if (busy) return 0; return -1; } return 1; } #define FIND_WHITE_BROKEN ((WHITE_ENTRY *)-1) static WHITE_ENTRY * find_white(DCC_EMSG *emsg, WF *wf, DCC_CK_TYPES type, const DCC_SUM *sum, WHITE_INX *binp) { u_long accum; WHITE_INX bin, inx; WHITE_ENTRY *e; int loop_cnt, i; if (!wf->wtbl || wf->wtbl->hdr.ascii_mtime == 0) return FIND_WHITE_BROKEN; accum = type; for (i = sizeof(DCC_SUM)-1; i >= 0; --i) accum = (accum >> 28) + (accum << 4) + sum->b[i]; bin = accum % DIM(wf->wtbl->bins); if (binp) *binp = bin; inx = wf->wtbl->bins[bin]; for (loop_cnt = wf->wtbl->hdr.entries+1; loop_cnt >= 0; --loop_cnt) { if (!inx) return 0; --inx; /* if necessary, expand the mapped window into the file */ if (inx >= wf->wtbl_entries) { if (inx >= wf->wtbl->hdr.entries) { dcc_pemsg(EX_DATAERR, emsg, "bogus index %u in %s", inx, wf->ht_nm.c); if (!(wf->wf_flags & WF_RO)) wf->wtbl->hdr.ascii_mtime = 0; sync_white(wf); return FIND_WHITE_BROKEN; } if (!map_white_ht(emsg, wf, wf->wtbl->hdr.entries)) return 0; } e = &wf->wtbl->tbl[inx]; if (e->type == type && !memcmp(e->sum.b, sum->b, sizeof(e->sum))) return e; inx = e->fwd; } dcc_pemsg(EX_DATAERR, emsg, "chain length %d in %s" " starting at %d near %d for %s %s", wf->wtbl->hdr.entries+1, wf->ht_nm.c, (WHITE_INX)(accum % DIM(wf->wtbl->bins)), inx, type2str_err(type, 0, 0, 0), ck2str_err(type, sum, 0)); if (!(wf->wf_flags & WF_RO)) wf->wtbl->hdr.ascii_mtime = 0; sync_white(wf); return FIND_WHITE_BROKEN; } static int /* 1=quit stat_white_nms() */ stat_1white_nm(int *resultp, /* set=1 if (supposedly) no change */ WF *wf, const char *nm, time_t ascii_mtime) { struct stat sb; time_t now; if (stat(nm, &sb) < 0) { if (errno != ENOENT || !wf->wtbl || (wf->wf_flags & WF_RO)) { dcc_trace_msg("stat(%s): %s", nm, ERROR_STR()); is_broken(wf); *resultp = 0; return 1; } /* only complain if an ASCII file disappears temporarily */ if (wf->broken == 0) { dcc_trace_msg("%s disappeared: %s", nm, ERROR_STR()); is_broken(wf); *resultp = 1; return 1; } now = time(0); if (STILL_BROKE(wf, now)) { if (dcc_clnt_debug > 1) dcc_trace_msg("ignoring stat(%s): %s", nm, ERROR_STR()); *resultp = 1; } else { if (dcc_clnt_debug > 1) dcc_trace_msg("pay attention to stat(%s): %s", nm, ERROR_STR()); *resultp = 0; } return 1; } if (sb.st_mtime != ascii_mtime) { *resultp = 0; return 1; } return 0; } /* see if the ASCII files have changed */ static int /* 1=same 0=changed or sick -1=broken */ stat_white_nms(DCC_EMSG *emsg, WF *wf) { struct stat ht_sb; time_t now; int i, result; if (!wf->wtbl) return -1; now = time(0); wf->next_stat_time = now + WHITE_STAT_DELAY; /* Notice if the hash file has been unlinked */ if (stat(wf->ht_nm.c, &ht_sb) < 0) { if (emsg && dcc_clnt_debug) dcc_error_msg("stat(%s): %s", wf->ht_nm.c, ERROR_STR()); return -1; } #ifdef DCC_WIN32 /* open files cannot be unlinked in WIN32, which lets us not * worry about whether WIN32 files have device and i-numbers */ #else if (wf->ht_sb.st_dev != ht_sb.st_dev || wf->ht_sb.st_ino != ht_sb.st_ino) { if (emsg && dcc_clnt_debug > 2) dcc_error_msg("%s disappeared", wf->ht_nm.c); return -1; } #endif /* DCC_WIN32 */ wf->ht_sb = ht_sb; /* delays on re-parsing and complaining in the file override */ if (wf->reparse < wf->wtbl->hdr.reparse) wf->reparse = wf->wtbl->hdr.reparse; if (wf->broken < wf->wtbl->hdr.broken) wf->broken = wf->wtbl->hdr.broken; /* seriously broken hash tables are unusable */ if (wf->wtbl->hdr.ascii_mtime == 0) return -1; /* pretend things are fine if they are recently badly broken */ if (STILL_BROKE(wf, now)) return 1; /* if the main ASCII file has disappeared, * leave the hash file open and just complain, * but only for a while */ result = 1; if (stat_1white_nm(&result, wf, wf->ascii_nm.c, wf->wtbl->hdr.ascii_mtime)) return result; /* see if any of the included ASCII files are new */ for (i = 0; i < DIM(wf->wtbl->hdr.white_incs); ++i) { if (wf->wtbl->hdr.white_incs[i].nm.c[0] == '\0') break; /* stop at the first missing or changed included file */ if (stat_1white_nm(&result, wf, wf->wtbl->hdr.white_incs[i].nm.c, wf->wtbl->hdr.white_incs[i].mtime)) return result; } /* force periodic reparsing of syntax errors to nag in system log */ if (wf->reparse != 0 && DCC_IS_TIME(now, wf->reparse, WHITE_REPARSE_DELAY)) return 0; if ((wf->wtbl_fgs & WHITE_FG_PER_USER) && !(wf->wf_flags & WF_PER_USER)) { dcc_error_msg("%s is a per-user whitelist" " used as a global whitelist", wf->ht_nm.c); return 0; } if (!(wf->wtbl_fgs & WHITE_FG_PER_USER) && (wf->wf_flags & WF_PER_USER)) { dcc_error_msg("%s is a global whitelist" " used as a per-user whitelist", wf->ht_nm.c); return 0; } /* Checksums of SMTP client IP addresses are compared against the * checksums of the IP addresses of the host names in the ASCII file. * Occassionaly check for changes in DNS A RR's for entries in * the ASCII file, but only if there are host names or IP * addresses in the file */ if ((wf->wtbl->hdr.fgs & WHITE_FG_HOSTNAMES) && DCC_IS_TIME(now, wf->ht_sb.st_mtime+DCC_WHITECLNT_RESOLVE, DCC_WHITECLNT_RESOLVE)) { if (dcc_clnt_debug > 2) dcc_trace_msg("time to rebuild %s", wf->ht_nm.c); return 0; } return 1; } static u_char write_white(DCC_EMSG *emsg, WF *wf, const void *buf, int buf_len, off_t pos) { int i; if (wf->wtbl) { #ifdef DCC_WIN32 /* Windows disclaims coherence between ordinary writes * and memory mapped writing. The hash tables are * fixed size on Windows because of problems with WIN32 * mapping objects, so we do not need to worry about * extending the hash table file. */ memcpy((char *)wf->wtbl+pos, buf, buf_len); return 1; #else /* some UNIX systems have coherence trouble without msync() */ if (0 > MSYNC(wf->wtbl, wf->wtbl_size, MS_SYNC)) { dcc_pemsg(EX_IOERR, emsg, "msync(%s): %s", wf->ht_nm.c, ERROR_STR()); return 0; } #endif } i = lseek(wf->ht_fd, pos, SEEK_SET); if (i < 0) { dcc_pemsg(EX_IOERR, emsg, "lseek(%s,"OFF_DPAT"): %s", wf->ht_nm.c, pos, ERROR_STR()); return 0; } i = write(wf->ht_fd, buf, buf_len); if (i != buf_len) { if (i < 0) dcc_pemsg(EX_IOERR, emsg, "write(%s,%d): %s", wf->ht_nm.c, buf_len, ERROR_STR()); else dcc_pemsg(EX_IOERR, emsg, "write(%s,%d): %d", wf->ht_nm.c, buf_len, i); return 0; } return 1; } /* add a checksum to a whiteclnt hash table */ static int /* 1=ok, 0=bad entry, -1=fatal */ add_white(DCC_EMSG *emsg, WF *wf, DCC_CK_TYPES type, DCC_SUM *sum, DCC_TGTS tgts) { WHITE_ENTRY *e, new; WHITE_INX bin; DCC_FNM_LNO_BUF fnm_buf; off_t end; /* find the hash chain for the new entry */ e = find_white(emsg, wf, type, sum, &bin); if (e == FIND_WHITE_BROKEN) return -1; /* ignore duplicates on this, the first pass */ if (e) return 1; memset(&new, 0, sizeof(new)); new.type = type; new.sum = *sum; new.tgts = tgts; new.fwd = wf->wtbl->bins[bin]; new.lno = wf->lno; new.fno = wf->fno; /* Use a new entry at the end of the file * It will be beyond the memory mapped window into the file */ if (wf->wtbl->hdr.entries >= MAX_WHITE_ENTRIES) { dcc_pemsg(EX_DATAERR, emsg, "more than maximum %d entries%s", wf->wtbl->hdr.entries, wf_fnm_lno(&fnm_buf, wf)); return 0; } end = ENTRIES2SIZE(wf->wtbl->hdr.entries); wf->wtbl->bins[bin] = ++wf->wtbl->hdr.entries; return write_white(emsg, wf, &new, sizeof(new), end) ? 1 : -1; } /* add a block of IP address checksums to a whiteclnt hash table */ #define MIN_IP_RANGE_SIZE (1<<8) /* fix dcc.man if this changes */ static int /* 1=ok, 0=bad entry, -1=fatal */ add_white_ip_range(DCC_EMSG *emsg, WF *wf, const DCC_IP_RANGE *range, DCC_TGTS tgts) { WHITE_IP_RANGE *r; DCC_FNM_LNO_BUF fnm_buf; u_int n, range_len; struct in6_addr addr; DCC_SUM sum; int result, j; /* use individual entries for smaller than MIN_IP_RANGE_SIZE blocks */ range_len = len_ip_range(range); if (range_len < MIN_IP_RANGE_SIZE) { addr = range->lo; result = 1; for (n = 0; n < range_len; ++n) { ipv6tock(&sum, &addr); j = add_white(emsg, wf, DCC_CK_IP, &sum, tgts); if (j <= 0) { if (j < 0) return j; result = j; } inc_ip6(&addr); } return result; } n = wf->wtbl->hdr.ranges.len; for (r = wf->wtbl->hdr.ranges.rs; n > 0; ++r, --n) { /* ignore collisions on this, the first pass */ if (!memcmp(&r->range, range, sizeof(r->range))) return 1; } if (wf->wtbl->hdr.ranges.len >= DIM(wf->wtbl->hdr.ranges.rs)) { dcc_pemsg(EX_DATAERR, emsg, "too many IP address blocks%s", wf_fnm_lno(&fnm_buf, wf)); return 0; } r->range = *range; r->len = range_len; r->tgts = tgts; r->lno = wf->lno; r->fno = wf->fno; ++wf->wtbl->hdr.ranges.len; return 1; } static void dup_msg(DCC_EMSG *emsg, WF *wf, int e_fno, int e_lno, DCC_TGTS e_tgts, DCC_TGTS tgts) { char tgts_buf[30], e_tgts_buf[30]; const char *fname1, *fname2; fname1 = wf_fnm(wf, wf->fno); fname2 = wf_fnm(wf, e_fno); dcc_pemsg(EX_DATAERR, emsg, "\"%s\" in line %d%s%s conflicts with \"%s\" in line" " %d of %s", tgts2str(tgts_buf, sizeof(tgts_buf), tgts, 0), wf->lno, fname1 != fname2 ? " of " : "", fname1 != fname2 ? fname1 : "", tgts2str(e_tgts_buf, sizeof(e_tgts_buf), e_tgts, 0), e_lno, fname2); } static DCC_TGTS combine_white_tgts(DCC_TGTS new, DCC_TGTS old) { if (new < DCC_TGTS_TOO_MANY && old == DCC_TGTS_TOO_MANY) return new; if (new == DCC_TGTS_OK || old == DCC_TGTS_OK) return DCC_TGTS_OK; if (new == DCC_TGTS_OK2 || old == DCC_TGTS_OK2) return DCC_TGTS_OK2; if (new == DCC_TGTS_OK_MX || old == DCC_TGTS_OK_MX) return DCC_TGTS_OK_MX; if (new == DCC_TGTS_OK_MXDCC || old == DCC_TGTS_OK_MXDCC) return DCC_TGTS_OK_MXDCC; if (new == DCC_TGTS_TOO_MANY || old == DCC_TGTS_TOO_MANY) return DCC_TGTS_TOO_MANY; if (new == DCC_TGTS_SUBMIT_CLIENT || old == DCC_TGTS_SUBMIT_CLIENT) return DCC_TGTS_SUBMIT_CLIENT; return new; } static int /* 1=ok, 0=bad entry, -1=fatal */ ck_dup_white(DCC_EMSG *emsg, WF *wf, DCC_CK_TYPES type, DCC_SUM *sum, DCC_TGTS tgts, u_char complain) { WHITE_ENTRY *e; DCC_FNM_LNO_BUF buf; const char *from; /* find the hash table entry */ e = find_white(emsg, wf, type, sum, 0); if (e == FIND_WHITE_BROKEN) return -1; if (!e) { /* We failed to find the hash table entry. Either the * hash file is corrupt, the ASCII file is changing beneath * our feet, or a host name that failed to resolve the first * time we parsed the ASCII file is now resolving during * this second parsing. */ from = wf_fnm_lno(&buf, wf); if (type == DCC_CK_IP) { if (dcc_clnt_debug > 2) dcc_trace_msg("%s entry (dis)appeared in %s%s", type2str_err(type, 0, 0, 0), wf->ht_nm.c, *from == '\0' ? "in ?" : from); return 0; } dcc_pemsg(EX_DATAERR, emsg, "%s entry (dis)appeared in %s%s", type2str_err(type, 0, 0, 0), wf->ht_nm.c, *from == '\0' ? "in ?" : from); return -1; } /* ignore perfect duplicates */ if (e->tgts == tgts) return 1; if (complain) dup_msg(emsg, wf, e->fno, e->lno, e->tgts, tgts); if (e->tgts != combine_white_tgts(tgts, e->tgts)) { e->tgts = tgts; e->lno = wf->lno; e->fno = wf->fno; } return 0; } static int /* 1=ok, 0=bad entry, -1=fatal */ ck_dup_white_complain(DCC_EMSG *emsg, WF *wf, DCC_CK_TYPES type, DCC_SUM *sum, DCC_TGTS tgts) { return ck_dup_white(emsg, wf, type, sum, tgts, 1); } /* Without brute force checks that would take too long, it is impossible to * check that a new IP address block does not collide with individual entries * that are already in the hash table. Without expanding a big block into * IP addresses and looking for each in the hash table, how can you check * whether it covers an entry for an individual address? One way is to * parse the file twice and so check each individual IP address against * the blocks. */ static int /* 1=ok, 0=bad entry, -1=fatal */ ck_dup_white_ip_range(DCC_EMSG *emsg DCC_UNUSED, WF *wf, const DCC_IP_RANGE *range, DCC_TGTS tgts) { WHITE_IP_RANGE *r; u_int range_len; struct in6_addr addr; DCC_SUM sum; int result, i, j; result = 1; /* Check for collisions between address blocks and other * address blocks or individual addresses. Individual addresses * are sent here as /128 blocks. */ i = wf->wtbl->hdr.ranges.len; for (r = wf->wtbl->hdr.ranges.rs; i > 0; ++r, --i) { if (!in_range(&range->lo, &r->range) && !in_range(&range->hi, &r->range)) continue; /* ignore simple duplicates */ if (r->tgts == tgts) continue; dup_msg(emsg, wf, r->fno, r->lno, r->tgts, tgts); result = 0; /* fix direct collisions */ if (!memcmp(&r->range, range, sizeof(r->range)) && r->tgts != combine_white_tgts(tgts, r->tgts)) { r->tgts = tgts; r->lno = wf->lno; r->fno = wf->fno; } } /* check and fix collisions among individual entries */ range_len = len_ip_range(range); if (range_len < MIN_IP_RANGE_SIZE) { addr = range->lo; while (range_len-- != 0) { ipv6tock(&sum, &addr); j = ck_dup_white(emsg, wf, DCC_CK_IP, &sum, tgts, result > 0); if (j < 0) return j; if (j == 0) result = j; inc_ip6(&addr); } } return result; } /* bail out on creating a whiteclnt hash table file */ static WHITE_RESULT make_white_hash_bail(WHITE_RESULT result, WF *wf, WF *tmp_wf) { if (tmp_wf->ht_nm.c[0] != '\0') { unlink_white_ht(0, tmp_wf, 0); tmp_wf->ht_nm.c[0] = '\0'; } /* we have a usable file open */ if (wf->wtbl) return WHITE_CONTINUE; return result; } /* (re)create the hash table file */ static WHITE_RESULT make_white_hash(DCC_EMSG *emsg, WF *wf, WF *tmp_wf) { static const u_char zero = 0; int ascii_fd; struct stat ascii_sb; DCC_PATH path; WHITE_RESULT result; DCC_CK_TYPES type; int part_result, i; if (dcc_clnt_debug > 2) dcc_trace_msg("start parsing %s", wf->ascii_nm.c); /* Do not wait to create a new, locked hash table file if we have * a usable file. Assume some other process is re-parsing * the ASCII file. * If we should wait to create a new file, then we don't have a * usable hash table and so there is no reason to unlock the * WF structure. */ #ifdef DCC_DEBUG_LOCKS if (tmp_wf == &cmn_tmp_wf) assert_cwf_locked(); #endif wf_init(tmp_wf, wf->wf_flags); if (0 > stat(wf->ascii_nm.c, &ascii_sb)) { result = ((errno == ENOENT || errno == ENOTDIR) ? WHITE_NOFILE : WHITE_COMPLAIN); dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s", wf->ascii_nm.c, ERROR_STR()); /* Delete the hash table if the ASCII file has disappeared. * stat_white_nms() delays forcing a re-build if the ASCII * file disappears only temporarily */ if (wf->ht_nm.c[0] != '\0') { close_white_ht(0, wf); i = unlink(wf->ht_nm.c); if (i < 0) { if (errno != ENOENT && errno != ENOTDIR) dcc_trace_msg("%s missing,:unlink(%s):" " %s", wf->ascii_nm.c, dcc_fnm2abs_msg(&path, wf->ht_nm.c), ERROR_STR()); } else if (dcc_clnt_debug > 1) { dcc_trace_msg("delete %s after %s missing", dcc_fnm2abs_msg(&path, wf->ht_nm.c), wf->ascii_nm.c); } } return make_white_hash_bail(result, wf, tmp_wf); } part_result = create_white_ht(emsg, tmp_wf, wf); if (part_result == 0) { /* The new hash table file is busy. * Do not complain if we have a usable open hash table file */ if (!dcc_clnt_debug && wf->wtbl) return WHITE_OK; /* at least ignore the need to reparse */ return WHITE_CONTINUE; } if (part_result < 0) return make_white_hash_bail(WHITE_COMPLAIN, wf, tmp_wf); /* clear the new hash table */ if (!write_white(emsg, tmp_wf, white_magic, sizeof(white_magic), 0) || !write_white(emsg, tmp_wf, &zero, 1, EMPTY_WHITE_SIZE-1) || !map_white_ht(emsg, tmp_wf, 0)) return make_white_hash_bail(WHITE_COMPLAIN, wf, tmp_wf); if (tmp_wf->wf_flags & WF_PER_USER) tmp_wf->wtbl->hdr.fgs = WHITE_FG_PER_USER; tmp_wf->wtbl_fgs = tmp_wf->wtbl->hdr.fgs; tmp_wf->wtbl_sws = tmp_wf->wtbl->hdr.sws; for (type = 0; type <= DCC_CK_TYPE_LAST; ++type) tmp_wf->wtbl->hdr.tholds_rej.t[type] = THOLD_UNSET; ascii_fd = dcc_lock_open(emsg, tmp_wf->ascii_nm.c, O_RDONLY, DCC_LOCK_OPEN_NOWAIT | DCC_LOCK_OPEN_SHARE, DCC_LOCK_ALL_FILE, 0); if (ascii_fd == -1) return make_white_hash_bail(WHITE_COMPLAIN, wf, tmp_wf); tmp_wf->wtbl->hdr.ascii_mtime = ascii_sb.st_mtime; part_result = parse_whitefile(emsg, tmp_wf, ascii_fd, add_white, add_white_ip_range); if (part_result > 0) { /* parse again to detect colliding definitions among * host names and IP address blocks */ if (0 != lseek(ascii_fd, 0, SEEK_SET)) { dcc_pemsg(EX_IOERR, emsg, "lseek(%s, 0, SEEK_SET): %s", wf->ascii_nm.c, ERROR_STR()); part_result = -1; } else { part_result = parse_whitefile(emsg, tmp_wf, ascii_fd, ck_dup_white_complain, ck_dup_white_ip_range); } } /* if the hash table is tolerable, compute a checksum of it * to detect differing per-user hash tables */ if (part_result >= 0 && map_white_ht(emsg, tmp_wf, tmp_wf->wtbl->hdr.entries)) { MD5_CTX ctx; MD5Init(&ctx); MD5Update(&ctx, (u_char *)&tmp_wf->wtbl->hdr.ranges, sizeof(tmp_wf->wtbl->hdr.ranges)); i = tmp_wf->wtbl->hdr.entries; while (--i >= 0) { MD5Update(&ctx, &tmp_wf->wtbl->tbl[i].type, sizeof(tmp_wf->wtbl->tbl[i].type)); MD5Update(&ctx, tmp_wf->wtbl->tbl[i].sum.b, sizeof(tmp_wf->wtbl->tbl[i].sum.b)); } MD5Final(tmp_wf->wtbl->hdr.ck_sum.b, &ctx); } #ifdef DCC_WIN32 /* unlock the file before closing it to keep Win95 happy */ dcc_unlock_fd(0, ascii_fd, DCC_LOCK_ALL_FILE, "whitelist ", tmp_wf->ascii_nm.c); #endif if (close(ascii_fd) < 0) dcc_trace_msg("close(%s): %s", wf->ascii_nm.c, ERROR_STR()); if (part_result < 0) return make_white_hash_bail(WHITE_COMPLAIN, wf, tmp_wf); result = (part_result > 0) ? WHITE_OK : WHITE_CONTINUE; /* ensure continued complaints about errors */ if (result == WHITE_CONTINUE) tmp_wf->wtbl->hdr.reparse = time(0) + WHITE_REPARSE_DELAY; sync_white(tmp_wf); #ifdef DCC_WIN32 /* WIN32 prohibits renaming open files * and there is little or no concurrency on WIN32 DCC clients * So lock the original file and copy to it. */ close_white_ht(0, wf); wf->ht_fd = dcc_lock_open(emsg, wf->ht_nm.c, O_RDWR | O_CREAT, 0, DCC_LOCK_ALL_FILE, 0); if (wf->ht_fd < 0) return make_white_hash_bail(WHITE_COMPLAIN, wf, tmp_wf); if (!map_white_ht(emsg, wf, tmp_wf->wtbl_entries)) return make_white_hash_bail(WHITE_COMPLAIN, wf, tmp_wf); memcpy(wf->wtbl, tmp_wf->wtbl, tmp_wf->wtbl_size); close_white_ht(emsg, tmp_wf); unlink(tmp_wf->ht_nm.c); #else part_result = rename(tmp_wf->ht_nm.c, wf->ht_nm.c); if (0 > part_result && dcc_get_priv_home(wf->ht_nm.c)) { part_result = rename(tmp_wf->ht_nm.c, wf->ht_nm.c); dcc_rel_priv(); } if (0 > part_result) { dcc_pemsg(EX_IOERR, emsg, "rename(%s, %s): %s", tmp_wf->ht_nm.c, wf->ht_nm.c, ERROR_STR()); return make_white_hash_bail(WHITE_COMPLAIN, wf, tmp_wf); } #endif close_white_ht(0, tmp_wf); if (dcc_clnt_debug > 1) dcc_trace_msg("finished parsing %s", wf->ascii_nm.c); part_result = open_white_ht(emsg, wf); if (part_result < 0) result = WHITE_COMPLAIN; else if (part_result == 0 && result == WHITE_OK) result = WHITE_CONTINUE; wf->next_stat_time = time(0) + WHITE_STAT_DELAY; return result; } /* see that a local whitelist is ready * on failure the file is not locked * The caller must lock the WF if necessary */ WHITE_RESULT wf_rdy(DCC_EMSG *emsg, WF *wf, WF *tmp_wf) { time_t now; int i; if (wf->need_reopen) { /* The resolver thread has change the hash table in the file * and possibly renamed it. * We need to re-open the hash table */ wf->broken = 0; wf->reparse = 0; close_white_ht(0, wf); } now = time(0); if (emsg) emsg->c[0] = '\0'; if (wf->ht_fd >= 0) { /* The hash table is open. * If we have checked recently, assume everything is good. */ if (!DCC_IS_TIME(now, wf->next_stat_time, WHITE_STAT_DELAY)) return WHITE_OK; i = stat_white_nms(emsg, wf); if (i > 0) return WHITE_OK; if (i < 0) { /* Things are broken or not open, so try to open the * hash table. * We may be racing here. Be happy if another process * fixes the hash table while we stall trying to open * it locked. */ i = open_white_ht(emsg, wf); if (i < 0) return WHITE_COMPLAIN; if (i > 0) i = stat_white_nms(emsg, wf); } } else { if (wf->ascii_nm.c[0] == '\0') { dcc_pemsg(EX_NOINPUT, emsg, "no whitelist"); return WHITE_NOFILE; } if (STILL_BROKE(wf, now)) { /* only check for a missing file occassionally */ if (wf->wf_flags & WF_NOFILE) { dcc_pemsg(EX_NOINPUT, emsg, "%s does not exist", wf->ascii_nm.c); return WHITE_NOFILE; } /* If things are broken, and it has not been a while, * then assume things are still broken. */ dcc_pemsg(EX_DATAERR, emsg, "%s still broken", wf->ascii_nm.c); return (dcc_clnt_debug > 2 ? WHITE_COMPLAIN : WHITE_SILENT); } i = open_white_ht(emsg, wf); if (i < 0) return WHITE_COMPLAIN; if (i > 0) i = stat_white_nms(emsg, wf); } if (i > 0) return WHITE_OK; /* Try to let the resolver thread wait for the DNS chitchat * for host names that might now be in the ASCII file. * To avoid racing with the resolver thread to delete the main * hash files, fail if there is no hash table and this is not * the resolver thread. */ if (i == 0 && !(wf->wf_flags & WF_PER_USER) && dcc_clnt_wake_resolve()) { if (wf->wtbl) return WHITE_OK; return (dcc_clnt_debug > 2 ? WHITE_COMPLAIN : WHITE_SILENT); } if (STILL_BROKE(wf, now)) { dcc_pemsg(EX_DATAERR, emsg, "%s still broken", wf->ascii_nm.c); if (i == 0 && wf->wtbl) return (dcc_clnt_debug > 2 ? WHITE_CONTINUE : WHITE_SILENT); return (dcc_clnt_debug > 2 ? WHITE_COMPLAIN : WHITE_SILENT); } if (emsg && emsg->c[0] != '\0') { if (i < 0) { dcc_error_msg("%s", emsg->c); } else if (dcc_clnt_debug > 2) { dcc_trace_msg("%s", emsg->c); } emsg->c[0] = '\0'; } switch (make_white_hash(emsg, wf, tmp_wf)) { case WHITE_OK: wf->wf_flags &= ~WF_NOFILE; return WHITE_OK; /* all is good */ case WHITE_CONTINUE: /* minor syntax error or bad host name */ wf->wf_flags &= ~WF_NOFILE; return WHITE_CONTINUE; case WHITE_SILENT: /* no new message */ wf->wf_flags &= ~WF_NOFILE; return WHITE_CONTINUE; case WHITE_NOFILE: wf->wf_flags |= WF_NOFILE; return WHITE_NOFILE; case WHITE_COMPLAIN: default: is_broken(wf); wf->wf_flags &= ~WF_NOFILE; return WHITE_COMPLAIN; } } static u_char lookup_white(DCC_EMSG *emsg, DCC_TGTS *tgtsp, /* value if hit else DCC_TGTS_INVALID */ WF *wf, const GOT_CKS *cks, const GOT_SUM *g) { const WHITE_ENTRY *e; WHITE_IP_RANGE *r; u_int r_len; e = find_white(emsg, wf, g->type, &g->sum, 0); if (e == FIND_WHITE_BROKEN) { *tgtsp = DCC_TGTS_OK; return 0; } if (!e) { if (g->type != DCC_CK_IP) { *tgtsp = DCC_TGTS_INVALID; return 1; } /* check the IP address ranges * if we had no hit and it is an IP address */ r_len = 0; r = &wf->wtbl->hdr.ranges.rs[wf->wtbl->hdr.ranges.len]; while (r != wf->wtbl->hdr.ranges.rs) { --r; /* look for the first longest match */ if (r->len <= r_len) continue; if (in_range(&cks->ip_addr, &r->range)) { *tgtsp = r->tgts; r_len = r->len; } } if (r_len == 0) *tgtsp = DCC_TGTS_INVALID; return 1; } *tgtsp = e->tgts; return 1; } /* check a local whitelist for a single checksum * on exit the file is locked except after an error */ WHITE_RESULT wf_sum(DCC_EMSG *emsg, WF *wf, /* in this whitelist */ DCC_CK_TYPES type, /* look for this checksum */ const DCC_SUM *sum, DCC_TGTS *tgtsp, /* set only if we find the checksum */ WHITE_LISTING *listingp) { WHITE_ENTRY *e; WHITE_RESULT result; result = wf_rdy(emsg, wf, &cmn_tmp_wf); switch (result) { case WHITE_OK: case WHITE_CONTINUE: break; case WHITE_SILENT: case WHITE_NOFILE: case WHITE_COMPLAIN: *listingp = WHITE_RESULT_FAILURE; return result; } e = find_white(emsg, wf, type, sum, 0); if (e == FIND_WHITE_BROKEN) { *listingp = WHITE_RESULT_FAILURE; return WHITE_COMPLAIN; } if (!e) { *listingp = WHITE_UNLISTED; } else if (e->tgts == DCC_TGTS_OK2 && type == DCC_CK_ENV_TO) { /* deprecated mechanism for turn DCC checks on and off for * individual targets */ *tgtsp = e->tgts; *listingp = WHITE_USE_DCC; } else if (e->tgts == DCC_TGTS_OK) { *tgtsp = e->tgts; *listingp = WHITE_LISTED; } else if (e->tgts == DCC_TGTS_TOO_MANY) { *tgtsp = e->tgts; *listingp = WHITE_BLACK; } else { *listingp = WHITE_UNLISTED; } return result; } /* see if an IP addess is that of one of our MX servers * the caller must lock cmn_wf if necessray */ u_char /* 0=problems */ white_mx(DCC_EMSG *emsg, DCC_TGTS *tgtsp, /* !=0 if listed */ const GOT_CKS *cks) /* this IP address checksum */ { u_char result; result = 1; switch (wf_rdy(emsg, &cmn_wf, &cmn_tmp_wf)) { case WHITE_OK: break; case WHITE_CONTINUE: result = 0; break; case WHITE_NOFILE: *tgtsp = 0; return 1; case WHITE_SILENT: case WHITE_COMPLAIN: *tgtsp = 0; return 0; } if (cks->sums[DCC_CK_IP].type != DCC_CK_IP) { *tgtsp = 0; return result; } if (!lookup_white(emsg, tgtsp, &cmn_wf, cks, &cks->sums[DCC_CK_IP])) return 0; return result; } /* See what a local whitelist file says about the checksums for a message. * The message is whitelisted if at least one checksum is in the local * whitelist or if there are two or more OK2 values. * Otherwise it is blacklisted if at least one checksum is. * The caller must lock the WF if necessary. */ WHITE_RESULT /* whether the lookup worked */ wf_cks(DCC_EMSG *emsg, WF *wf, GOT_CKS *cks, /* these checksums */ CKS_WTGTS *wtgts, /* whitelist targets, each cks->sums */ WHITE_LISTING *listingp) /* the answer found */ { const GOT_SUM *g; int inx; DCC_TGTS tgts, prev_tgts; WHITE_RESULT result; result = wf_rdy(emsg, wf, &cmn_tmp_wf); switch (result) { case WHITE_OK: case WHITE_CONTINUE: break; case WHITE_NOFILE: case WHITE_COMPLAIN: case WHITE_SILENT: *listingp = WHITE_RESULT_FAILURE; return result; } /* look for each checksum in the hash file */ *listingp = WHITE_UNLISTED; prev_tgts = DCC_TGTS_INVALID; tgts = DCC_TGTS_INVALID; for (g = &cks->sums[inx = DCC_CK_TYPE_FIRST]; g <= LAST(cks->sums); ++g, ++inx) { /* ignore checksums we don't have */ if (g->type == DCC_CK_INVALID) continue; if (!lookup_white(emsg, &tgts, wf, cks, g)) { *listingp = WHITE_RESULT_FAILURE; return WHITE_COMPLAIN; } if (tgts == DCC_TGTS_INVALID) { /* report any body checksums as spam for a spam trap */ if ((wf->wtbl_sws & WHITE_SW_TRAPS) && DCC_CK_IS_BODY(g->type)) tgts = DCC_TGTS_TOO_MANY; else continue; } if (wtgts) wtgts->v[inx] = tgts; if (tgts == DCC_TGTS_OK || tgts == DCC_TGTS_OK_MX || tgts == DCC_TGTS_OK_MXDCC) { /* We found the checksum in our whitelist, * so we have the answer. Mail from an MX relay * itself is assumed good. */ *listingp = WHITE_LISTED; } else if (tgts == DCC_TGTS_OK2) { if (prev_tgts == DCC_TGTS_OK2) { /* two half-white checksums count the same * as a single pure white checksum * and gives the answer */ *listingp = WHITE_LISTED; continue; } prev_tgts = DCC_TGTS_OK2; } else if (tgts == DCC_TGTS_TOO_MANY) { if (*listingp == WHITE_UNLISTED) *listingp = WHITE_BLACK; } } return result; }