Mercurial > notdcc
diff dccproc/dccproc.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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dccproc/dccproc.c Tue Mar 10 13:49:58 2009 +0100 @@ -0,0 +1,1349 @@ +/* Distributed Checksum Clearinghouse server + * + * report a message for such as procmail + * + * 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.189 $Revision$ + */ + +#include "dcc_ck.h" +#include "dcc_xhdr.h" +#include "dcc_heap_debug.h" +#include <signal.h> /* for Linux and SunOS*/ +#ifndef DCC_WIN32 +#include <arpa/inet.h> +#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 size_t log_size; + +static char id[DCC_MSG_ID_LEN+1]; +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_fname[sizeof(DCC_XHDR_START)+sizeof(DCC_BRAND)+1]; +static int xhdr_fname_len; +static u_char add_xhdr; /* add instead of replace header */ +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[DCC_MAXDOMAINLEN]; +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 = 0; + +static char mail_host[DCC_MAXDOMAINLEN]; + +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 void tmp_close(void); +static void scopy(int, u_char); +static void log_write(const void *, int); +static void log_body_write(const char *, u_int); +static void thr_log_write(void *, const char *, u_int); +static void log_late(void); +static int log_print(u_char, const char *, ...) PATTRIB(2,3); +#define LOG_CAPTION(s) log_write((s), LITZ(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\"\nusage: %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; + const char *tmpdir = 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 */ + dcc_syslog_init(1, argv[0], 0); + dcc_clear_tholds(); + + /* get ready for the IP and From header checksums */ + dcc_cks_init(&cks); + + /* 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:")) != -1) { + 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': + ask_st |= ASK_ST_QUERY; + 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, 10); + 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': + 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; + 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, 10); + 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, 10); + 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, 1)) + dccproc_error_msg("%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 SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif +#ifdef SIGHUP + signal(SIGHUP, sigterm); +#endif + signal(SIGTERM, sigterm); + signal(SIGINT, sigterm); +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* Close STDERR to keep it from being mixed with the message, + * unless we are not going to output the message to STDOUT. + * 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 && !ofile_nm) { + close(STDERR_FILENO); + clean_stdio(); + } + + dcc_clnt_unthread_init(); + dcc_cdhome(0, homedir, 0); + 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'; + } + tmp_path_init(tmpdir, logdir); + + 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) { + char date_buf[40]; + + gettimeofday(&ldate, 0); + log_print(0, DCC_LOG_DATE_PAT"\n", + dcc_time2str(date_buf, sizeof(date_buf), + DCC_LOG_DATE_FMT, + ldate.tv_sec)); + } + + if (!local_tgts_set) { + local_tgts = (ask_st & ASK_ST_QUERY) ? 0 : 1; + local_tgts_spam = 0; + } else if (local_tgts == 0) { + ask_st |= ASK_ST_QUERY; + local_tgts_spam = 0; + } else if (ask_st & ASK_ST_QUERY) { + 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; + } + + if (logging + || (!cksums_only && !x_dcc_only)) { + tmp_fd = dcc_mkstemp(dcc_emsg, tmp_nm, sizeof(tmp_nm), + id, sizeof(id), 0, + 0, DCC_TMP_LOG_PREFIX, 1, 0); + if (tmp_fd < 0) + dcc_logbad(EX_IOERR, "%s", dcc_emsg); + } + + /* start a connection to a DCC server + * we need the server's name for the X-DCC header. */ + ctxt = dcc_clnt_start(dcc_emsg, 0, mapfile_nm, + DCC_CLNT_FG_BAD_SRVR_OK + | DCC_CLNT_FG_NO_PICK_SRVR + | DCC_CLNT_FG_NO_FAIL); + if (ctxt) { + if (!homedir) + start_dccifd(); + dcc_emsg[0] = '\0'; + ctxt = dcc_clnt_start_fin(dcc_emsg, ctxt); + } + if (!ctxt) { + dccproc_error_msg("%s", dcc_emsg); + } else { + xhdr_fname_len = get_xhdr_fname(xhdr_fname, sizeof(xhdr_fname), + dcc_clnt_info); + } + + /* get the local whitelist ready */ + dcc_wf_init(&cmn_wf, DCC_WF_EITHER); + 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(); + + dcc_dnsbl_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 (!CLITCMP(buf, h)) { \ + dcc_get_cks(&cks,DCC_CK_##t, &buf[LITZ(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 + + /* notice UNIX From_ line */ + if (!seen_hdr + && !env_from + && parse_unix_from(buf, env_from_buf, + sizeof(env_from_buf))) { + env_from = env_from_buf; + seen_hdr = 1; + continue; + } + + if (!env_from && parse_return_path(buf, env_from_buf, + sizeof(env_from_buf))) { + env_from = env_from_buf; + seen_hdr = 1; + continue; + } + + if (!CLITCMP(buf, DCC_XHDR_TYPE_RECEIVED":")) { + seen_hdr = 1; + + p2 = &buf[LITZ(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_client_dnsbl(cks.dnsbl, &cks.ip_addr, sender_name); + + if (env_from) { + dcc_get_cks(&cks, DCC_CK_ENV_FROM, env_from, 1); + if (parse_mail_host(env_from, mail_host, sizeof(mail_host))) { + 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); + + 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, cks.dnsbl); + + /* Unlike dccm and dccifd, no "option DNSBL-on" line is required in + * the whiteclnt file. A -B argument is sufficient to show that + * DNSBL filtering is wanted. */ + if (ask_st & ASK_ST_DNSBL_HIT_M) + ask_st |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT); + + if (ctxt) { + if (ask_st & ASK_ST_QUERY) { + 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)); + 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') + xhdr_write(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) { + DCC_PATH abs_nm; + + if (ask_st & ASK_ST_WLIST_NOTSPAM) + log_print(0, "%s"DCC_XHDR_ISOK"\n", + fnm2abs_err(abs_nm, white_nm)); + else if (ask_st & ASK_ST_WLIST_ISSPAM) + log_print(0, "%s%s\n", + fnm2abs_err(abs_nm, white_nm), + (rcpt_sws & FLTR_SW_TRAP_ACC) + ? "-->"DCC_XHDR_TRAP_ACC + : (rcpt_sws & FLTR_SW_TRAP_REJ) + ? "-->"DCC_XHDR_TRAP_REJ + : DCC_XHDR_ISSPAM); + log_ask_st(thr_log_write, 0, ask_st, rcpt_sws, 0, &header); + } + + /* log error message for most prematurely closed output pipes before + * logging the checksums or sending them to the pipe for -C */ + if (EOF == fflush(ofile)) { + dcc_error_msg("fflush(%s): %s", ofile_nm, ERROR_STR()); + fclose(ofile); + ofile = 0; + } + + dcc_print_cks(log_ck, 0, local_tgts_spam, local_tgts, &cks, wtgts, 0); + + if (ofile) { + if (fclose(ofile)) + dcc_logbad(EX_IOERR, "fclose(%s): %s", + ofile_nm, ERROR_STR()); + ofile = 0; + } + + /* Exit saying it was spam unless this is an accepting spam trap. + * Spam traps report all mail as spam, but expect to receive it for + * logging or analysis. */ + if (((ask_st & ASK_ST_CLNT_ISSPAM) + || ((ask_st & ASK_ST_SRVR_ISSPAM) + && !(rcpt_sws & FLTR_SW_DCC_OFF)) + || ((ask_st & ASK_ST_REP_ISSPAM) + && (rcpt_sws & FLTR_SW_REP_ON))) + && !(rcpt_sws & FLTR_SW_TRAP_ACC)) { + p2 = "\n"DCC_XHDR_RESULT DCC_XHDR_RESULT_REJECT"\n"; + + } else { + p2 = "\n"DCC_XHDR_RESULT DCC_XHDR_RESULT_ACCEPT"\n"; + exit_code = EX_OK; + } + if (fake_envelope) + log_write(p2, strlen(p2)); + + log_fin(); + exit(exit_code); + +#ifdef DCC_WIN32 + return 0; +#endif +} + + + +static void +start_dccifd(void) +{ +#ifndef DCC_WIN32 + time_t t; + int c; + pid_t pid; + + assert_info_locked(); + + /* 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_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 */ +} + + + +/* If the immediate SMTP client because it is a listed MX server, + * then we must ignore its IP address and keep looking for the + * real SMTP client. */ +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) { + return 0; + } + + if (tgts == DCC_TGTS_SUBMIT_CLIENT) { + log_print(1, "%s is a listed 'submit' client\n", + dcc_trim_ffff(sender_str)); + return 0; + } + + if (tgts == DCC_TGTS_OK_MXDCC) { + log_print(1, "%s is a whitelisted MX server with DCC client\n", + dcc_trim_ffff(sender_str)); + ask_st |= ASK_ST_QUERY; + } 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; + } + + sender_str[0] = '\0'; + dcc_unget_ip_ck(&cks); + + /* tell caller to look at the next Received: header */ + 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) +{ + u_int i; + const char *estr; + + log_body_write(buf, buf_len); + if (ofile) { + i = fwrite(buf, 1, buf_len, ofile); + if (i != buf_len) { + estr = ERROR_STR(); + fclose(ofile); + ofile = 0; + dcc_logbad(EX_IOERR, "fwrite(add_hdr %s,%d)=%d: %s", + ofile_nm, buf_len, i, estr); + } + } +} + + + +/* 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 at the start of a field */ + if (hlen == 0 && !add_xhdr + && is_xhdr(buf, llen)) { + 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 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]; + const char *estr; + int buf_len, data_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)) { + estr = ERROR_STR(); + tmp_close(); + if (complain) + dcc_logbad(EX_IOERR, "rewind(%s): %s", + tmp_nm, estr); + } + } + + while (total_len > 0) { + buf_len = sizeof(buf); + if (buf_len > total_len) { + buf_len = total_len; + } + + data_len = read(tmp_fd, buf, buf_len); + if (data_len <= 0) { + estr = ERROR_STR(); + tmp_close(); + if (data_len < 0) + dcc_logbad(EX_IOERR, "read(%s, %d): %s", + tmp_nm, data_len, estr); + if (complain) + dcc_logbad(EX_IOERR, "premature end of %s", + tmp_nm); + return; + } + + log_body_write(buf, data_len); + + if (ofile && (!cksums_only && !x_dcc_only)) { + i = fwrite(buf, 1, data_len, ofile); + if (i != data_len) { + estr = ERROR_STR(); + fclose(ofile); + ofile = 0; + if (complain) + dcc_logbad(EX_IOERR, + "fwrite(scopy %s, %d)=%d:" + " %s", + ofile_nm, data_len, i, estr); + tmp_close(); + return; + } + } + + total_len -= data_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; + log_size += len; + } 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 +log_body_write(const char *buf, u_int buflen) +{ + int trimlen; + const char *p, *lim; + + if (lfd < 0) + return; + + /* just write if there is room */ + trimlen = MAX_LOG_KBYTE*1024 - log_size; + if (trimlen >= (int)buflen) { + log_write(buf, buflen); + return; + } + + /* do nothing if too much already written */ + if (trimlen < 0) + return; + + /* look for and end-of-line near the end of the buffer + * so that we can make the truncation pretty */ + lim = buf; + p = lim+trimlen; + if (trimlen > 90) + lim += trimlen-90; + while (--p > lim) { + if (*p == '\n') { + trimlen = p-buf+1; + break; + } + } + log_write(buf, trimlen); + if (buf[trimlen-1] != '\n') + LOG_EOL(); + LOG_CAPTION(DCC_LOG_TRN_MSG_CR); + log_size = MAX_LOG_KBYTE*1024+1; +} + + + +static void +thr_log_write(void *context UATTRIB, const char *buf, u_int len) +{ + log_write(buf, len); +} + + + +/* does not append '\n' */ +static int +vlog_print(u_char error, const char *p, va_list args) +{ + char logbuf[LOGBUF_SIZE]; + int i; + + /* buffer the message if we cannot write to the log file */ + if (error && (lfd < 0 || !tmp_rewound)) + 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; + } +} + + + +/* does not append '\n' */ +static int PATTRIB(2,3) +log_print(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; +} + + + +/* does not append '\n' */ +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 int +dccproc_verror_msg(const char *p, va_list args) +{ + char logbuf[LOGBUF_SIZE]; + + /* Some systems including Linux with gcc 3.4.2 on AMD 64 processors + * do not allow two uses of a va_list but requires va_copy() + * Other systems do not have any notion of va_copy(). */ + if (vsnprintf(logbuf, sizeof(logbuf), p, args) >= ISZ(logbuf)) + strcpy(&logbuf[ISZ(logbuf)-sizeof("...")], "..."); + + dcc_error_msg(logbuf); + + ask_st |= ASK_ST_LOGIT; + return log_print(1, "%s\n", logbuf); +} + + + +/* 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); +} + + + +int +thr_error_msg(void *cp UATTRIB, const char *p, ...) +{ + va_list args; + int i; + + va_start(args, p); + i = dccproc_verror_msg(p, args); + va_end(args); + + return i; +} + + + +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(EX_OK); /* don't tell procmail to reject mail */ +} + + + +/* watch for fatal signals */ +static void NRATTRIB +sigterm(int sig) +{ + log_fin(); + exit(-sig); +}