/* Distributed Checksum Clearinghouse server * * report a message for such as procmail * * 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.150 $Revision$ */ #include "dcc_ck.h" #include "dcc_xhdr.h" #include "dcc_paths.h" #include "dcc_heap_debug.h" #include /* for Linux and SunOS*/ #ifndef DCC_WIN32 #include #endif static DCC_EMSG dcc_emsg; static const char *mapfile_nm = DCC_MAP_NM_DEF; static u_char priv_logdir; static DCC_PATH log_path; static int lfd = -1; static struct timeval ldate; static u_char logging = 1; /* 0=no log, 1=have file, 2=used it */ static char id[DCC_MSG_ID_LEN+1]; static const char *tmpdir; /* can't static initialize for WIN32 */ static DCC_PATH tmp_nm; static int tmp_fd = -1; static u_char tmp_rewound; static int hdrs_len, body_len; static u_char seen_hdr; static int exit_code = EX_NOUSER; static DCC_TGTS local_tgts; static u_char local_tgts_spam, local_tgts_set; static int total_hdrs, cr_hdrs; static const char* white_nm; static const char *ifile_nm = "stdin", *ofile_nm = "stdout"; static FILE *ifile, *ofile; static DCC_CLNT_CTXT *ctxt; static char xhdr[sizeof(DCC_XHDR_START)+sizeof(DCC_BRAND)+1]; static int xhdr_len; static u_char add_xhdr; /* add instead of replace header */ static u_char dcc_query_only; static u_char cksums_only; /* output only checksums */ static u_char x_dcc_only; /* output only the X-DCC header */ static u_char fake_envelope; /* fake envelope log lines */ static int std_received; /* Received: line is standard */ static ASK_ST ask_st; static FLTR_SWS rcpt_sws; static DCC_GOT_CKS cks; static DCC_CKS_WTGTS wtgts; static char helo[DCC_HELO_MAX]; static char sender_name[MAXHOSTNAMELEN]; static char sender_str[INET6_ADDRSTRLEN]; static struct in6_addr clnt_addr; static char env_from_buf[DCC_HDR_CK_MAX+1]; static const char *env_from; static char mail_host[MAXHOSTNAMELEN]; static DCC_HEADER_BUF header; static EARLY_LOG early_log; static void start_dccifd(void); static u_char check_mx_listing(void); static int get_hdr(char *, int); static void add_hdr(void *, const char *, u_int); static void tmp_write(const void *, int); static int tmp_read(void *, int); static void tmp_close(void); static void scopy(int, u_char); static void thr_log_write(void *, const char *, u_int); static void log_write(const void *, int); static void log_late(void); static void log_print(u_char, const char *, ...) PATTRIB(2,3); #define LOG_CAPTION(s) log_write((s), STRZ(s)) #define LOG_EOL() LOG_CAPTION("\n") static void log_fin(void); static void log_ck(void *, const char *, u_int); static void dccproc_error_msg(const char *, ...) PATTRIB(1,2); static void sigterm(int); static const char *usage_str = "[-VdAQCHER] [-h homedir] [-m map] [-w whiteclnt] [-T tmpdir]\n" " [-a IP-address] [-f env_from] [-t targets] [-x exitcode]\n" " [-c type,[log-thold,][spam-thold]] [-g [not-]type] [-S header]\n" " [-i infile] [-o outfile] [-l logdir] [-B dnsbl-option]\n" " [-L ltype,facility.level]"; static void NRATTRIB usage(const char* barg) { if (barg) { dcc_logbad(EX_USAGE, "unrecognized \"%s\"\n%s\n", barg, usage_str); } else { dcc_logbad(EX_USAGE, "%s\n", usage_str); } } int NRATTRIB main(int argc, char **argv) { char buf[20*DCC_HDR_CK_MAX]; /* at least DCC_HDR_CK_MAX*3 */ u_char log_tgts_set = 0; const char *homedir = 0; const char *logdir = 0; u_char ask_result; char *p; const char *p2; u_long l; int error, blen, i; /* because stderr is often mixed with stdout and effectively * invisible, also complain to syslog */ tmpdir = _PATH_TMP; dcc_syslog_init(1, argv[0], 0); dcc_clear_tholds(); /* we must be SUID to read and write the system's common connection * parameter memory mapped file. We also need to read the common * local white list and write the mmap()'ed hash file */ dcc_init_priv(); ofile = stdout; ifile = stdin; opterr = 0; while ((i = getopt(argc, argv, "VdAQCHER" "r:h:m:w:T:a:f:g:S:t:x:c:i:o:l:B:L:")) != EOF) { switch (i) { case 'V': fprintf(stderr, DCC_VERSION"\n"); exit(EX_OK); break; case 'd': ++dcc_clnt_debug; break; case 'A': add_xhdr = 1; break; case 'Q': dcc_query_only = 1; break; case 'C': cksums_only = 1; break; case 'H': x_dcc_only = 1; break; case 'E': fake_envelope = 1; break; case 'R': if (!std_received) std_received = 1; break; case 'r': /* a bad idea replacment for -R */ std_received = strtoul(optarg, &p, 0); if (*p != '\0' || i == 0) { dccproc_error_msg("invalid count" "\"-e %s\"", optarg); std_received = 1; } break; case 'h': homedir = optarg; break; case 'm': mapfile_nm = optarg; break; case 'w': white_nm = optarg; break; case 'T': if (optarg[0] == '\0') dcc_error_msg("illegal temporary directory"); else tmpdir = optarg; break; case 'a': /* ignore SpamAssassin noise */ if (!strcmp("0.0.0.0", optarg)) break; dcc_host_lock(); if (!dcc_get_host(optarg, 2, &error)) { dccproc_error_msg("\"-a %s\": %s", optarg, DCC_HSTRERROR(error)); } else { if (dcc_hostaddrs[0].sa.sa_family == AF_INET) dcc_ipv4toipv6(&clnt_addr, dcc_hostaddrs[0] .ipv4.sin_addr); else clnt_addr = (dcc_hostaddrs[0].ipv6 .sin6_addr); dcc_get_ipv6_ck(&cks, &clnt_addr); dcc_ipv6tostr(sender_str, sizeof(sender_str), &clnt_addr); } dcc_host_unlock(); break; case 'f': env_from = optarg; dcc_get_cks(&cks, DCC_CK_ENV_FROM, env_from, 1); break; case 'g': /* honor not-spam "counts" */ dcc_parse_honor(optarg); break; case 'S': if (!dcc_add_sub_hdr(dcc_emsg, optarg)) dcc_logbad(EX_USAGE, "%s", dcc_emsg); break; case 't': if (!strcasecmp(optarg, "many")) { local_tgts = 1; local_tgts_spam = 1; local_tgts_set = 1; } else { l = strtoul(optarg, &p, 0); if (*p != '\0' || l > DCC_TGTS_RPT_MAX) { dccproc_error_msg("invalid count" "\"-t %s\"", optarg); } else { local_tgts = l; local_tgts_spam = 0; local_tgts_set = 1; } } break; case 'x': l = strtoul(optarg, &p, 0); if (*p != '\0') { dccproc_error_msg("invalid exit code \"-x %s\"", optarg); } else { exit_code = l; } break; case 'c': if (dcc_parse_tholds("-c ", optarg)) log_tgts_set = 1; break; case 'i': /* open the input file now, before changing to the * home DCC directory */ ifile_nm = optarg; ifile = fopen(ifile_nm, "r"); if (!ifile) dcc_logbad(EX_USAGE, "bad input file \"%s\": %s", ifile_nm, ERROR_STR()); break; case 'o': /* open the output file now, before changing to the * home DCC directory */ ofile_nm = optarg; ofile = fopen(ofile_nm, "w"); if (!ofile) dcc_logbad(EX_USAGE, "bad output file \"%s\": %s", ofile_nm, ERROR_STR()); break; case 'l': logdir = optarg; break; case 'B': if (!dcc_parse_dnsbl(dcc_emsg, optarg, 0)) dcc_logbad(EX_USAGE, "%s", dcc_emsg); break; #ifndef DCC_WIN32 case 'L': dcc_parse_log_opt(optarg); break; #endif default: usage(optopt2str(optopt)); } } if (argc != optind) usage(argv[optind]); #ifdef SIGHUP signal(SIGHUP, sigterm); #endif signal(SIGTERM, sigterm); signal(SIGINT, sigterm); #ifdef SIGXFSZ signal(SIGXFSZ, SIG_IGN); #endif dcc_clnt_unthread_init(); dcc_cdhome(0, homedir); if (!dcc_main_logdir_init(dcc_emsg, logdir)) { dcc_error_msg("%s", dcc_emsg); /* dccproc will not be around as a daemon * when and if the log directory is created * so forget about a directory that might * someday be ok */ dcc_main_logdir[0] = '\0'; } if (dcc_main_logdir[0] == '\0') { if (log_tgts_set) dccproc_error_msg("log thresholds set with -c" " but no -l directory"); logging = 0; } else { #ifndef DCC_WIN32 /* use privileges to make log files in the built-in home * directory */ if (!homedir && 0 > access(dcc_main_logdir, R_OK|W_OK|X_OK)) { priv_logdir = 1; dcc_get_priv_home(dcc_main_logdir); } #endif lfd = dcc_main_log_open(dcc_emsg, log_path, id, sizeof(id)); if (priv_logdir) dcc_rel_priv(); if (lfd < 0) { dccproc_error_msg("%s", dcc_emsg); logging = 0; } } if (fake_envelope && lfd >= 0) { struct tm tm; char date_buf[40]; gettimeofday(&ldate, 0); strftime(date_buf, sizeof(date_buf), DCC_LOG_DATE_FMT, dcc_localtime(ldate.tv_sec, &tm)); log_print(0, DCC_LOG_DATE_PAT"\n", date_buf); } if (!local_tgts_set) { local_tgts = dcc_query_only ? 0 : 1; local_tgts_spam = 0; } else if (local_tgts == 0) { dcc_query_only = 1; local_tgts_spam = 0; } else if (dcc_query_only) { dcc_error_msg("\"-t %s\" is incompatible with \"-Q\"", local_tgts_spam ? "many" : dcc_tgts2str(buf, sizeof(buf), local_tgts, 0)); local_tgts = 0; local_tgts_spam = 0; } if (local_tgts == DCC_TGTS_TOO_MANY) { local_tgts = 1; local_tgts_spam = 1; } #ifdef DCC_WIN32 /* Apparently WIN32 lacks /dev/null */ #else /* Close STDERR to keep it from being mixed with the message, * unless we are not going to output the message. * Ensure that stderr and file descriptor 2 are open to something * to prevent surprises from busybody libraries. */ if (!dcc_clnt_debug && !cksums_only && !x_dcc_only) { close(STDERR_FILENO); clean_stdio(); } #endif if (logging || (!cksums_only && !x_dcc_only)) { tmp_fd = dcc_mkstemp(dcc_emsg, tmp_nm, sizeof(tmp_nm), id, sizeof(id), tmpdir, DCC_TMP_LOG_PREFIX, 1, 0, 0); if (tmp_fd < 0) dcc_logbad(EX_IOERR, "%s", dcc_emsg); } /* start a connection to a DCC server */ ctxt = dcc_clnt_start(dcc_emsg, 0, mapfile_nm, DCC_CLNT_FG_NO_SRVR_OK); if (ctxt) { if (!homedir) start_dccifd(); ctxt = dcc_clnt_start_fin(dcc_emsg, ctxt); } if (!ctxt) { dccproc_error_msg("%s", dcc_emsg); } else { xhdr_len = dcc_xhdr_start(xhdr, sizeof(xhdr), dcc_clnt_info); } /* get the local whitelist ready */ dcc_wf_init(&cmn_wf, 0); if (white_nm && !dcc_new_white_nm(dcc_emsg, &cmn_wf, white_nm)) { dccproc_error_msg("%s", dcc_emsg); white_nm = 0; } /* look past the SMTP client if it is a listed MX server */ if (sender_str[0] != '\0' && white_nm) { check_mx_listing(); } /* get ready for the body checksums before the headers so that * we can notice the MIME separator */ dcc_cks_init(&cks, ctxt, 0, id); /* get the headers */ for (;;) { int hlen; hlen = get_hdr(buf, sizeof(buf)); if (hlen <= 2 && (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n'))) { /* stop at the separator between the body and headers */ if (!seen_hdr) dcc_logbad(EX_DATAERR, "missing SMTP header lines"); hdrs_len -= hlen; body_len = hlen; break; } #define GET_HDR_CK(h,t) { \ if (!CSTRCMP(buf, h)) { \ dcc_get_cks(&cks,DCC_CK_##t, &buf[STRZ(h)], 1);\ seen_hdr = 1; \ continue;}} GET_HDR_CK(DCC_XHDR_TYPE_FROM":", FROM); GET_HDR_CK(DCC_XHDR_TYPE_MESSAGE_ID":", MESSAGE_ID); #undef GET_HDR_CK if (!CSTRCMP(buf, "Return-Path:")) { if (parse_return_path(&buf[STRZ("Return-Path:")], &cks, env_from_buf)) env_from = env_from_buf; seen_hdr = 1; continue; } /* notice UNIX From_ line */ if (!seen_hdr && !strncmp(buf, "From ", STRZ("From "))) { p = &buf[STRZ("From ")]; p += strspn(p, " "); p2 = strchr(p, ' '); if (p2 != 0) { if (cks.sums[DCC_CK_ENV_FROM].type == DCC_CK_INVALID) { if (p2 > p+sizeof(env_from_buf)) p2 = p+sizeof(env_from_buf); memcpy(env_from_buf, p, p2-p); env_from_buf[p2-p] = '\0'; env_from = env_from_buf; dcc_get_cks(&cks, DCC_CK_ENV_FROM, env_from, 1); } seen_hdr = 1; continue; } } if (!CSTRCMP(buf, DCC_XHDR_TYPE_RECEIVED":")) { seen_hdr = 1; p2 = &buf[STRZ(DCC_XHDR_TYPE_RECEIVED":")]; /* compute checksum of the last Received: header */ dcc_get_cks(&cks, DCC_CK_RECEIVED, p2, 1); /* pick IP address out of Nth Received: header * unless we had a good -a value */ if (sender_str[0] != '\0') std_received = 0; if (!std_received) continue; if (--std_received > 0) continue; p2 = parse_received(p2, &cks, helo, sizeof(helo), sender_str, sizeof(sender_str), sender_name, sizeof(sender_name)); if (p2 == 0) { /* to avoid being fooled by forged Received: * fields, do not skip unrecognized forms */ std_received = 0; } else if (*p2 != '\0') { log_print(1, "skip %s Received: header\n", p2); std_received = 1; } else { std_received = check_mx_listing(); } continue; } /* Notice MIME multipart boundary definitions */ dcc_ck_mime_hdr(&cks, buf, 0); if (dcc_ck_get_sub(&cks, buf, 0)) seen_hdr = 1; /* notice any sort of header */ if (!seen_hdr) { for (p = buf; ; ++p) { if (*p == ':') { seen_hdr = 1; break; } if (*p <= ' ' || *p >= 0x7f) break; } } } /* Create a checksum for a null Message-ID header if there * was no Message-ID header. */ if (cks.sums[DCC_CK_MESSAGE_ID].type != DCC_CK_MESSAGE_ID) dcc_get_cks(&cks, DCC_CK_MESSAGE_ID, "", 0); /* Check DNS blacklists for STMP client and envelope sender * before collecting the body to avoid wasting time DNS resolving * URLs if the envelope answers the question. Much of the DNS * work for the envelope has probably already been done. */ if (cks.sums[DCC_CK_IP].type == DCC_CK_IP) dcc_sender_dnsbl(cks.dnsbl, &cks.ip_addr); if (env_from && (p = strchr(env_from, '@')) != 0) { BUFCPY(mail_host, p+1); p = strchr(mail_host, '>'); if (p) *p = '\0'; if (strpbrk(mail_host, ";@,")) mail_host[0] = '\0'; } if (mail_host[0] != '\0') { dcc_ck_get_sub(&cks, "mail_host", mail_host); dcc_mail_host_dnsbl(cks.dnsbl, mail_host); } /* collect the body */ do { blen = fread(buf, 1, sizeof(buf), ifile); if (blen != sizeof(buf)) { if (ferror(ifile)) dcc_logbad(EX_DATAERR, "fgets(%s): %s", ifile_nm, ERROR_STR()); if (!blen) break; } tmp_write(buf, blen); body_len += blen; dcc_ck_body(&cks, buf, blen); } while (!feof(ifile)); fclose(ifile); dcc_cks_fin(&cks, 0); if (!unthr_ask_white(dcc_emsg, &ask_st, &rcpt_sws, white_nm, &cks, wtgts)) dccproc_error_msg("%s", dcc_emsg); dcc_dnsbl_result(&ask_st, 0, cks.dnsbl); if ((ask_st & ASK_ST_DNSBL_ISSPAM) && (rcpt_sws & DCC_WHITE_FG_DNSBL_ON)) ask_st |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT); if (ctxt) { if (dcc_query_only) { local_tgts_spam = 0; local_tgts = 0; } if (local_tgts != 0 && (ask_st & ASK_ST_CLNT_ISSPAM)) local_tgts_spam = 1; ask_result = unthr_ask_dcc(dcc_emsg, ctxt, &header, &ask_st, &cks, local_tgts_spam, local_tgts); if (!ask_result) dccproc_error_msg("%s", dcc_emsg); } if (fake_envelope && lfd >= 0) { if (sender_str[0] != '\0') { LOG_CAPTION(DCC_XHDR_TYPE_IP": "); log_write(sender_name, strlen(sender_name)); LOG_CAPTION(" "); log_write(sender_str, strlen(sender_str)); LOG_EOL(); } if (helo[0] != '\0') { LOG_CAPTION("HELO: "); log_write(helo, strlen(helo)); LOG_EOL(); dcc_ck_get_sub(&cks, "helo", helo); } if (env_from) { LOG_CAPTION(DCC_XHDR_TYPE_ENV_FROM": "); log_write(env_from, strlen(env_from)); /* this would be nice, but might break compatibility */ log_print(0, " mail_host=%s", mail_host); LOG_EOL(); } LOG_EOL(); } /* copy the headers to the log file and the output */ scopy(hdrs_len, 1); /* emit the X-DCC and external filter headers * End them with "\r\n" if at least half of the header lines * ended that way. Otherwise use "\n" */ if (header.buf[0] != '\0') dcc_write_header(add_hdr, 0, header.buf, header.used, cr_hdrs > total_hdrs/2); /* emit body */ scopy(body_len, 1); LOG_CAPTION(DCC_LOG_MSG_SEP); log_late(); /* make the log file look like a dccm or dccifd log file */ if (fake_envelope) log_ask_st(thr_log_write, 0, ask_st, rcpt_sws, 0, 0, &header); dcc_print_cks(log_ck, 0, local_tgts_spam, local_tgts, &cks, wtgts, 0); if (ofile && fclose(ofile)) dcc_logbad(EX_IOERR, "fclose(%s): %s", ofile_nm, ERROR_STR()); log_fin(); if ((ask_st & ASK_ST_CLNT_ISSPAM) || ((ask_st & ASK_ST_SRVR_ISSPAM) && !(rcpt_sws & DCC_WHITE_FG_DCC_OFF)) || ((ask_st & ASK_ST_REP_ISSPAM) && (rcpt_sws & DCC_WHITE_FG_REP_ON))) exit(exit_code); exit(EX_OK); #ifdef DCC_WIN32 return 0; #endif } static void start_dccifd(void) { #ifndef DCC_WIN32 time_t t; int c; pid_t pid; /* once an hour, * start dccifd if dccproc is run more often than * DCCPROC_MAX_CREDITS times at an average rate of at least * DCCPROC_COST times per second */ t = (ctxt->start.tv_sec/DCCPROC_COST - dcc_clnt_info->dccproc_last/DCCPROC_COST); if (t > DCCPROC_MAX_CREDITS*2) /* don't overflow */ t = DCCPROC_MAX_CREDITS*2; else if (t < 0) t = 0; c = t + dcc_clnt_info->dccproc_c; if (c > DCCPROC_MAX_CREDITS) c = DCCPROC_MAX_CREDITS; --c; if (c < -DCCPROC_MAX_CREDITS) c = -DCCPROC_MAX_CREDITS; dcc_clnt_info->dccproc_c = c; dcc_clnt_info->dccproc_last = ctxt->start.tv_sec; if (dcc_clnt_info->dccproc_c >= 0) return; if (!DCC_IS_TIME(ctxt->start.tv_sec, dcc_clnt_info->dccproc_dccifd_try, DCCPROC_TRY_DCCIFD)) return; dcc_clnt_info->dccproc_dccifd_try = (ctxt->start.tv_sec + DCCPROC_TRY_DCCIFD); pid = fork(); if (pid) { if (pid < 0) dccproc_error_msg("fork(): %s", ERROR_STR()); return; } close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); clean_stdio(); dcc_unmap_info(0); dcc_rel_ctxt(ctxt); dcc_ctxts_unlock(); dcc_get_priv(); setuid(dcc_effective_uid); setgid(dcc_effective_gid); dcc_trace_msg("try to start dccifd"); execl(DCC_LIBEXECDIR"/start-dccifd", "start-dccifd", "-A", (const char *)0); dcc_trace_msg("exec("DCC_LIBEXECDIR"/start-dccifd): %s", ERROR_STR()); exit(0); #endif /* DCC_WIN32 */ } static u_char /* 1=listed MX server */ check_mx_listing(void) { DCC_TGTS tgts; if (!dcc_white_mx(dcc_emsg, &tgts, &cks)) dccproc_error_msg("%s", dcc_emsg); if (tgts == DCC_TGTS_OK_MXDCC) { log_print(1, "%s is a whitelisted MX server with DCC client\n", dcc_trim_ffff(sender_str)); dcc_query_only = 1; } else if (tgts == DCC_TGTS_OK_MX) { log_print(1, "%s is a whitelisted MX server\n", dcc_trim_ffff(sender_str)); } else { return 0; } dcc_unget_ipv6_ck(&cks); sender_str[0] = '\0'; return 1; } /* send a new header to the output and the log */ static void add_hdr(void *wp0 UATTRIB, const char *buf, u_int buf_len) { log_write(buf, buf_len); if (ofile) fwrite(buf, buf_len, 1, ofile); } /* get the next header line */ static int /* header length */ get_hdr(char *buf, int buflen) /* >DCC_HDR_CK_MAX*3 */ { u_char no_copy; int hlen, wpos; const char *line; char c; int llen, i; no_copy = 0; hlen = wpos = 0; for (;;) { line = fgets(&buf[hlen], buflen-hlen, ifile); if (!line) { if (ferror(ifile)) dcc_logbad(EX_DATAERR, "fgets(%s): %s", ifile_nm, ERROR_STR()); else dcc_logbad(EX_DATAERR, "missing message body"); } llen = strlen(line); /* delete our X-DCC header */ if (hlen == 0 && !add_xhdr && xhdr_len != 0 && llen > xhdr_len && buf[xhdr_len] == ':' && !strncasecmp(buf, xhdr, xhdr_len)) { seen_hdr = 1; no_copy = 1; } /* do not crash on too-long headers */ hlen += llen; if (hlen > DCC_HDR_CK_MAX*2) { /* truncate headers too big for our buffer */ if (!no_copy && ((i = (hlen - wpos)) > 0)) { tmp_write(&buf[wpos], i); hdrs_len += i; } c = buf[hlen-1]; hlen = DCC_HDR_CK_MAX; buf[hlen++] = '\r'; buf[hlen++] = '\n'; wpos = hlen; if (c != '\n') continue; } /* get the next character after the end-of-line to see if * the next line is a continuation */ if (hlen > 2) { i = getc(ifile); if (i != EOF) ungetc(i, ifile); if (i == ' ' || i == '\t') continue; } /* not a continuation, so stop reading the field */ ++total_hdrs; /* notice if this line ended with "\r\n" */ if (hlen > 1 && buf[hlen-2] == '\r') ++cr_hdrs; if (!no_copy) { i = hlen - wpos; if (i > 0) { tmp_write(&buf[wpos], hlen-wpos); hdrs_len += i; } return hlen; } /* at the end of our X-DCC header, look for another */ no_copy = 0; hlen = wpos = 0; } } static void tmp_write(const void *buf, int len) { int i; if (tmp_fd < 0) return; if (tmp_rewound) dcc_logbad(EX_SOFTWARE, "writing to rewound temp file"); i = write(tmp_fd, buf, len); if (i != len) { if (i < 0) dcc_logbad(EX_IOERR, "write(%s,%d): %s", tmp_nm, len, ERROR_STR()); else dcc_logbad(EX_IOERR, "write(%s,%d)=%d", tmp_nm, len, i); } } static int tmp_read(void *buf, int len) { int i; i = read(tmp_fd, buf, len); if (i <= 0) { if (i < 0) dcc_logbad(EX_IOERR, "read(%s,%d): %s", tmp_nm, len, ERROR_STR()); tmp_close(); } return i; } static void tmp_close(void) { if (tmp_fd >= 0) { if (0 < close(tmp_fd)) dcc_error_msg("close(%s): %s", tmp_nm, ERROR_STR()); tmp_fd = -1; } } /* copy some of the temporary file to the output */ static void scopy(int total_len, /* copy this much of temporary file */ u_char complain) /* 1=ok to complain about problems */ { char buf[BUFSIZ]; int len, i; if (tmp_fd < 0) return; /* if the temporary file has not been rewound, * then rewind it now */ if (!tmp_rewound) { tmp_rewound = 1; if (0 > lseek(tmp_fd, 0, SEEK_SET)) { if (complain) dcc_logbad(EX_IOERR, "rewind(%s): %s", tmp_nm, ERROR_STR()); tmp_close(); } } while (total_len > 0) { len = sizeof(buf); if (len > total_len) { len = total_len; } len = tmp_read(buf, len); if (len == 0) { if (complain) dcc_logbad(EX_IOERR, "premature end of %s", tmp_nm); tmp_close(); return; } log_write(buf, len); if (ofile && (!cksums_only && !x_dcc_only)) { i = fwrite(buf, 1, len, ofile); if (i != len) { if (complain) { if (feof(ofile)) dcc_logbad(EX_IOERR, "premature end of %s", ofile_nm); else dcc_logbad(EX_IOERR, "fwrite(%s): %s", ofile_nm, ERROR_STR()); } tmp_close(); return; } } total_len -= len; } } static void log_write(const void *buf, int len) { int i; if (lfd < 0) return; i = write(lfd, buf, len); if (i == len) { logging = 2; } else { dcc_error_msg("write(log %s): %s", log_path, ERROR_STR()); dcc_log_close(0, log_path, lfd, &ldate); lfd = -1; logging = 0; log_path[0] = '\0'; } } static void thr_log_write(void *cp UATTRIB, const char *buf, u_int len) { log_write(buf, len); } static int vlog_print(u_char error, const char *p, va_list args) { char logbuf[MAXHOSTNAMELEN*2]; int i; if (error && (lfd < 0 || !tmp_rewound)) { /* buffer the message if we cannot write to the log file */ return dcc_vearly_log(&early_log, p, args); } if (lfd < 0) return 0; i = vsnprintf(logbuf, sizeof(logbuf), p, args); if (i >= ISZ(logbuf)) i = sizeof(logbuf)-1; log_write(logbuf, i); return i; } static void log_late(void) { if (early_log.len) { log_write(early_log.buf, early_log.len); early_log.len = 0; } } static void PATTRIB(2,3) log_print(u_char error, const char *p, ...) { va_list args; va_start(args, p); vlog_print(error, p, args); va_end(args); } int thr_log_print(void *cp UATTRIB, u_char error, const char *p, ...) { va_list args; int i; va_start(args, p); i = vlog_print(error, p, args); va_end(args); return i; } static void log_fin(void) { if (log_path[0] == '\0') return; /* Close before renaming to accomodate WIN32 foolishness. * Assuming dcc_mkstemp() works properly, there is no race */ dcc_log_close(0, log_path, lfd, &ldate); lfd = -1; #ifndef DCC_WIN32 if (priv_logdir) dcc_get_priv_home(dcc_main_logdir); #endif if (!(ask_st & ASK_ST_LOGIT) || !dcc_log_keep(0, log_path)) { if (0 > unlink(log_path)) dccproc_error_msg("unlink(%s): %s", log_path, ERROR_STR()); log_path[0] = '\0'; } if (priv_logdir) dcc_rel_priv(); } static void log_ck(void *arg UATTRIB, const char *buf, u_int buf_len) { if (cksums_only && ofile) fputs(buf, ofile); log_write(buf, buf_len); } /* try to send error message to dccproc log file as well as sendmail */ static void dccproc_verror_msg(const char *p, va_list args) { DCC_ARGS2_COPY(); dcc_verror_msg(p, DCC_ARGS2); DCC_ARGS2_END(); vlog_print(1, p, args); log_print(1, "\n"); ask_st |= ASK_ST_LOGIT; } /* try to send error message to dccproc log file as well as sendmail */ static void PATTRIB(1,2) dccproc_error_msg(const char *p, ...) { va_list args; va_start(args, p); dccproc_verror_msg(p, args); va_end(args); } void thr_error_msg(void *cp UATTRIB, const char *p, ...) { va_list args; va_start(args, p); dccproc_verror_msg(p, args); va_end(args); } void thr_trace_msg(void *cp UATTRIB, const char *p, ...) { va_list args; va_start(args, p); dccproc_verror_msg(p, args); va_end(args); } /* things are so sick that we must bail out */ void NRATTRIB dcc_logbad(int ex_code, const char *p, ...) { char buf[BUFSIZ]; va_list args; size_t len; log_late(); if (*p >= ' ' && !tmp_rewound) { va_start(args, p); dcc_vfatal_msg(p, args); va_end(args); ask_st |= ASK_ST_LOGIT; if (logging > 1) log_write("\n", 1); va_start(args, p); vlog_print(0, p, args); va_end(args); log_write("\n\n", 2); p = 0; } /* copy first from the temporary file and then the input * to try to ensure that we don't lose mail */ scopy(INT_MAX, 0); if (ifile && ofile && !cksums_only && !x_dcc_only) { do { len = fread(buf, 1, sizeof(buf), ifile); if (!len) break; log_write(buf, len); } while (len == fwrite(buf, 1, len, ofile)); } if (p && *p >= ' ') { va_start(args, p); dcc_vfatal_msg(p, args); va_end(args); log_write("\n\n", 2); va_start(args, p); vlog_print(0,p, args); va_end(args); log_write("\n", 1); } log_fin(); if (ex_code == EX_SOFTWARE) abort(); exit(0); /* don't tell procmail to reject mail */ } /* watch for fatal signals */ static void NRATTRIB sigterm(int sig) { log_fin(); exit(-sig); }