/* Copyright 1996 by Eric S. Raymond * All rights reserved. * For license terms, see the file COPYING in this directory. */ /*********************************************************************** module: driver.c project: fetchmail programmer: Eric S. Raymond description: Generic driver for mail fetch method protocols ***********************************************************************/ #include #include #include #include #include #ifdef HAVE_RRESVPORT_H #include #endif /* HAVE_RRESVPORT_H */ #include "socket.h" #include "fetchmail.h" #include "smtp.h" static struct method *protocol; #define SMTP_PORT 25 /* standard SMTP service port */ char tag[TAGLEN]; static int tagnum; #define GENSYM (sprintf(tag, "a%04d", ++tagnum), tag) /********************************************************************* function: description: hack message headers so replies will work properly arguments: after where to put the hacked header before header to hack host name of the pop header return value: none. calls: none. *********************************************************************/ static void reply_hack(buf, host) /* hack local mail IDs -- code by Eric S. Raymond 20 Jun 1996 */ char *buf; const char *host; { const char *from; int state = 0; char mycopy[POPBUFSIZE+1]; if (strncmp("From: ", buf, 6) && strncmp("To: ", buf, 4) && strncmp("Reply-", buf, 6) && strncmp("Cc: ", buf, 4) && strncmp("Bcc: ", buf, 5)) { return; } strcpy(mycopy, buf); for (from = mycopy; *from; from++) { switch (state) { case 0: /* before header colon */ if (*from == ':') state = 1; break; case 1: /* we've seen the colon, we're looking for addresses */ if (*from == '"') state = 2; else if (*from == '(') state = 3; else if (*from == '<' || isalnum(*from)) state = 4; break; case 2: /* we're in a quoted human name, copy and ignore */ if (*from == '"') state = 1; break; case 3: /* we're in a parenthesized human name, copy and ignore */ if (*from == ')') state = 1; break; case 4: /* the real work gets done here */ /* * We're in something that might be an address part, * either a bare unquoted/unparenthesized text or text * enclosed in <> as per RFC822. */ /* if the address part contains an @, don't mess with it */ if (*from == '@') state = 5; /* If the address token is not properly terminated, ignore it. */ else if (*from == ' ' || *from == '\t') state = 1; /* * On proper termination with no @, insert hostname. * Case '>' catches <>-enclosed mail IDs. Case ',' catches * comma-separated bare IDs. Cases \r and \n catch the case * of a single ID alone on the line. */ else if (strchr(">,\r\n", *from)) { strcpy(buf, "@"); strcat(buf, host); buf += strlen(buf); state = 1; } /* everything else, including alphanumerics, just passes through */ break; case 5: /* we're in a remote mail ID, no need to append hostname */ if (*from == '>' || *from == ',' || isspace(*from)) state = 1; break; } /* a
../design-notes.html
("From: ", bufp, 6)) fromhdr = bufp; else if (!strncmp("To: ", bufp, 4)) tohdr = bufp; else if (!strncmp("Cc: ", bufp, 4)) cchdr = bufp; else if (!strncmp("Bcc: ", bufp, 5)) bcchdr = bufp; goto skipwrite; } else if (headers) { char *cp; switch (output) { case TO_SMTP: if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK) return(PS_SMTP); #ifdef SMTP_RESEND /* * This is what we'd do if fetchmail were a real MDA * a la sendmail -- crack all the destination headers * and send to every address we can reach via SMTP. */ if ((cp = nxtaddr(tohdr)) != (char *)NULL) do { if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE) return(PS_SMTP); } while (cp = nxtaddr(NULL)); if ((cp = nxtaddr(cchdr)) != (char *)NULL) do { if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE) return(PS_SMTP); } while (cp = nxtaddr(NULL)); if ((cp = nxtaddr(bcchdr)) != (char *)NULL) do { if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE) return(PS_SMTP); } while (cp = nxtaddr(NULL)); #else /* * Since we're really only fetching mail for one user * per host query, we can be simpler */ if (SMTP_rcpt(mboxfd, popuser) == SM_UNRECOVERABLE) return(PS_SMTP); #endif /* SMTP_RESEND */ SMTP_data(mboxfd); if (outlevel == O_VERBOSE) fputs("SMTP> ", stderr); break; case TO_FOLDER: case TO_STDOUT: if (unixfrom) (void) strcpy(fromBuf, unixfrom); else { now = time(NULL); if (fromhdr && (cp = nxtaddr(fromhdr))) sprintf(fromBuf, "From %s %s", cp, ctime(&now)); else sprintf(fromBuf, "From POPmail %s",ctime(&now)); } if (write(mboxfd,fromBuf,strlen(fromBuf)) < 0) { perror("gen_readmsg: writing From header"); return(PS_IOERR); } break; case TO_MDA: break; } /* change continuation markers back to regular newlines */ for (cp = headers; cp < headers + oldlen; cp++) if (*cp == '\r') *cp = '\n'; if (write(mboxfd,headers,oldlen) < 0) { free(headers); headers = NULL; perror("gen_readmsg: writing RFC822 headers"); return(PS_IOERR); } else if (outlevel == O_VERBOSE) fputs("#", stderr); free(headers); headers = NULL; } /* write this line to the file */ if (write(mboxfd,bufp,strlen(bufp)) < 0) { perror("gen_readmsg: writing message text"); return(PS_IOERR); } else if (outlevel == O_VERBOSE) fputc('*', stderr); skipwrite:; sizeticker += strlen(bufp); while (sizeticker >= SIZETICKER) { if (outlevel > O_SILENT && outlevel < O_VERBOSE && mboxfd != 1) fputc('.',stderr); sizeticker -= SIZETICKER; } lines++; } if (outlevel == O_VERBOSE) fputc('\n', stderr); /* write message terminator, if any */ switch (output) { case TO_SMTP: if (SMTP_eom(mboxfd) != SM_OK) return(PS_SMTP); break; case TO_FOLDER: case TO_STDOUT: /* The server may not write the extra newline required by the Unix mail folder format, so we write one here just in case */ if (write(mboxfd,"\n",1) < 0) { perror("gen_readmsg: writing terminator"); return(PS_IOERR); } break; case TO_MDA: /* The mail delivery agent may require a terminator. Write it if it has been defined */ #ifdef BINMAIL_TERM if (write(mboxfd,BINMAIL_TERM,strlen(BINMAIL_TERM)) < 0) { perror("gen_readmsg: writing terminator"); return(PS_IOERR); } #endif /* BINMAIL_TERM */ break; } /* finish up display output */ if (outlevel == O_VERBOSE) fprintf(stderr,"(%d lines of message content)\n",lines); else if (outlevel > O_SILENT && mboxfd != 1) fputs("\n",stderr); else ; return(0); } /********************************************************************* function: do_protocol description: retrieve messages from the specified mail server using a given set of methods arguments: queryctl fully-specified options (i.e. parsed, defaults invoked, etc). proto protocol method pointer return value: exit code from the set of PS_.* constants defined in fetchmail.h calls: globals: reads outlevel. *********************************************************************/ int do_protocol(queryctl, proto) struct hostrec *queryctl; struct method *proto; { int ok, len; int mboxfd = -1; char buf [POPBUFSIZE+1], host[HOSTLEN+1]; int socket; #ifdef HAVE_RRESVPORT_H int privport = -1; #endif /* HAVE_RRESVPORT_H */ int first, num, count; tagnum = 0; protocol = proto; #ifdef HAVE_RRESVPORT_H /* * If we're trying to bind to a reserved port on the remote system, * do likewise on the local one so the remote will know we're privileged. * (This is most likely to happen in connection with RPOP.) */ if (queryctl->port < IPPORT_RESERVED) { ok = IPPORT_RESERVED - 1; if ((privport = rresvport(&ok)) == -1) { perror("fetchmail, binding to reserved port"); return(PS_SOCKET); } } #endif /* HAVE_RRESVPORT_H */ /* open a socket to the mail server */ if ((socket = Socket(queryctl->servername, queryctl->port ? queryctl->port : protocol->port))<0) { perror("fetchmail, connecting to host"); ok = PS_SOCKET; goto closeUp; } /* accept greeting message from mail server */ ok = (protocol->parse_response)(buf, socket); if (ok != 0) goto cleanUp; /* try to get authorized to fetch mail */ ok = (protocol->getauth)(socket, queryctl, buf); if (ok == PS_ERROR) ok = PS_AUTHFAIL; if (ok != 0) goto cleanUp; /* compute count and first, and get UID list if possible */ if ((*protocol->getrange)(socket, queryctl, &count, &first) != 0) goto cleanUp; /* show user how many messages we'll be downloading */ if (outlevel > O_SILENT && outlevel < O_VERBOSE) if (count == 0) fprintf(stderr, "No mail from %s\n", queryctl->servername); else if (first > 1) fprintf(stderr, "%d message%s from %s, %d new messages.\n", count, count > 1 ? "s" : "", queryctl->servername, count - first + 1); else fprintf(stderr, "%d %smessage%s from %s.\n", count, ok ? "" : "new ", count > 1 ? "s" : "", queryctl->servername); if (count > 0) { /* * We expect the open of the output sink to always succeed. * Therefore, defer it until here so the typical case (no * mail waiting) is just a bit cheaper and faster. */ if (queryctl->output == TO_FOLDER || queryctl->output == TO_STDOUT) { if ((mboxfd = openuserfolder(queryctl)) < 0) { ok = PS_IOERR; goto cleanUp; } } else if (queryctl->output == TO_SMTP) { ok = PS_SMTP; if ((mboxfd = Socket(queryctl->smtphost, SMTP_PORT)) < 0) goto cleanUp; /* eat the greeting message */ if (SMTP_ok(mboxfd, NULL) != SM_OK) { close(mboxfd); mboxfd = -1; goto cleanUp; } /* make it look like mail is coming from the server */ if (SMTP_helo(mboxfd,queryctl->servername) != SM_OK) { close(mboxfd); mboxfd = -1; goto cleanUp; } } /* read, forward, and delete messages */ for (num = queryctl->flush ? 1 : first; num <= count; num++) { if (queryctl->flush && num < first && !queryctl->fetchall) ok = 0; /* retrieval suppressed */ else { /* we may want to reject this message based on its UID */ if (!queryctl->fetchall && *protocol->is_old) if ((*protocol->is_old)(socket, queryctl, num)) continue; /* request a message */ (*protocol->fetch)(socket, num, linelimit, &len); if (outlevel == O_VERBOSE) if (protocol->delimited) fprintf(stderr, "fetching message %d (delimited)\n", num); else fprintf(stderr, "fetching message %d (%d bytes)\n", num, len); /* open the mail pipe now if we're using an MDA */ if (queryctl->output == TO_MDA) { ok = (mboxfd = openmailpipe(queryctl)) < 0 ? -1 : 0; if (ok != 0) goto cleanUp; } /* read the message and ship it to the output sink */ ok = gen_readmsg(socket, mboxfd, len, protocol->delimited, queryctl->localname, queryctl->servername, queryctl->output, !queryctl->norewrite); /* close the mail pipe, we'll reopen before next message */ if (queryctl->output == TO_MDA) { ok = closemailpipe(mboxfd); if (ok != 0) goto cleanUp; } /* tell the server we got it OK and resynchronize */ if (protocol->trail) (*protocol->trail)(socket, queryctl, num); if (ok != 0) goto cleanUp; } /* maybe we delete this message now? */ if (protocol->delete) { if ((num < first && queryctl->flush) || !queryctl->keep) { if (outlevel > O_SILENT && outlevel < O_VERBOSE) fprintf(stderr,"flushing message %d\n", num); ok = (protocol->delete)(socket, queryctl, num); if (ok != 0) goto cleanUp; } } } /* remove all messages flagged for deletion */ if (!queryctl->keep && protocol->expunge_cmd) { ok = gen_transact(socket, protocol->expunge_cmd); if (ok != 0) goto cleanUp; } ok = gen_transact(socket, protocol->exit_cmd); if (ok == 0) ok = PS_SUCCESS; close(socket); goto closeUp; } else { ok = gen_transact(socket, protocol->exit_cmd); if (ok == 0) ok = PS_NOMAIL; close(socket); goto closeUp; } cleanUp: if (ok != 0 && ok != PS_SOCKET) { gen_transact(socket, protocol->exit_cmd); close(socket); } #ifdef HAVE_RRESVPORT_H if (privport != -1) close(privport); /* no big deal if this fails */ #endif /* HAVE_RRESVPORT_H */ closeUp: if (queryctl->output == TO_FOLDER) { if (closeuserfolder(mboxfd) < 0 && ok == 0) { perror("fetchmail, closing output sink"); ok = PS_IOERR; } } else if (queryctl->output == TO_SMTP && mboxfd > -1) { SMTP_quit(mboxfd); close(mboxfd); } return(ok); } /********************************************************************* function: gen_send description: Assemble command in print style and send to the server arguments: socket socket to which the server is connected. fmt printf-style format return value: none. calls: SockPuts. globals: reads outlevel. *********************************************************************/ void gen_send(socket, fmt, va_alist) int socket; const char *fmt; va_dcl { char buf [POPBUFSIZE+1]; va_list ap; if (protocol->tagged) (void) sprintf(buf, "%s ", GENSYM); else buf[0] = '\0'; va_start(ap); vsprintf(buf + strlen(buf), fmt, ap); va_end(ap); SockPuts(socket, buf); if (outlevel == O_VERBOSE) fprintf(stderr,"> %s\n", buf); } /********************************************************************* function: gen_transact description: Assemble command in print style and send to the server. then accept a protocol-dependent response. arguments: socket socket to which the server is connected. fmt printf-style format return value: none. calls: SockPuts. globals: reads outlevel. *********************************************************************/ int gen_transact(socket, fmt, va_alist) int socket; const char *fmt; va_dcl { int ok; char buf [POPBUFSIZE+1]; va_list ap; if (protocol->tagged) (void) sprintf(buf, "%s ", GENSYM); else buf[0] = '\0'; va_start(ap); vsprintf(buf + strlen(buf), fmt, ap); va_end(ap); SockPuts(socket, buf); if (outlevel == O_VERBOSE) fprintf(stderr,"> %s\n", buf); /* we presume this does its own response echoing */ ok = (protocol->parse_response)(buf,socket); return(ok); }