diff options
author | Eric S. Raymond <esr@thyrsus.com> | 1998-10-16 13:01:06 +0000 |
---|---|---|
committer | Eric S. Raymond <esr@thyrsus.com> | 1998-10-16 13:01:06 +0000 |
commit | 580682dcaae8305ac9ca9c5e1011ef963d86f5d1 (patch) | |
tree | 8d5ccac1987c1e986400daa4f5a411bde885ecf9 | |
parent | 380a2d1a3a08c38a6280e1a1bc376a4c3b79e21d (diff) | |
download | fetchmail-580682dcaae8305ac9ca9c5e1011ef963d86f5d1.tar.gz fetchmail-580682dcaae8305ac9ca9c5e1011ef963d86f5d1.tar.bz2 fetchmail-580682dcaae8305ac9ca9c5e1011ef963d86f5d1.zip |
Now we can use --limit with daemon mode.
svn path=/trunk/; revision=2091
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | conf.c | 1 | ||||
-rw-r--r-- | driver.c | 947 | ||||
-rw-r--r-- | fetchmail-features.html | 9 | ||||
-rw-r--r-- | fetchmail.c | 25 | ||||
-rw-r--r-- | fetchmail.h | 4 | ||||
-rw-r--r-- | fetchmail.man | 32 | ||||
-rwxr-xr-x | fetchmailconf | 6 | ||||
-rw-r--r-- | options.c | 12 | ||||
-rw-r--r-- | rcfile_l.l | 1 | ||||
-rw-r--r-- | rcfile_y.y | 3 | ||||
-rw-r--r-- | tunable.h | 3 | ||||
-rw-r--r-- | uid.c | 2 |
13 files changed, 647 insertions, 405 deletions
@@ -3,6 +3,13 @@ fetchmail-4.6.2 (): * Time out server open requests like we do reads. This protects against buggy TCP/IP configurations that hang forever on a bad open. +* The '--limit' option can now be used with daemon mode. +* Implementation of a warning procedure to notify a user that some messages + are being skipped on the mail server; the user get notified by an E-Mail + that mentions all the messages being skipped (including their size). + (Implementation is thanks to Marc Jauvin). +* A -w/--warnings parameter can be set on the command line to specify at + what interval size warnings are sent to the user (default = 3600 sec). There are 255 people on fetchmail-friends and 293 on fetchmail-announce. @@ -268,6 +268,7 @@ void dump_config(struct runctl *runp, struct query *querylist) stringdump("preconnect", ctl->preconnect); stringdump("postconnect", ctl->postconnect); numdump("limit", ctl->limit); + numdump("warnings", ctl->warnings); numdump("fetchlimit", ctl->fetchlimit); numdump("batchlimit", ctl->batchlimit); numdump("expunge", ctl->expunge); @@ -438,7 +438,7 @@ static int smtp_open(struct query *ctl) return(ctl->smtp_socket); } -/* these are shared by stuffline, readheaders and readbody */ +/* these are shared by open_sink, stuffline, readheaders and readbody */ static FILE *sinkfp; static RETSIGTYPE (*sigchld)(); static int sizeticker; @@ -514,10 +514,8 @@ static int stuffline(struct query *ctl, char *buf) return(n); } - -static void sanitize(s) +static void sanitize(char *s) /* replace unsafe shellchars by an _ */ -char *s; { static char *ok_chars = " 1234567890!@%-_=+:,./abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; char *cp; @@ -526,6 +524,379 @@ char *s; *cp = '_'; } +int open_sink(struct query *ctl, + char *return_path, char *destaddr, + struct idlist *xmit_names, + long reallen, + int *good_addresses, int *bad_addresses) +/* set up sinkfp to be an input sink we can ship a message to */ +{ + struct idlist *idp; + + *bad_addresses = *good_addresses = 0; + + if (ctl->mda) /* we have a declared MDA */ + { + int length = 0, fromlen = 0, nameslen = 0; + char *names = NULL, *before, *after, *from = NULL; + + for (idp = xmit_names; idp; idp = idp->next) + if (idp->val.status.mark == XMIT_ACCEPT) + (*good_addresses)++; + + length = strlen(ctl->mda); + before = xstrdup(ctl->mda); + + /* get user addresses for %T (or %s for backward compatibility) */ + if (strstr(before, "%s") || strstr(before, "%T")) + { + /* + * We go through this in order to be able to handle very + * long lists of users and (re)implement %s. + */ + nameslen = 0; + for (idp = xmit_names; idp; idp = idp->next) + if (idp->val.status.mark == XMIT_ACCEPT) + nameslen += (strlen(idp->id) + 1); /* string + ' ' */ + if (*good_addresses = 0) + nameslen = strlen(run.postmaster); + + names = (char *)xmalloc(nameslen + 1); /* account for '\0' */ + if (*good_addresses == 0) + strcpy(names, run.postmaster); + else + { + names[0] = '\0'; + for (idp = xmit_names; idp; idp = idp->next) + if (idp->val.status.mark == XMIT_ACCEPT) + { + strcat(names, idp->id); + strcat(names, " "); + } + names[--nameslen] = '\0'; /* chop trailing space */ + } + + /* sanitize names in order to contain only harmless shell chars */ + sanitize(names); + } + + /* get From address for %F */ + if (strstr(before, "%F")) + { + from = xstrdup(return_path); + + /* sanitize from in order to contain *only* harmless shell chars */ + sanitize(from); + + fromlen = strlen(from); + } + + /* do we have to build an mda string? */ + if (names || from) + { + char *sp, *dp; + + /* find length of resulting mda string */ + sp = before; + while (sp = strstr(sp, "%s")) { + length += nameslen - 2; /* subtract %s */ + sp += 2; + } + sp = before; + while (sp = strstr(sp, "%T")) { + length += nameslen - 2; /* subtract %T */ + sp += 2; + } + sp = before; + while (sp = strstr(sp, "%F")) { + length += fromlen - 2; /* subtract %F */ + sp += 2; + } + + after = xmalloc(length + 1); + + /* copy mda source string to after, while expanding %[sTF] */ + for (dp = after, sp = before; *dp = *sp; dp++, sp++) { + if (sp[0] != '%') continue; + + /* need to expand? BTW, no here overflow, because in + ** the worst case (end of string) sp[1] == '\0' */ + if (sp[1] == 's' || sp[1] == 'T') { + strcpy(dp, names); + dp += nameslen; + sp++; /* position sp over [sT] */ + dp--; /* adjust dp */ + } else if (sp[1] == 'F') { + strcpy(dp, from); + dp += fromlen; + sp++; /* position sp over F */ + dp--; /* adjust dp */ + } + } + + if (names) { + free(names); + names = NULL; + } + if (from) { + free(from); + from = NULL; + } + + free(before); + + before = after; + } + + + if (outlevel == O_VERBOSE) + error(0, 0, "about to deliver with: %s", before); + +#ifdef HAVE_SETEUID + /* + * Arrange to run with user's permissions if we're root. + * This will initialize the ownership of any files the + * MDA creates properly. (The seteuid call is available + * under all BSDs and Linux) + */ + seteuid(ctl->uid); +#endif /* HAVE_SETEUID */ + + sinkfp = popen(before, "w"); + free(before); + before = NULL; + +#ifdef HAVE_SETEUID + /* this will fail quietly if we didn't start as root */ + seteuid(0); +#endif /* HAVE_SETEUID */ + + if (!sinkfp) + { + error(0, 0, "MDA open failed"); + return(PS_IOERR); + } + + sigchld = signal(SIGCHLD, SIG_DFL); + } + else + { + char *ap, options[MSGBUFSIZE], addr[128]; + + /* build a connection to the SMTP listener */ + if ((smtp_open(ctl) == -1)) + { + error(0, errno, "SMTP connect to %s failed", + ctl->smtphost ? ctl->smtphost : "localhost"); + return(PS_SMTP); + } + + /* + * Compute ESMTP options. + */ + options[0] = '\0'; + if (ctl->server.esmtp_options & ESMTP_8BITMIME) { + if (ctl->pass8bits || (mimemsg & MSG_IS_8BIT)) + strcpy(options, " BODY=8BITMIME"); + else if (mimemsg & MSG_IS_7BIT) + strcpy(options, " BODY=7BIT"); + } + + if ((ctl->server.esmtp_options & ESMTP_SIZE) && reallen > 0) + sprintf(options + strlen(options), " SIZE=%ld", reallen); + + /* + * Try to get the SMTP listener to take the Return-Path + * address as MAIL FROM . If it won't, fall back on the + * calling-user ID. This won't affect replies, which use the + * header From address anyway. + * + * RFC 1123 requires that the domain name part of the + * MAIL FROM address be "canonicalized", that is a + * FQDN or MX but not a CNAME. We'll assume the From + * header is already in this form here (it certainly + * is if rewrite is on). RFC 1123 is silent on whether + * a nonexistent hostname part is considered canonical. + * + * This is a potential problem if the MTAs further upstream + * didn't pass canonicalized From/Return-Path lines, *and* the + * local SMTP listener insists on them. + * + * None of these error conditions generates bouncemail. Comments + * below explain for each case why this is so. + */ + ap = (return_path[0]) ? return_path : user; + if (SMTP_from(ctl->smtp_socket, ap, options) != SM_OK) + { + int smtperr = atoi(smtp_response); + + if (str_find(&ctl->antispam, smtperr)) + { + /* + * SMTP listener explicitly refuses to deliver mail + * coming from this address, probably due to an + * anti-spam domain exclusion. Respect this. Don't + * try to ship the message, and don't prevent it from + * being deleted. Typical values: + * + * 501 = exim's old antispam response + * 550 = exim's new antispam response (temporary) + * 553 = sendmail 8.8.7's generic REJECT + * 571 = sendmail's "unsolicited email refused" + * + * We don't send bouncemail on antispam failures because + * we don't want the scumbags to know the address is even + * valid. + */ + SMTP_rset(ctl->smtp_socket); /* required by RFC1870 */ + return(PS_REFUSED); + } + + /* + * Suppress error message only if the response specifically + * meant `excluded for policy reasons'. We *should* see + * an error when the return code is less specific. + */ + if (smtperr >= 400) + error(0, -1, "SMTP error: %s", smtp_response); + + switch (smtperr) + { + case 452: /* insufficient system storage */ + /* + * Temporary out-of-queue-space condition on the + * ESMTP server. Don't try to ship the message, + * and suppress deletion so it can be retried on + * a future retrieval cycle. + * + * Bouncemail *might* be appropriate here as a delay + * notification. But it's not really necessary because + * this is not an actual failure, we're very likely to be + * able to recover on the next cycle. + */ + SMTP_rset(ctl->smtp_socket); /* required by RFC1870 */ + return(PS_TRANSIENT); + + case 552: /* message exceeds fixed maximum message size */ + case 553: /* invalid sending domain */ + /* + * Permanent no-go condition on the + * ESMTP server. Don't try to ship the message, + * and allow it to be deleted. + * + * Bouncemail would be appropriate for 552, but in these + * latter days 553 usually means a spammer is trying to + * cover his tracks. We'd rather deny the scumbags any + * feedback that the address is valid. + */ + SMTP_rset(ctl->smtp_socket); /* required by RFC1870 */ + return(PS_REFUSED); + + default: /* retry with postmaster's address */ + if (SMTP_from(ctl->smtp_socket,run.postmaster,options)!=SM_OK) + { + error(0, -1, "SMTP error: %s", smtp_response); + return(PS_SMTP); /* should never happen */ + } + } + } + + /* + * Now list the recipient addressees + */ + for (idp = xmit_names; idp; idp = idp->next) + if (idp->val.status.mark == XMIT_ACCEPT) + { + if (strchr(idp->id, '@')) + strcpy(addr, idp->id); + else +#ifdef HAVE_SNPRINTF + snprintf(addr, sizeof(addr)-1, "%s@%s", idp->id, destaddr); +#else + sprintf(addr, "%s@%s", idp->id, destaddr); +#endif /* HAVE_SNPRINTF */ + + if (SMTP_rcpt(ctl->smtp_socket, addr) == SM_OK) + (*good_addresses)++; + else + { + (*bad_addresses)++; + idp->val.status.mark = XMIT_ANTISPAM; + error(0, 0, + "SMTP listener doesn't like recipient address `%s'", + addr); + } + } + if (!(*good_addresses)) + { +#ifdef HAVE_SNPRINTF + snprintf(addr, sizeof(addr)-1, "%s@%s", run.postmaster, destaddr); +#else + sprintf(addr, "%s@%s", run.postmaster, destaddr); +#endif /* HAVE_SNPRINTF */ + + if (SMTP_rcpt(ctl->smtp_socket, addr) != SM_OK) + { + error(0, 0, "can't even send to %s!", run.postmaster); + return(PS_SMTP); + } + } + + /* tell it we're ready to send data */ + SMTP_data(ctl->smtp_socket); + } + + return(PS_SUCCESS); +} + +void release_sink(struct query *ctl) +/* release the per-message output sink, whether it's a pipe or SMTP socket */ +{ + if (ctl->mda) + { + if (sinkfp) + { + pclose(sinkfp); + sinkfp = (FILE *)NULL; + } + signal(SIGCHLD, sigchld); + } +} + +int close_sink(struct query *ctl, flag forward) +/* perform end-of-message actions on the current output sink */ +{ + if (ctl->mda) + { + int rc; + + /* close the delivery pipe, we'll reopen before next message */ + if (sinkfp) + { + rc = pclose(sinkfp); + sinkfp = (FILE *)NULL; + } + else + rc = 0; + signal(SIGCHLD, sigchld); + if (rc) + { + error(0, -1, "MDA exited abnormally or returned nonzero status"); + return(FALSE); + } + } + else if (forward) + { + /* write message terminator */ + if (SMTP_eom(ctl->smtp_socket) != SM_OK) + { + error(0, -1, "SMTP listener refused delivery"); + return(FALSE); + } + } + + return(TRUE); +} #define EMPTYLINE(s) ((s)[0] == '\r' && (s)[1] == '\n' && (s)[2] == '\0') @@ -552,13 +923,12 @@ int num; /* index of message */ int from_offs, reply_to_offs, resent_from_offs; int app_from_offs, sender_offs, resent_sender_offs; int env_offs; - char *headers, *received_for, *destaddr, *rcv, *cp; + char *headers, *destaddr, *received_for, *rcv, *cp; int n, linelen, oldlen, ch, remaining, skipcount; struct idlist *idp, *xmit_names; - flag good_addresses, bad_addresses, has_nuls; flag no_local_matches = FALSE; - flag headers_ok; - int olderrs; + flag headers_ok, has_nuls; + int olderrs, good_addresses, bad_addresses; sizeticker = 0; has_nuls = headers_ok = FALSE; @@ -944,7 +1314,7 @@ int num; /* index of message */ /* cons up a list of local recipients */ xmit_names = (struct idlist *)NULL; - bad_addresses = good_addresses = accept_count = reject_count = 0; + accept_count = reject_count = 0; /* is this a multidrop box? */ if (MULTIDROP(ctl)) { @@ -1020,332 +1390,27 @@ int num; /* index of message */ free_str_list(&xmit_names); return(PS_TRANSIENT); } - else if (ctl->mda) /* we have a declared MDA */ + else { - int length = 0, fromlen = 0, nameslen = 0; - char *names = NULL, *before, *after, *from = NULL; - - for (idp = xmit_names; idp; idp = idp->next) - if (idp->val.status.mark == XMIT_ACCEPT) - good_addresses++; - - destaddr = "localhost"; - - length = strlen(ctl->mda); - before = xstrdup(ctl->mda); - - /* get user addresses for %T (or %s for backward compatibility) */ - if (strstr(before, "%s") || strstr(before, "%T")) - { + if (ctl->mda) + destaddr = "localhost"; + else /* - * We go through this in order to be able to handle very - * long lists of users and (re)implement %s. + * RFC 1123 requires that the domain name part of the + * RCPT TO address be "canonicalized", that is a FQDN + * or MX but not a CNAME. Some listeners (like exim) + * enforce this. */ - nameslen = 0; - for (idp = xmit_names; idp; idp = idp->next) - if (idp->val.status.mark == XMIT_ACCEPT) - nameslen += (strlen(idp->id) + 1); /* string + ' ' */ + destaddr = ctl->smtpaddress ? ctl->smtpaddress : ( ctl->smtphost ? ctl->smtphost : "localhost"); - names = (char *)xmalloc(nameslen + 1); /* account for '\0' */ - names[0] = '\0'; - for (idp = xmit_names; idp; idp = idp->next) - if (idp->val.status.mark == XMIT_ACCEPT) - { - strcat(names, idp->id); - strcat(names, " "); - } - names[--nameslen] = '\0'; /* chop trailing space */ - - /* sanitize names in order to contain *only* harmless shell chars */ - sanitize(names); - } - - /* get From address for %F */ - if (strstr(before, "%F")) - { - from = xstrdup(return_path); - - /* sanitize from in order to contain *only* harmless shell chars */ - sanitize(from); - - fromlen = strlen(from); - } - - /* do we have to build an mda string? */ - if (names || from) { - - char *sp, *dp; - - - /* find length of resulting mda string */ - sp = before; - while (sp = strstr(sp, "%s")) { - length += nameslen - 2; /* subtract %s */ - sp += 2; - } - sp = before; - while (sp = strstr(sp, "%T")) { - length += nameslen - 2; /* subtract %T */ - sp += 2; - } - sp = before; - while (sp = strstr(sp, "%F")) { - length += fromlen - 2; /* subtract %F */ - sp += 2; - } - - after = xmalloc(length + 1); - - /* copy mda source string to after, while expanding %[sTF] */ - for (dp = after, sp = before; *dp = *sp; dp++, sp++) { - if (sp[0] != '%') continue; - - /* need to expand? BTW, no here overflow, because in - ** the worst case (end of string) sp[1] == '\0' */ - if (sp[1] == 's' || sp[1] == 'T') { - strcpy(dp, names); - dp += nameslen; - sp++; /* position sp over [sT] */ - dp--; /* adjust dp */ - } else if (sp[1] == 'F') { - strcpy(dp, from); - dp += fromlen; - sp++; /* position sp over F */ - dp--; /* adjust dp */ - } - } - - if (names) { - free(names); - names = NULL; - } - if (from) { - free(from); - from = NULL; - } - - free(before); - - before = after; - } - - - if (outlevel == O_VERBOSE) - error(0, 0, "about to deliver with: %s", before); - -#ifdef HAVE_SETEUID - /* - * Arrange to run with user's permissions if we're root. - * This will initialize the ownership of any files the - * MDA creates properly. (The seteuid call is available - * under all BSDs and Linux) - */ - seteuid(ctl->uid); -#endif /* HAVE_SETEUID */ - - sinkfp = popen(before, "w"); - free(before); - before = NULL; - -#ifdef HAVE_SETEUID - /* this will fail quietly if we didn't start as root */ - seteuid(0); -#endif /* HAVE_SETEUID */ - - if (!sinkfp) + /* set up sinkfp so we can deliver the message body through it */ + if ((n = open_sink(ctl, return_path, destaddr, xmit_names, reallen, + &good_addresses, &bad_addresses)) != PS_SUCCESS) { - error(0, 0, "MDA open failed"); free(headers); free_str_list(&xmit_names); - return(PS_IOERR); + return(n); } - - sigchld = signal(SIGCHLD, SIG_DFL); - } - else - { - char *ap, options[MSGBUFSIZE], addr[128]; - - /* build a connection to the SMTP listener */ - if ((smtp_open(ctl) == -1)) - { - error(0, errno, "SMTP connect to %s failed", - ctl->smtphost ? ctl->smtphost : "localhost"); - free(headers); - free_str_list(&xmit_names); - return(PS_SMTP); - } - - /* - * Compute ESMTP options. - */ - options[0] = '\0'; - if (ctl->server.esmtp_options & ESMTP_8BITMIME) { - if (ctl->pass8bits || (mimemsg & MSG_IS_8BIT)) - strcpy(options, " BODY=8BITMIME"); - else if (mimemsg & MSG_IS_7BIT) - strcpy(options, " BODY=7BIT"); - } - - if ((ctl->server.esmtp_options & ESMTP_SIZE) && reallen > 0) - sprintf(options + strlen(options), " SIZE=%ld", reallen); - - /* - * Try to get the SMTP listener to take the Return-Path - * address as MAIL FROM . If it won't, fall back on the - * calling-user ID. This won't affect replies, which use the - * header From address anyway. - * - * RFC 1123 requires that the domain name part of the - * MAIL FROM address be "canonicalized", that is a - * FQDN or MX but not a CNAME. We'll assume the From - * header is already in this form here (it certainly - * is if rewrite is on). RFC 1123 is silent on whether - * a nonexistent hostname part is considered canonical. - * - * This is a potential problem if the MTAs further upstream - * didn't pass canonicalized From/Return-Path lines, *and* the - * local SMTP listener insists on them. - * - * None of these error conditions generates bouncemail. Comments - * below explain for each case why this is so. - */ - ap = (return_path[0]) ? return_path : user; - if (SMTP_from(ctl->smtp_socket, ap, options) != SM_OK) - { - int smtperr = atoi(smtp_response); - - if (str_find(&ctl->antispam, smtperr)) - { - /* - * SMTP listener explicitly refuses to deliver mail - * coming from this address, probably due to an - * anti-spam domain exclusion. Respect this. Don't - * try to ship the message, and don't prevent it from - * being deleted. Typical values: - * - * 501 = exim's old antispam response - * 550 = exim's new antispam response (temporary) - * 553 = sendmail 8.8.7's generic REJECT - * 571 = sendmail's "unsolicited email refused" - * - * We don't send bouncemail on antispam failures because - * we don't want the scumbags to know the address is even - * valid. - */ - SMTP_rset(ctl->smtp_socket); /* required by RFC1870 */ - free(headers); - free_str_list(&xmit_names); - return(PS_REFUSED); - } - - /* - * Suppress error message only if the response specifically - * meant `excluded for policy reasons'. We *should* see - * an error when the return code is less specific. - */ - if (smtperr >= 400) - error(0, -1, "SMTP error: %s", smtp_response); - - switch (smtperr) - { - case 452: /* insufficient system storage */ - /* - * Temporary out-of-queue-space condition on the - * ESMTP server. Don't try to ship the message, - * and suppress deletion so it can be retried on - * a future retrieval cycle. - * - * Bouncemail *might* be appropriate here as a delay - * notification. But it's not really necessary because - * this is not an actual failure, we're very likely to be - * able to recover on the next cycle. - */ - SMTP_rset(ctl->smtp_socket); /* required by RFC1870 */ - free(headers); - free_str_list(&xmit_names); - return(PS_TRANSIENT); - - case 552: /* message exceeds fixed maximum message size */ - case 553: /* invalid sending domain */ - /* - * Permanent no-go condition on the - * ESMTP server. Don't try to ship the message, - * and allow it to be deleted. - * - * Bouncemail would be appropriate for 552, but in these - * latter days 553 usually means a spammer is trying to - * cover his tracks. We'd rather deny the scumbags any - * feedback that the address is valid. - */ - SMTP_rset(ctl->smtp_socket); /* required by RFC1870 */ - free(headers); - free_str_list(&xmit_names); - return(PS_REFUSED); - - default: /* retry with postmaster's address */ - if (SMTP_from(ctl->smtp_socket,run.postmaster,options)!=SM_OK) - { - error(0, -1, "SMTP error: %s", smtp_response); - free(headers); - free_str_list(&xmit_names); - return(PS_SMTP); /* should never happen */ - } - } - } - - /* - * Now list the recipient addressees - * - * RFC 1123 requires that the domain name part of the - * RCPT TO address be "canonicalized", that is a FQDN - * or MX but not a CNAME. Some listeners (like exim) - * enforce this. - */ - destaddr = ctl->smtpaddress ? ctl->smtpaddress : ( ctl->smtphost ? ctl->smtphost : "localhost"); - - for (idp = xmit_names; idp; idp = idp->next) - if (idp->val.status.mark == XMIT_ACCEPT) - { - if (strchr(idp->id, '@')) - strcpy(addr, idp->id); - else -#ifdef HAVE_SNPRINTF - snprintf(addr, sizeof(addr)-1, "%s@%s", idp->id, destaddr); -#else - sprintf(addr, "%s@%s", idp->id, destaddr); -#endif /* HAVE_SNPRINTF */ - - if (SMTP_rcpt(ctl->smtp_socket, addr) == SM_OK) - good_addresses++; - else - { - bad_addresses++; - idp->val.status.mark = XMIT_ANTISPAM; - error(0, 0, - "SMTP listener doesn't like recipient address `%s'", - addr); - } - } - if (!good_addresses) - { -#ifdef HAVE_SNPRINTF - snprintf(addr, sizeof(addr)-1, "%s@%s", run.postmaster, destaddr); -#else - sprintf(addr, "%s@%s", run.postmaster, destaddr); -#endif /* HAVE_SNPRINTF */ - - if (SMTP_rcpt(ctl->smtp_socket, addr) != SM_OK) - { - error(0, 0, "can't even send to %s!", run.postmaster); - free(headers); - free_str_list(&xmit_names); - return(PS_SMTP); - } - } - - /* tell it we're ready to send data */ - SMTP_data(ctl->smtp_socket); } n = 0; @@ -1438,12 +1503,7 @@ int num; /* index of message */ if (n == -1) { error(0, errno, "writing RFC822 headers"); - if (ctl->mda) - { - if (sinkfp) - pclose(sinkfp); - signal(SIGCHLD, sigchld); - } + release_sink(ctl); free(headers); free_str_list(&xmit_names); return(PS_IOERR); @@ -1504,8 +1564,7 @@ int num; /* index of message */ strcat(errmsg, "\n"); /* ship out the error line */ - if (sinkfp) - stuffline(ctl, errmsg); + stuffline(ctl, errmsg); } /* issue the delimiter line */ @@ -1539,12 +1598,7 @@ flag forward; /* TRUE to forward */ if ((linelen = SockRead(sock, inbufp, sizeof(buf)-4-(inbufp-buf)))==-1) { set_timeout(0); - if (ctl->mda) - { - if (sinkfp) - pclose(sinkfp); - signal(SIGCHLD, sigchld); - } + release_sink(ctl); return(PS_SOCKET); } set_timeout(0); @@ -1605,12 +1659,7 @@ flag forward; /* TRUE to forward */ if (n < 0) { error(0, errno, "writing message text"); - if (ctl->mda) - { - if (sinkfp) - pclose(sinkfp); - signal(SIGCHLD, sigchld); - } + release_sink(ctl); return(PS_IOERR); } else if (outlevel == O_VERBOSE) @@ -1725,6 +1774,110 @@ const char *canonical; /* server name */ } #endif /* KERBEROS_V5 */ +static void clean_skipped_list(struct idlist **skipped_list) +/* struct "idlist" contains no "prev" ptr; we must remove unused items first */ +{ + struct idlist *current=NULL, *prev=NULL, *tmp=NULL, *head=NULL; + prev = current = head = *skipped_list; + + if (!head) + return; + do + { + /* if item has no reference, remove it */ + if (current && current->val.status.mark == 0) + { + if (current == head) /* remove first item (head) */ + { + head = current->next; + if (current->id) free(current->id); + free(current); + prev = current = head; + } + else /* remove middle/last item */ + { + tmp = current->next; + prev->next = tmp; + if (current->id) free(current->id); + free(current); + current = tmp; + } + } + else /* skip this item */ + { + prev = current; + current = current->next; + } + } while(current); + + *skipped_list = head; +} + +static void send_warning(struct query *ctl, + struct idlist **skipped_list, const char *user) +/* send warning mail with skipped msg; reset msg count when user notified */ +{ + int size, nbr; + int msg_to_send = FALSE; + FILE *tmpfile = NULL; + struct idlist *head=NULL, *current=NULL; + int max_warning_poll_count, good, bad; + char buf[100]; + + head = *skipped_list; + if (!head) + return; + + /* don't start a notification message unless we need to */ + for (current = head; current; current = current->next) + if (current->val.status.num == 0 && current->val.status.mark) + msg_to_send = TRUE; + if (!msg_to_send) + return; + + /* + * There's no good way to recover if we can't send notification mail, + * but it's not a disaster, either, since the skipped mail will not + * be deleted. + * + * We give a null address list here because we actually *want* + * this message to go to run.postmaster. + */ + if (open_sink(ctl, "FETCHMAIL-DAEMON", "localhost", NULL, 0, &good, &bad)) + return; + + /* stuffline() requires its input to be writeable for CR stripping */ +#define OVERHD "Subject: Fetchmail WARNING.\r\n\r\nThe following oversized messages remain on the mail server:\n\r\n" + strcpy(buf, OVERHD); + stuffline(ctl, buf); + + if (run.poll_interval == 0) + max_warning_poll_count = 0; + else + max_warning_poll_count = ctl->warnings/run.poll_interval; + + /* parse list of skipped msg, adding items to the mail */ + for (current = head; current; current = current->next) + { + if (current->val.status.num == 0 && current->val.status.mark) + { + nbr = current->val.status.mark; + size = atoi(current->id); + sprintf(buf, + "\t%d msg of %d bytes skipped by fetchmail.\n", + nbr, size); + stuffline(ctl, buf); + } + current->val.status.num++; + current->val.status.mark = 0; + + if (current->val.status.num >= max_warning_poll_count) + current->val.status.num = 0; + } + + close_sink(ctl, TRUE); +} + int do_protocol(ctl, proto) /* retrieve messages from server using given protocol method table */ struct query *ctl; /* parsed options with merged-in defaults */ @@ -1732,7 +1885,15 @@ const struct method *proto; /* protocol method table */ { int ok, js, sock = -1; char *msg; + char localuser[32]; void (*sigsave)(); + struct idlist *current=NULL, *prev=NULL, *next=NULL, *head=NULL, *tmp=NULL; + + strcpy(localuser, "root"); + if (ctl->localnames) + { + strcpy(localuser, ctl->localnames->id); + } #ifndef KERBEROS_V4 if (ctl->server.preauthenticate == A_KERBEROS_V4) @@ -1806,12 +1967,11 @@ const struct method *proto; /* protocol method table */ else error(0, 0, "timeout after %d seconds.", ctl->server.timeout); + release_sink(ctl); if (ctl->smtp_socket != -1) close(ctl->smtp_socket); if (sock != -1) SockClose(sock); - if (sinkfp) - pclose(sinkfp); ok = PS_ERROR; } else @@ -2062,8 +2222,9 @@ const struct method *proto; /* protocol method table */ { flag toolarge = NUM_NONZERO(ctl->limit) && msgsizes && (msgsizes[num-1] > ctl->limit); + flag oldmsg = (protocol->is_old && (protocol->is_old)(sock,ctl,num)); flag fetch_it = !toolarge - && (ctl->fetchall || force_retrieval || !(protocol->is_old && (protocol->is_old)(sock,ctl,num))); + && (ctl->fetchall || force_retrieval || !oldmsg); flag suppress_delete = FALSE; flag suppress_forward = FALSE; flag suppress_readbody = FALSE; @@ -2094,9 +2255,55 @@ const struct method *proto; /* protocol method table */ if (outlevel > O_SILENT) { error_build("skipping message %d", num); - if (toolarge) + if (toolarge && !check_only) + { + char size[32]; + int cnt, bytesz = msgsizes[num-1]; +#ifdef POP3_ENABLE + /* convert sz to string */ + sprintf(size, "%d", msgsizes[num-1]); + + /* build a list of skipped messages + * val.id = size of msg (string cnvt) + * val.status.num = warning_poll_count + * val.status.mask = nbr of msg this size + */ + + current = ctl->skipped; + + /* initialise warning_poll_count to the + * current value so that all new msg will + * be included in the next mail + */ + cnt = current? current->val.status.num : 0; + + /* if entry exists, increment the count */ + if (current && + str_in_list(¤t, size, FALSE)) + { + for ( ; current; + current = current->next) + { + if (strcmp(current->id, size) == 0) + { + current->val.status.mark++; + break; + } + } + } + /* otherwise, create a new entry */ + /* initialise with current poll count */ + else + { + tmp = save_str(&ctl->skipped, size, 1); + tmp->val.status.num = cnt; + } + +#endif /* POP3_ENABLE */ + error_build(" (oversized, %d bytes)", msgsizes[num-1]); + } } } else @@ -2129,9 +2336,6 @@ const struct method *proto; /* protocol method table */ error_build(" "); } - /* later we'll test for this before closing */ - sinkfp = (FILE *)NULL; - /* * Read the message headers and ship them to the * output sink. @@ -2244,34 +2448,11 @@ const struct method *proto; /* protocol method table */ } /* end-of-message processing starts here */ - - if (ctl->mda) + if (!close_sink(ctl, !suppress_forward)) { - int rc; - - /* close the delivery pipe, we'll reopen before next message */ - if (sinkfp) - rc = pclose(sinkfp); - else - rc = 0; - signal(SIGCHLD, sigchld); - if (rc) - { - error(0, -1, "MDA exited abnormally or returned nonzero status"); - goto cleanUp; - } + ctl->errcount++; + suppress_delete = TRUE; } - else if (!suppress_forward) - { - /* write message terminator */ - if (SMTP_eom(ctl->smtp_socket) != SM_OK) - { - error(0, -1, "SMTP listener refused delivery"); - ctl->errcount++; - suppress_delete = TRUE; - } - } - fetches++; } @@ -2323,6 +2504,12 @@ const struct method *proto; /* protocol method table */ if (NUM_NONZERO(ctl->fetchlimit) && ctl->fetchlimit <= fetches) goto no_error; } + + if (!check_only && ctl->skipped) + { + clean_skipped_list(&ctl->skipped); + send_warning(ctl, &ctl->skipped, localuser); + } } } while /* diff --git a/fetchmail-features.html b/fetchmail-features.html index 0d70f3cd..c984bb05 100644 --- a/fetchmail-features.html +++ b/fetchmail-features.html @@ -10,7 +10,7 @@ <table width="100%" cellpadding=0><tr> <td width="30%">Back to <a href="index.html">Fetchmail Home Page</a> <td width="30%" align=center>To <a href="/~esr/sitemap.html">Site Map</a> -<td width="30%" align=right>$Date: 1998/10/09 22:06:42 $ +<td width="30%" align=right>$Date: 1998/10/16 13:01:04 $ </table> <HR> @@ -18,9 +18,12 @@ <H2>Since 4.0:</H2> <UL> +<LI> The --limit option can now be used in daemon mode, with oversized-message +notifications being mailed to the calling user. + <LI> Configurable support for the <a href="http://www.demon.net/services/mail/sdps-tech.html">SDPS extensions</a> -in <a href="http://www.demon.net/">www.demon.net</a>'s POP3 service.<P> +in <a href="http://www.demon.net/">www.demon.net</a>'s POP3 service. <LI> There is now an interactive GUI fetchmail configurator, fetchmailconf. @@ -152,7 +155,7 @@ get-mail, gwpop, pimp-1.0, pop-perl5-1.2, popc, popmail-1.6 and upop.<P> <table width="100%" cellpadding=0><tr> <td width="30%">Back to <a href="index.html">Fetchmail Home Page</a> <td width="30%" align=center>To <a href="/~esr/sitemap.html">Site Map</a> -<td width="30%" align=right>$Date: 1998/10/09 22:06:42 $ +<td width="30%" align=right>$Date: 1998/10/16 13:01:04 $ </table> <P><ADDRESS>Eric S. Raymond <A HREF="mailto:esr@thyrsus.com"><esr@snark.thyrsus.com></A></ADDRESS> diff --git a/fetchmail.c b/fetchmail.c index a9d737c9..6a53499b 100644 --- a/fetchmail.c +++ b/fetchmail.c @@ -765,6 +765,7 @@ static void optmerge(struct query *h2, struct query *h1, int force) FLAG_MERGE(dropstatus); FLAG_MERGE(mimedecode); FLAG_MERGE(limit); + FLAG_MERGE(warnings); FLAG_MERGE(fetchlimit); FLAG_MERGE(batchlimit); FLAG_MERGE(expunge); @@ -786,6 +787,7 @@ static int load_params(int argc, char **argv, int optind) def_opts.server.protocol = P_AUTO; def_opts.server.timeout = CLIENT_TIMEOUT; + def_opts.warnings = WARNING_INTERVAL; def_opts.remotename = user; def_opts.expunge = 1; @@ -883,13 +885,6 @@ static int load_params(int argc, char **argv, int optind) if (!ctl->smtphunt) save_str(&ctl->smtphunt, fetchmailhost, FALSE); - /* keep lusers from shooting themselves in the foot :-) */ - if (run.poll_interval && ctl->limit) - { - fprintf(stderr,"fetchmail: you'd never see large messages!\n"); - exit(PS_SYNTAX); - } - /* if `user' doesn't name a real local user, try to run as root */ if ((pw = getpwnam(user)) == (struct passwd *)NULL) ctl->uid = 0; @@ -1318,10 +1313,18 @@ void dump_params (struct runctl *runp, struct query *querylist, flag implicit) ctl->dropstatus ? "discarded" : "kept", ctl->dropstatus ? "on" : "off"); if (NUM_NONZERO(ctl->limit)) - printf(" Message size limit is %d bytes (--limit %d).\n", - ctl->limit, ctl->limit); - else if (outlevel == O_VERBOSE) - printf(" No message size limit (--limit 0).\n"); + { + if (NUM_NONZERO(ctl->limit)) + printf(" Message size limit is %d bytes (--limit %d).\n", + ctl->limit, ctl->limit); + else if (outlevel == O_VERBOSE) + printf(" No message size limit (--limit 0).\n"); + if (run.poll_interval > 0) + printf(" Message size warning interval is %d seconds (--warnings %d).\n", + ctl->warnings, ctl->warnings); + else if (outlevel == O_VERBOSE) + printf(" Size warnings on every poll (--warnings 0).\n"); + } if (NUM_NONZERO(ctl->fetchlimit)) printf(" Received-message limit is %d (--fetchlimit %d).\n", ctl->fetchlimit, ctl->fetchlimit); diff --git a/fetchmail.h b/fetchmail.h index d7bd4142..57678a5e 100644 --- a/fetchmail.h +++ b/fetchmail.h @@ -193,10 +193,14 @@ struct query flag dropstatus; /* if TRUE, drop Status lines in mail */ flag mimedecode; /* if TRUE, decode MIME-coded headers/coded printable*/ int limit; /* limit size of retrieved messages */ + int warnings; /* size warning interval */ int fetchlimit; /* max # msgs to get in single poll */ int batchlimit; /* max # msgs to pass in single SMTP session */ int expunge; /* max # msgs to pass between expunges */ + /* holds the info on the messages skipped on the mail server */ + struct idlist *skipped; + struct idlist *oldsaved, *newsaved; /* internal use */ diff --git a/fetchmail.man b/fetchmail.man index 2731343a..e486905a 100644 --- a/fetchmail.man +++ b/fetchmail.man @@ -260,8 +260,8 @@ You can force mail to be passed to an MDA directly (rather than forwarded to port 25) with the -mda or -m option. If \fIfetchmail\fR is running as root, it sets its userid to that of the target user while delivering mail through an MDA. Some possible MDAs are -"/usr/sbin/sendmail -oem", "/usr/lib/sendmail -oem", -"/usr/bin/formail", and "/usr/bin/deliver". Local delivery addresses +"/usr/sbin/sendmail -oem $USER", "/usr/bin/procmail -d $USER" +and "/usr/bin/deliver". Local delivery addresses will be inserted into the MDA command wherever you place a %T; the mail message's From address will be inserted where you place an %F. Do \fInot\fR use an MDA invocation like @@ -275,11 +275,24 @@ down upon your head. Takes a maximum octet size argument. Messages larger than this size will not be fetched, not be marked seen, and will be left on the server (in foreground sessions, the progress messages will note that -they are "oversized"). An explicit --limit of 0 overrides any limits set -in your run control file. This option is intended for those needing to -strictly control fetch time in interactive mode. It may not be used -with daemon mode, as users would never receive a notification that -messages were waiting. This option does not work with ETRN. +they are "oversized"). An explicit --limit of 0 overrides any limits +set in your run control file. This option is intended for those +needing to strictly control fetch time due to expensive and variable +phone rates. In daemon mode, oversize notifications are mailed to the +calling user (see the --warnings option). This option does not work +with ETRN. +.TP +.B \-w, --warnings +(Keyword: warnings) +Takes an interval in seconds. When you call +.I fetchmail +with a `limit' option in daemon mode, this controls the interval at +which warnings about oversized messages are mailed to the calling user +(or the user specified by the `postmaster' option). One such +notification is always mailed at the end of the the first poll that +the oversized message is detected. Thereafter, renotification is +suppressed until after the warning interval elapses (it will take +place at the end of the first following poll). .TP .B -b, --batchlimit (Keyword: batchlimit) @@ -292,7 +305,7 @@ after receiving the message terminator, some SMTP listeners are not so prompt. MTAs like \fIqmail\fR(8) and \fIsmail\fR(8) may wait till the delivery socket is shut down to deliver. This may produce annoying delays when -.IR fetchmail (8) +.I fetchmail is processing very large batches. Setting the batch limit to some nonzero size will prevent these delays. This option does not work with ETRN. @@ -1000,6 +1013,9 @@ T} limit -l T{ Set message size limit T} +warnings -l T{ +Set message size warning interval +T} batchlimit -b T{ Max # messages to fetch in single connect T} diff --git a/fetchmailconf b/fetchmailconf index 6b7f0cd4..22fddfd5 100755 --- a/fetchmailconf +++ b/fetchmailconf @@ -194,6 +194,7 @@ class User: self.mimedecode = FALSE # Undo MIME armoring self.dropstatus = FALSE # Drop incoming Status lines self.limit = 0 # Message size limit + self.warnings = 0 # Size warning interval self.fetchlimit = 0 # Max messages fetched per batch self.batchlimit = 0 # Max message forwarded per batch self.expunge = 1 # Interval between expunges (IMAP) @@ -217,6 +218,7 @@ class User: ('mimedecode', 'Boolean'), ('dropstatus', 'Boolean'), ('limit', 'Int'), + ('warnings', 'Int'), ('fetchlimit', 'Int'), ('batchlimit', 'Int'), ('expunge', 'Int')) @@ -261,6 +263,8 @@ class User: str = str + flag2str(self.dropstatus, 'dropstatus') if self.limit != UserDefaults.limit: str = str + " limit " + `self.limit` + if self.warnings != UserDefaults.warnings: + str = str + " warnings " + `self.warnings` if self.fetchlimit != UserDefaults.fetchlimit: str = str + " fetchlimit " + `self.fetchlimit` if self.batchlimit != UserDefaults.batchlimit: @@ -1203,6 +1207,8 @@ class UserEdit(Frame, MyWidget): Label(limwin, text="Resource Limits").pack(side=TOP) LabeledEntry(limwin, 'Message size limit:', self.limit, '30').pack(side=TOP, fill=X) + LabeledEntry(limwin, 'Size warning interval:', + self.warnings, '30').pack(side=TOP, fill=X) LabeledEntry(limwin, 'Max messages to fetch per poll:', self.fetchlimit, '30').pack(side=TOP, fill=X) LabeledEntry(limwin, 'Max messages to forward per poll:', @@ -69,10 +69,11 @@ #define LA_MONITOR 41 #define LA_CONFIGDUMP 42 #define LA_YYDEBUG 43 +#define LA_WARNINGS 44 /* options still left: CgGhHjJoORwWxXYz */ static const char *shortoptions = - "?Vcsvd:NqL:f:i:p:UP:A:t:E:Q:u:akKFnl:r:S:Z:b:B:e:m:T:I:M:y"; + "?Vcsvd:NqL:f:i:p:UP:A:t:E:Q:u:akKFnl:r:S:Z:b:B:e:m:T:I:M:yw:"; static const struct option longoptions[] = { /* this can be const because all flag fields are 0 and will never get set */ @@ -134,6 +135,8 @@ static const struct option longoptions[] = { {"yydebug", no_argument, (int *) 0, LA_YYDEBUG }, + {"warnings", required_argument, (int *) 0, LA_WARNINGS }, + {(char *) 0, no_argument, (int *) 0, 0 } }; @@ -495,6 +498,12 @@ struct query *ctl; /* option record to be initialized */ yydebug = TRUE; break; + case 'w': + case LA_WARNINGS: + c = xatoi(optarg, &errflag); + ctl->warnings = NUM_VALUE(c); + break; + case LA_CONFIGDUMP: configdump = TRUE; break; @@ -557,6 +566,7 @@ struct query *ctl; /* option record to be initialized */ fputs(" -F, --flush delete old messages from server\n", stderr); fputs(" -n, --norewrite don't rewrite header addresses\n", stderr); fputs(" -l, --limit don't fetch messages over given size\n", stderr); + fputs(" -w, --warnings interval between warning mail notification\n", stderr); #if NET_SECURITY fputs(" -T, --netsec set IP security request\n", stderr); @@ -28,6 +28,7 @@ daemon { return DAEMON; } syslog { return SYSLOG; } invisible { return INVISIBLE; } postmaster { return POSTMASTER; } +warnings { return WARNINGS; } defaults { return DEFAULTS; } server { return POLL; } @@ -64,7 +64,7 @@ extern char * yytext; %token NETSEC INTERFACE MONITOR %token IS HERE THERE TO MAP WILDCARD %token BATCHLIMIT FETCHLIMIT EXPUNGE -%token SET LOGFILE DAEMON SYSLOG IDFILE INVISIBLE POSTMASTER +%token SET LOGFILE DAEMON SYSLOG IDFILE INVISIBLE POSTMASTER WARNINGS %token <proto> PROTO %token <sval> STRING %token <number> NUMBER @@ -319,6 +319,7 @@ user_option : TO localnames HERE | NO MIMEDECODE {current.mimedecode = FLAG_FALSE;} | LIMIT NUMBER {current.limit = NUM_VALUE($2);} + | WARNINGS NUMBER {current.warnings = NUM_VALUE($2);} | FETCHLIMIT NUMBER {current.fetchlimit = NUM_VALUE($2);} | BATCHLIMIT NUMBER {current.batchlimit = NUM_VALUE($2);} | EXPUNGE NUMBER {current.expunge = NUM_VALUE($2);} @@ -10,3 +10,6 @@ /* default timeout period in seconds if server connection dies */ #define CLIENT_TIMEOUT 300 +/* default skipped message warning interval in seconds */ +#define WARNING_INTERVAL 3600 + @@ -76,7 +76,7 @@ void initialize_saved_lists(struct query *hostlist, const char *idfile) /* make sure lists are initially empty */ for (ctl = hostlist; ctl; ctl = ctl->next) - ctl->oldsaved = ctl->newsaved = (struct idlist *)NULL; + ctl->skipped = ctl->oldsaved = ctl->newsaved = (struct idlist *)NULL; /* let's get stored message UIDs from previous queries */ if ((tmpfp = fopen(idfile, "r")) != (FILE *)NULL) { |