]> Pileus Git - ~andy/fetchmail/blob - driver.c
Handle RFC822 continuation.
[~andy/fetchmail] / driver.c
1 /* Copyright 1996 by Eric S. Raymond
2  * All rights reserved.
3  * For license terms, see the file COPYING in this directory.
4  */
5
6 /***********************************************************************
7   module:       driver.c
8   project:      popclient
9   programmer:   Eric S. Raymond
10   description:  Generic driver for mail fetch method protocols
11
12  ***********************************************************************/
13
14 #include  <config.h>
15 #include  <varargs.h>
16
17 #include  <stdio.h>
18 #if defined(STDC_HEADERS)
19 #include  <string.h>
20 #endif
21 #if defined(HAVE_UNISTD_H)
22 #include  <unistd.h>
23 #endif
24
25 #include  <sys/time.h>
26 #include  <ctype.h>
27 #include  <errno.h>
28 #include  <malloc.h>
29
30 #include  "socket.h"
31 #include  "popclient.h"
32 #include  "smtp.h"
33
34 static struct method *protocol;
35
36 #define SMTP_PORT       25      /* standard SMTP service port */
37
38 char tag[TAGLEN];
39 static int tagnum;
40 #define GENSYM  (sprintf(tag, "a%04d", ++tagnum), tag)
41
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 */
46
47 /*********************************************************************
48   function:      do_protocol
49   description:   retrieve messages from the specified mail server
50                  using a given set of methods
51
52   arguments:     
53     queryctl     fully-specified options (i.e. parsed, defaults invoked,
54                  etc).
55     proto        protocol method pointer
56
57   return value:  exit code from the set of PS_.* constants defined in 
58                  popclient.h
59   calls:
60   globals:       reads outlevel.
61  *********************************************************************/
62
63 int do_protocol(queryctl, proto)
64 struct hostrec *queryctl;
65 struct method *proto;
66 {
67     int ok, len;
68     int mboxfd;
69     char buf [POPBUFSIZE+1], host[HOSTLEN+1];
70     int socket;
71     int first,number,count;
72
73     tagnum = 0;
74     protocol = proto;
75
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) 
79             return(PS_IOERR);
80     } else if (queryctl->output == TO_SMTP) {
81         if ((mboxfd = Socket(queryctl->smtphost, SMTP_PORT)) < 0) 
82             return(PS_SOCKET);
83
84         /* eat the greeting message */
85         if (SMTP_ok(mboxfd, NULL) != SM_OK) {
86             close(mboxfd);
87             mboxfd = 0;
88             return(PS_SMTP);
89         }
90     
91         /* make it look like mail is coming from the server */
92         if (SMTP_helo(mboxfd,queryctl->servername) != SM_OK) {
93             close(mboxfd);
94             mboxfd = 0;
95             return(PS_SMTP);
96         }
97     }
98
99     /* open a socket to the mail server */
100     if ((socket = Socket(queryctl->servername,
101                          queryctl->port ? queryctl->port : protocol->port))<0)
102     {
103         perror("do_protocol: socket");
104         ok = PS_SOCKET;
105         goto closeUp;
106     }
107
108     /* accept greeting message from mail server */
109     ok = (protocol->parse_response)(buf, socket);
110     if (ok != 0) {
111         if (ok != PS_SOCKET)
112             gen_transact(socket, protocol->exit_cmd);
113         close(socket);
114         goto closeUp;
115     }
116
117     /* print the greeting */
118     if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
119         fprintf(stderr,"%s greeting: %s\n", protocol->name, buf);
120
121     /* try to get authorized to fetch mail */
122     ok = (protocol->getauth)(socket, queryctl, buf);
123     if (ok == PS_ERROR)
124         ok = PS_AUTHFAIL;
125     if (ok != 0)
126         goto cleanUp;
127
128     /* compute count and first */
129     if ((*protocol->getrange)(socket, queryctl, &count, &first) != 0)
130         goto cleanUp;
131
132     /* show them how many messages we'll be downloading */
133     if (outlevel > O_SILENT && outlevel < O_VERBOSE)
134         if (first > 1) 
135             fprintf(stderr,"%d messages in folder, %d new messages.\n", 
136                     count, count - first + 1);
137         else
138             fprintf(stderr,"%d %smessages in folder.\n", count, ok ? "" : "new ");
139
140     if (count > 0) { 
141         for (number = queryctl->flush ? 1 : first;  number<=count; number++) {
142
143             char *cp;
144
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;
149                 if (ok != 0)
150                     goto cleanUp;
151             }
152            
153             if (queryctl->flush && number < first && !queryctl->fetchall) 
154                 ok = 0;  /* no command to send here, will delete message below */
155             else
156             {
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);
161                     else
162                         fprintf(stderr,"fetching message %d (%d bytes)\n",number,len);
163                 ok = gen_readmsg(socket,mboxfd,len,protocol->delimited,
164                                  queryctl->localname,
165                                  queryctl->servername,
166                                  queryctl->output, 
167                                  !queryctl->norewrite);
168                 if (protocol->trail)
169                     (*protocol->trail)(socket, queryctl, number);
170                 if (ok != 0)
171                     goto cleanUp;
172             }
173
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);
178                 else
179                     ;
180                 ok = gen_transact(socket, protocol->delete_cmd, number);
181                 if (ok != 0)
182                     goto cleanUp;
183             }
184
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);
189                 if (ok != 0)
190                     goto cleanUp;
191             }
192         }
193
194         /* remove all messages flagged for deletion */
195         if (!queryctl->keep && protocol->expunge_cmd)
196         {
197             ok = gen_transact(socket, protocol->expunge_cmd);
198             if (ok != 0)
199                 goto cleanUp;
200         }
201
202         ok = gen_transact(socket, protocol->exit_cmd);
203         if (ok == 0)
204             ok = PS_SUCCESS;
205         close(socket);
206         goto closeUp;
207     }
208     else {
209         ok = gen_transact(socket, protocol->exit_cmd);
210         if (ok == 0)
211             ok = PS_NOMAIL;
212         close(socket);
213         goto closeUp;
214     }
215
216 cleanUp:
217     if (ok != 0 && ok != PS_SOCKET)
218         gen_transact(socket, protocol->exit_cmd);
219
220 closeUp:
221     if (queryctl->output == TO_FOLDER)
222     {
223         if (closeuserfolder(mboxfd) < 0 && ok == 0)
224             ok = PS_IOERR;
225     }
226     else if (queryctl->output == TO_SMTP && mboxfd  > 0)
227         close(mboxfd);
228
229     if (ok == PS_IOERR || ok == PS_SOCKET) 
230         perror("do_protocol: cleanUp");
231
232     return(ok);
233 }
234
235 /*********************************************************************
236   function:      gen_send
237   description:   Assemble command in print style and send to the server
238
239   arguments:     
240     socket       socket to which the server is connected.
241     fmt          printf-style format
242
243   return value:  none.
244   calls:         SockPuts.
245   globals:       reads outlevel.
246  *********************************************************************/
247
248 void gen_send(socket, fmt, va_alist)
249 int socket;
250 const char *fmt;
251 va_dcl {
252
253   char buf [POPBUFSIZE+1];
254   va_list ap;
255
256   if (protocol->tagged)
257       (void) sprintf(buf, "%s ", GENSYM);
258   else
259       buf[0] = '\0';
260
261   va_start(ap);
262   vsprintf(buf + strlen(buf), fmt, ap);
263   va_end(ap);
264
265   SockPuts(socket, buf);
266
267   if (outlevel == O_VERBOSE)
268     fprintf(stderr,"> %s\n", buf);
269 }
270
271 /*********************************************************************
272   function:      gen_transact
273   description:   Assemble command in print style and send to the server.
274                  then accept a protocol-dependent response.
275
276   arguments:     
277     socket       socket to which the server is connected.
278     fmt          printf-style format
279
280   return value:  none.
281   calls:         SockPuts, imap_ok.
282   globals:       reads outlevel.
283  *********************************************************************/
284
285 int gen_transact(socket, fmt, va_alist)
286 int socket;
287 const char *fmt;
288 va_dcl {
289
290   int ok;
291   char buf [POPBUFSIZE+1];
292   va_list ap;
293
294   if (protocol->tagged)
295       (void) sprintf(buf, "%s ", GENSYM);
296   else
297       buf[0] = '\0';
298
299   va_start(ap);
300   vsprintf(buf + strlen(buf), fmt, ap);
301   va_end(ap);
302
303   SockPuts(socket, buf);
304
305   if (outlevel == O_VERBOSE)
306     fprintf(stderr,"> %s\n", buf);
307
308   ok = (protocol->parse_response)(buf,socket);
309   if (ok != 0 && outlevel > O_SILENT && outlevel <= O_VERBOSE)
310     fprintf(stderr,"%s\n",buf);
311
312   return(ok);
313 }
314
315 /*********************************************************************
316   function:      
317   description:   hack message headers so replies will work properly
318
319   arguments:
320     after        where to put the hacked header
321     before       header to hack
322     host         name of the pop header
323
324   return value:  none.
325   calls:         none.
326  *********************************************************************/
327
328 static void reply_hack(buf, host)
329 /* hack local mail IDs -- code by Eric S. Raymond 20 Jun 1996 */
330 char *buf;
331 const char *host;
332 {
333     const char *from;
334     int state = 0;
335     char mycopy[POPBUFSIZE+1];
336
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)) {
342         return;
343     }
344
345     strcpy(mycopy, buf);
346     for (from = mycopy; *from; from++)
347     {
348         switch (state)
349         {
350         case 0:   /* before header colon */
351             if (*from == ':')
352                 state = 1;
353             break;
354
355         case 1:   /* we've seen the colon, we're looking for addresses */
356             if (*from == '"')
357                 state = 2;
358             else if (*from == '(')
359                 state = 3;    
360             else if (*from == '<' || isalnum(*from))
361                 state = 4;
362             break;
363
364         case 2:   /* we're in a quoted human name, copy and ignore */
365             if (*from == '"')
366                 state = 1;
367             break;
368
369         case 3:   /* we're in a parenthesized human name, copy and ignore */
370             if (*from == ')')
371                 state = 1;
372             break;
373
374         case 4:   /* the real work gets done here */
375             /*
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.
379              */
380             /* if the address part contains an @, don't mess with it */
381             if (*from == '@')
382                 state = 5;
383
384             /* If the address token is not properly terminated, ignore it. */
385             else if (*from == ' ' || *from == '\t')
386                 state = 1;
387
388             /*
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.
393              */
394             else if (strchr(">,\r\n", *from))
395             {
396                 strcpy(buf, "@");
397                 strcat(buf, host);
398                 buf += strlen(buf);
399                 state = 1;
400             }
401
402             /* everything else, including alphanumerics, just passes through */
403             break;
404
405         case 5:   /* we're in a remote mail ID, no need to append hostname */
406             if (*from == '>' || *from == ',' || isspace(*from))
407                 state = 1;
408             break;
409         }
410
411         /* all characters from the old buffer get copied to the new one */
412         *buf++ = *from;
413     }
414     *buf++ = '\0';
415 }
416
417 /*********************************************************************
418   function:      nxtaddr
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.
423   arguments:     
424     hdr          header line to be parsed, NUL to continue in previous hdr
425
426   return value:  next address, or NUL if there is no next address
427   calls:         none
428  *********************************************************************/
429
430 static char *nxtaddr(hdr)
431 char *hdr;
432 {
433     static char *hp, *tp, address[POPBUFSIZE+1];
434     static      state;
435
436     if (hdr)
437     {
438         hp = hdr;
439         state = 0;
440     }
441
442     for (; *hp; hp++)
443     {
444         switch (state)
445         {
446         case 0:   /* before header colon */
447             if (*hp == '\n')
448                 return(NULL);
449             else if (*hp == ':')
450             {
451                 state = 1;
452                 tp = address;
453             }
454             break;
455
456         case 1:   /* we've seen the colon, now grab the address */
457             if ((*hp == '\n') || (*hp == ','))  /* end of address list */
458             {
459                 *tp++ = '\0';
460                 return(address);
461             }
462             else if (*hp == '"') /* quoted string */
463             {
464                 state = 2;
465                 *tp++ = *hp;
466             }
467             else if (*hp == '(') /* address comment -- ignore */
468                 state = 3;    
469             else if (*hp == '<') /* begin <address> */
470             {
471                 state = 4;
472                 tp = address;
473             }
474             else if (isspace(*hp)) /* ignore space */
475                 state = 1;
476             else   /* just take it */
477             {
478                 state = 1;
479                 *tp++ = *hp;
480             }
481             break;
482
483         case 2:   /* we're in a quoted string, copy verbatim */
484             if (*hp == '\n')
485                 return(NULL);
486             if (*hp != '"')
487                 *tp++ = *hp;
488             else if (*hp == '"')
489             {
490                 *tp++ = *hp;
491                 state = 1;
492             }
493             break;
494
495         case 3:   /* we're in a parenthesized comment, ignore */
496             if (*hp == '\n')
497                 return(NULL);
498             else if (*hp == ')')
499                 state = 1;
500             break;
501
502         case 4:   /* possible <>-enclosed address */
503             if (*hp == '>') /* end of address */
504             {
505                 *tp++ = '\0';
506                 state = 1;
507                 return(address);
508             }
509             else if (*hp == '<')  /* nested <> */
510                 tp = address;
511             else if (*hp == '"') /* quoted address */
512             {
513                 *tp++ = *hp;
514                 state = 5;
515             }
516             else  /* just copy address */
517                 *tp++ = *hp;
518             break;
519
520         case 5:   /* we're in a quoted address, copy verbatim */
521             if (*hp == '\n')  /* mismatched quotes */
522                 return(NULL);
523             if (*hp != '"')  /* just copy it if it isn't a quote */
524                 *tp++ = *hp;
525             else if (*hp == '"')  /* end of quoted string */
526             {
527                 *tp++ = *hp;
528                 state = 4;
529             }
530             break;
531         }
532     }
533
534     return(NULL);
535 }
536
537 /*********************************************************************
538   function:      gen_readmsg
539   description:   Read the message content 
540
541  as described in RFC 1225.
542   arguments:     
543     socket       ... to which the server is connected.
544     mboxfd       open file descriptor to which the retrieved message will
545                  be written.
546     len          length of text 
547     popuser      name of the POP user 
548     pophost      name of the POP host 
549     output       output mode
550
551   return value:  zero if success else PS_* return code.
552   calls:         SockGets.
553   globals:       reads outlevel. 
554  *********************************************************************/
555
556 int gen_readmsg (socket,mboxfd,len,delimited,popuser,pophost,output,rewrite)
557 int socket;
558 int mboxfd;
559 long len;
560 int delimited;
561 char *popuser;
562 char *pophost;
563 int output;
564 int rewrite;
565
566     char buf [MSGBUFSIZE+1]; 
567     char fromBuf[MSGBUFSIZE+1];
568     char *bufp, *headers, *unixfrom, *fromhdr, *tohdr, *cchdr, *bcchdr;
569     int n, oldlen;
570     int inheaders;
571     int lines,sizeticker;
572     time_t now;
573     /* This keeps the retrieved message count for display purposes */
574     static int msgnum = 0;  
575
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 */
580         if (mboxfd == 1)
581             fputs("\n",stderr);
582     }
583
584     /* read the message content from the server */
585     inheaders = 1;
586     headers = unixfrom = fromhdr = tohdr = cchdr = bcchdr = NULL;
587     lines = 0;
588     sizeticker = 0;
589     while (delimited || len > 0) {
590         if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
591             return(PS_SOCKET);
592         len -= n;
593         bufp = buf;
594         if (buf[0] == '\0' || buf[0] == '\r' || buf[0] == '\n')
595             inheaders = 0;
596         if (*bufp == '.') {
597             bufp++;
598             if (delimited && *bufp == 0)
599                 break;  /* end of message */
600         }
601         strcat(bufp,"\n");
602      
603         if (inheaders)
604         {
605             if (rewrite)
606                 reply_hack(bufp, pophost);
607
608             if (!lines)
609             {
610                 oldlen = strlen(bufp);
611                 headers = malloc(oldlen + 1);
612                 if (headers == NULL)
613                     return(PS_IOERR);
614                 (void) strcpy(headers, bufp);
615                 bufp = headers;
616             }
617             else
618             {
619                 int     newlen;
620
621                 /*
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.
626                  */
627                 if (isspace(bufp[0]))
628                     headers[oldlen-1] = '\r';
629
630                 newlen = oldlen + strlen(bufp);
631                 headers = realloc(headers, newlen + 1);
632                 if (headers == NULL)
633                     return(PS_IOERR);
634                 strcpy(headers + oldlen, bufp);
635                 bufp = headers + oldlen;
636                 oldlen = newlen;
637             }
638
639             if (!strncmp(bufp,"From ",5))
640                 unixfrom = bufp;
641             else if (!strncmp("From: ", bufp, 6))
642                 fromhdr = bufp;
643             else if (!strncmp("To: ", bufp, 4))
644                 tohdr = bufp;
645             else if (!strncmp("Cc: ", bufp, 4))
646                 cchdr = bufp;
647             else if (!strncmp("Bcc: ", bufp, 5))
648                 bcchdr = bufp;
649
650             goto skipwrite;
651         }
652         else if (headers)
653         {
654             char        *cp;
655
656             switch (output)
657             {
658             case TO_SMTP:
659                 if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK)
660                     return(PS_SMTP);
661 #ifdef SMTP_RESEND
662                 /*
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.
666                  */
667                 if ((cp = nxtaddr(tohdr)) != (char *)NULL)
668                     do {
669                         if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
670                             return(PS_SMTP);
671                     } while
672                         (cp = nxtaddr(NULL));
673                 if ((cp = nxtaddr(cchdr)) != (char *)NULL)
674                     do {
675                         if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
676                             return(PS_SMTP);
677                     } while
678                         (cp = nxtaddr(NULL));
679                 if ((cp = nxtaddr(bcchdr)) != (char *)NULL)
680                     do {
681                         if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
682                             return(PS_SMTP);
683                     } while
684                         (cp = nxtaddr(NULL));
685 #else
686                 /*
687                  * Since we're really only fetching mail for one user
688                  * per host query, we can be simpler
689                  */
690                 if (SMTP_rcpt(mboxfd, popuser) == SM_UNRECOVERABLE)
691                     return(PS_SMTP);
692 #endif /* SMTP_RESEND */
693                 SMTP_data(mboxfd);
694                 if (outlevel == O_VERBOSE)
695                     fputs("SMTP> ", stderr);
696                 break;
697
698             case TO_FOLDER:
699             case TO_STDOUT:
700                 if (unixfrom)
701                     (void) strcpy(fromBuf, unixfrom);
702                 else
703                 {
704                     now = time(NULL);
705                     if (fromhdr && (cp = nxtaddr(fromhdr)))
706                         sprintf(fromBuf,
707                                 "From %s %s", cp, ctime(&now));
708                     else
709                         sprintf(fromBuf,
710                                 "From POPmail %s",ctime(&now));
711                 }
712
713                 if (write(mboxfd,fromBuf,strlen(fromBuf)) < 0) {
714                     perror("gen_readmsg: writing From header");
715                     return(PS_IOERR);
716                 }
717                 break;
718
719             case TO_MDA:
720                 break;
721             }
722
723             /* change continuation markers back to regular newlines */
724             for (cp = headers; cp < headers +  oldlen; cp++)
725                 if (*cp == '\r')
726                     *cp = '\n';
727
728             if (write(mboxfd,headers,oldlen) < 0)
729             {
730                 free(headers);
731                 headers = NULL;
732                 perror("gen_readmsg: writing RFC822 headers");
733                 return(PS_IOERR);
734             }
735             else if (outlevel == O_VERBOSE)
736                 fputs("#", stderr);
737             free(headers);
738             headers = NULL;
739         }
740
741         /* write this line to the file */
742         if (write(mboxfd,bufp,strlen(bufp)) < 0)
743         {
744             perror("gen_readmsg: writing message text");
745             return(PS_IOERR);
746         }
747         else if (outlevel == O_VERBOSE)
748             fputc('*', stderr);
749
750     skipwrite:;
751
752         sizeticker += strlen(bufp);
753         while (sizeticker >= MSGBUFSIZE)
754         {
755             if (outlevel > O_SILENT && outlevel < O_VERBOSE && mboxfd != 1)
756                 fputc('.',stderr);
757             sizeticker -= MSGBUFSIZE;
758         }
759         lines++;
760     }
761
762     if (outlevel == O_VERBOSE)
763         fputc('\n', stderr);
764
765     /* write message terminator, if any */
766     switch (output)
767     {
768     case TO_SMTP:
769         if (SMTP_eom(mboxfd) != SM_OK)
770             return(PS_SMTP);
771         break;
772
773     case TO_FOLDER:
774     case TO_STDOUT:
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");
779             return(PS_IOERR);
780         }
781         break;
782
783     case TO_MDA:
784         /* The mail delivery agent may require a terminator.  Write it if
785            it has been defined */
786 #ifdef BINMAIL_TERM
787         if (write(mboxfd,BINMAIL_TERM,strlen(BINMAIL_TERM)) < 0) {
788             perror("gen_readmsg: writing terminator");
789             return(PS_IOERR);
790         }
791 #endif /* BINMAIL_TERM */
792     }
793
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) 
798         fputs("\n",stderr);
799     else
800         ;
801     return(0);
802 }