1 /* Copyright 1996 by Eric S. Raymond
3 * For license terms, see the file COPYING in this directory.
6 /***********************************************************************
9 programmer: Eric S. Raymond
10 description: Generic driver for mail fetch method protocols
12 ***********************************************************************/
18 #if defined(STDC_HEADERS)
21 #if defined(HAVE_UNISTD_H)
31 #include "popclient.h"
34 static struct method *protocol;
36 #define SMTP_PORT 25 /* standard SMTP service port */
40 #define GENSYM (sprintf(tag, "a%04d", ++tagnum), tag)
42 #ifdef HAVE_PROTOTYPES
43 static int gen_readmsg (int socket, int mboxfd, long len, int delimited,
44 char *user, char *host, int topipe, int rewrite);
45 #endif /* HAVE_PROTOTYPES */
47 /*********************************************************************
49 description: retrieve messages from the specified mail server
50 using a given set of methods
53 queryctl fully-specified options (i.e. parsed, defaults invoked,
55 proto protocol method pointer
57 return value: exit code from the set of PS_.* constants defined in
60 globals: reads outlevel.
61 *********************************************************************/
63 int do_protocol(queryctl, proto)
64 struct hostrec *queryctl;
69 char buf [POPBUFSIZE+1], host[HOSTLEN+1];
71 int first,number,count;
76 /* open the output sink, locking it if it is a folder */
77 if (queryctl->output == TO_FOLDER || queryctl->output == TO_STDOUT) {
78 if ((mboxfd = openuserfolder(queryctl)) < 0)
80 } else if (queryctl->output == TO_SMTP) {
81 if ((mboxfd = Socket(queryctl->smtphost, SMTP_PORT)) < 0)
84 /* eat the greeting message */
85 if (SMTP_ok(mboxfd, NULL) != SM_OK) {
91 /* make it look like mail is coming from the server */
92 if (SMTP_helo(mboxfd,queryctl->servername) != SM_OK) {
99 /* open a socket to the mail server */
100 if ((socket = Socket(queryctl->servername,
101 queryctl->port ? queryctl->port : protocol->port))<0)
103 perror("do_protocol: socket");
108 /* accept greeting message from mail server */
109 ok = (protocol->parse_response)(buf, socket);
112 gen_transact(socket, protocol->exit_cmd);
117 /* print the greeting */
118 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
119 fprintf(stderr,"%s greeting: %s\n", protocol->name, buf);
121 /* try to get authorized to fetch mail */
122 ok = (protocol->getauth)(socket, queryctl, buf);
128 /* compute count and first */
129 if ((*protocol->getrange)(socket, queryctl, &count, &first) != 0)
132 /* show them how many messages we'll be downloading */
133 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
135 fprintf(stderr,"%d messages in folder, %d new messages.\n",
136 count, count - first + 1);
138 fprintf(stderr,"%d %smessages in folder.\n", count, ok ? "" : "new ");
141 for (number = queryctl->flush ? 1 : first; number<=count; number++) {
145 /* open the mail pipe if we're using an MDA */
146 if (queryctl->output == TO_MDA
147 && (queryctl->fetchall || number >= first)) {
148 ok = (mboxfd = openmailpipe(queryctl)) < 0 ? -1 : 0;
153 if (queryctl->flush && number < first && !queryctl->fetchall)
154 ok = 0; /* no command to send here, will delete message below */
157 (*protocol->fetch)(socket, number, linelimit, &len);
158 if (outlevel == O_VERBOSE)
159 if (protocol->delimited)
160 fprintf(stderr,"fetching message %d (delimited)\n",number);
162 fprintf(stderr,"fetching message %d (%d bytes)\n",number,len);
163 ok = gen_readmsg(socket,mboxfd,len,protocol->delimited,
165 queryctl->servername,
167 !queryctl->norewrite);
169 (*protocol->trail)(socket, queryctl, number);
174 /* maybe we delete this message now? */
175 if ((number < first && queryctl->flush) || !queryctl->keep) {
176 if (outlevel > O_SILENT && outlevel < O_VERBOSE)
177 fprintf(stderr,"flushing message %d\n", number);
180 ok = gen_transact(socket, protocol->delete_cmd, number);
185 /* close the mail pipe, we'll reopen before next message */
186 if (queryctl->output == TO_MDA
187 && (queryctl->fetchall || number >= first)) {
188 ok = closemailpipe(mboxfd);
194 /* remove all messages flagged for deletion */
195 if (!queryctl->keep && protocol->expunge_cmd)
197 ok = gen_transact(socket, protocol->expunge_cmd);
202 ok = gen_transact(socket, protocol->exit_cmd);
209 ok = gen_transact(socket, protocol->exit_cmd);
217 if (ok != 0 && ok != PS_SOCKET)
218 gen_transact(socket, protocol->exit_cmd);
221 if (queryctl->output == TO_FOLDER)
223 if (closeuserfolder(mboxfd) < 0 && ok == 0)
226 else if (queryctl->output == TO_SMTP && mboxfd > 0)
229 if (ok == PS_IOERR || ok == PS_SOCKET)
230 perror("do_protocol: cleanUp");
235 /*********************************************************************
237 description: Assemble command in print style and send to the server
240 socket socket to which the server is connected.
241 fmt printf-style format
245 globals: reads outlevel.
246 *********************************************************************/
248 void gen_send(socket, fmt, va_alist)
253 char buf [POPBUFSIZE+1];
256 if (protocol->tagged)
257 (void) sprintf(buf, "%s ", GENSYM);
262 vsprintf(buf + strlen(buf), fmt, ap);
265 SockPuts(socket, buf);
267 if (outlevel == O_VERBOSE)
268 fprintf(stderr,"> %s\n", buf);
271 /*********************************************************************
272 function: gen_transact
273 description: Assemble command in print style and send to the server.
274 then accept a protocol-dependent response.
277 socket socket to which the server is connected.
278 fmt printf-style format
281 calls: SockPuts, imap_ok.
282 globals: reads outlevel.
283 *********************************************************************/
285 int gen_transact(socket, fmt, va_alist)
291 char buf [POPBUFSIZE+1];
294 if (protocol->tagged)
295 (void) sprintf(buf, "%s ", GENSYM);
300 vsprintf(buf + strlen(buf), fmt, ap);
303 SockPuts(socket, buf);
305 if (outlevel == O_VERBOSE)
306 fprintf(stderr,"> %s\n", buf);
308 ok = (protocol->parse_response)(buf,socket);
309 if (ok != 0 && outlevel > O_SILENT && outlevel <= O_VERBOSE)
310 fprintf(stderr,"%s\n",buf);
315 /*********************************************************************
317 description: hack message headers so replies will work properly
320 after where to put the hacked header
321 before header to hack
322 host name of the pop header
326 *********************************************************************/
328 static void reply_hack(buf, host)
329 /* hack local mail IDs -- code by Eric S. Raymond 20 Jun 1996 */
335 char mycopy[POPBUFSIZE+1];
337 if (strncmp("From: ", buf, 6)
338 && strncmp("To: ", buf, 4)
339 && strncmp("Reply-", buf, 6)
340 && strncmp("Cc: ", buf, 4)
341 && strncmp("Bcc: ", buf, 5)) {
346 for (from = mycopy; *from; from++)
350 case 0: /* before header colon */
355 case 1: /* we've seen the colon, we're looking for addresses */
358 else if (*from == '(')
360 else if (*from == '<' || isalnum(*from))
364 case 2: /* we're in a quoted human name, copy and ignore */
369 case 3: /* we're in a parenthesized human name, copy and ignore */
374 case 4: /* the real work gets done here */
376 * We're in something that might be an address part,
377 * either a bare unquoted/unparenthesized text or text
378 * enclosed in <> as per RFC822.
380 /* if the address part contains an @, don't mess with it */
384 /* If the address token is not properly terminated, ignore it. */
385 else if (*from == ' ' || *from == '\t')
389 * On proper termination with no @, insert hostname.
390 * Case '>' catches <>-enclosed mail IDs. Case ',' catches
391 * comma-separated bare IDs. Cases \r and \n catch the case
392 * of a single ID alone on the line.
394 else if (strchr(">,\r\n", *from))
402 /* everything else, including alphanumerics, just passes through */
405 case 5: /* we're in a remote mail ID, no need to append hostname */
406 if (*from == '>' || *from == ',' || isspace(*from))
411 /* all characters from the old buffer get copied to the new one */
417 /*********************************************************************
419 description: Parse addresses in succession out of a specified RFC822
420 header. Note 1: RFC822 escaping with \ is *not* handled.
421 Note 2: it is important that this routine not stop on \r,
422 since we use \r as a marker for RFC822 continuations below.
424 hdr header line to be parsed, NUL to continue in previous hdr
426 return value: next address, or NUL if there is no next address
428 *********************************************************************/
430 static char *nxtaddr(hdr)
433 static char *hp, *tp, address[POPBUFSIZE+1];
446 case 0: /* before header colon */
456 case 1: /* we've seen the colon, now grab the address */
457 if ((*hp == '\n') || (*hp == ',')) /* end of address list */
462 else if (*hp == '"') /* quoted string */
467 else if (*hp == '(') /* address comment -- ignore */
469 else if (*hp == '<') /* begin <address> */
474 else if (isspace(*hp)) /* ignore space */
476 else /* just take it */
483 case 2: /* we're in a quoted string, copy verbatim */
495 case 3: /* we're in a parenthesized comment, ignore */
502 case 4: /* possible <>-enclosed address */
503 if (*hp == '>') /* end of address */
509 else if (*hp == '<') /* nested <> */
511 else if (*hp == '"') /* quoted address */
516 else /* just copy address */
520 case 5: /* we're in a quoted address, copy verbatim */
521 if (*hp == '\n') /* mismatched quotes */
523 if (*hp != '"') /* just copy it if it isn't a quote */
525 else if (*hp == '"') /* end of quoted string */
537 /*********************************************************************
538 function: gen_readmsg
539 description: Read the message content
541 as described in RFC 1225.
543 socket ... to which the server is connected.
544 mboxfd open file descriptor to which the retrieved message will
547 popuser name of the POP user
548 pophost name of the POP host
551 return value: zero if success else PS_* return code.
553 globals: reads outlevel.
554 *********************************************************************/
556 int gen_readmsg (socket,mboxfd,len,delimited,popuser,pophost,output,rewrite)
566 char buf [MSGBUFSIZE+1];
567 char fromBuf[MSGBUFSIZE+1];
568 char *bufp, *headers, *unixfrom, *fromhdr, *tohdr, *cchdr, *bcchdr;
571 int lines,sizeticker;
573 /* This keeps the retrieved message count for display purposes */
574 static int msgnum = 0;
576 /* set up for status message if outlevel allows it */
577 if (outlevel > O_SILENT && outlevel < O_VERBOSE) {
578 fprintf(stderr,"reading message %d",++msgnum);
579 /* won't do the '...' if retrieved messages are being sent to stdout */
584 /* read the message content from the server */
586 headers = unixfrom = fromhdr = tohdr = cchdr = bcchdr = NULL;
589 while (delimited || len > 0) {
590 if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
594 if (buf[0] == '\0' || buf[0] == '\r' || buf[0] == '\n')
598 if (delimited && *bufp == 0)
599 break; /* end of message */
606 reply_hack(bufp, pophost);
610 oldlen = strlen(bufp);
611 headers = malloc(oldlen + 1);
614 (void) strcpy(headers, bufp);
622 * We deal with RFC822 continuation lines here.
623 * Replace previous '\n' with '\r' so nxtaddr
624 * and reply-hack will be able to see past it.
625 * We'll undo this before writing the header.
627 if (isspace(bufp[0]))
628 headers[oldlen-1] = '\r';
630 newlen = oldlen + strlen(bufp);
631 headers = realloc(headers, newlen + 1);
634 strcpy(headers + oldlen, bufp);
635 bufp = headers + oldlen;
639 if (!strncmp(bufp,"From ",5))
641 else if (!strncmp("From: ", bufp, 6))
643 else if (!strncmp("To: ", bufp, 4))
645 else if (!strncmp("Cc: ", bufp, 4))
647 else if (!strncmp("Bcc: ", bufp, 5))
659 if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK)
663 * This is what we'd do if popclient were a real MDA
664 * a la sendmail -- crack all the destination headers
665 * and send to every address we can reach via SMTP.
667 if ((cp = nxtaddr(tohdr)) != (char *)NULL)
669 if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
672 (cp = nxtaddr(NULL));
673 if ((cp = nxtaddr(cchdr)) != (char *)NULL)
675 if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
678 (cp = nxtaddr(NULL));
679 if ((cp = nxtaddr(bcchdr)) != (char *)NULL)
681 if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
684 (cp = nxtaddr(NULL));
687 * Since we're really only fetching mail for one user
688 * per host query, we can be simpler
690 if (SMTP_rcpt(mboxfd, popuser) == SM_UNRECOVERABLE)
692 #endif /* SMTP_RESEND */
694 if (outlevel == O_VERBOSE)
695 fputs("SMTP> ", stderr);
701 (void) strcpy(fromBuf, unixfrom);
705 if (fromhdr && (cp = nxtaddr(fromhdr)))
707 "From %s %s", cp, ctime(&now));
710 "From POPmail %s",ctime(&now));
713 if (write(mboxfd,fromBuf,strlen(fromBuf)) < 0) {
714 perror("gen_readmsg: writing From header");
723 /* change continuation markers back to regular newlines */
724 for (cp = headers; cp < headers + oldlen; cp++)
728 if (write(mboxfd,headers,oldlen) < 0)
732 perror("gen_readmsg: writing RFC822 headers");
735 else if (outlevel == O_VERBOSE)
741 /* write this line to the file */
742 if (write(mboxfd,bufp,strlen(bufp)) < 0)
744 perror("gen_readmsg: writing message text");
747 else if (outlevel == O_VERBOSE)
752 sizeticker += strlen(bufp);
753 while (sizeticker >= MSGBUFSIZE)
755 if (outlevel > O_SILENT && outlevel < O_VERBOSE && mboxfd != 1)
757 sizeticker -= MSGBUFSIZE;
762 if (outlevel == O_VERBOSE)
765 /* write message terminator, if any */
769 if (SMTP_eom(mboxfd) != SM_OK)
775 /* The server may not write the extra newline required by the Unix
776 mail folder format, so we write one here just in case */
777 if (write(mboxfd,"\n",1) < 0) {
778 perror("gen_readmsg: writing terminator");
784 /* The mail delivery agent may require a terminator. Write it if
785 it has been defined */
787 if (write(mboxfd,BINMAIL_TERM,strlen(BINMAIL_TERM)) < 0) {
788 perror("gen_readmsg: writing terminator");
791 #endif /* BINMAIL_TERM */
794 /* finish up display output */
795 if (outlevel == O_VERBOSE)
796 fprintf(stderr,"(%d lines of message content)\n",lines);
797 else if (outlevel > O_SILENT && mboxfd != 1)