]> Pileus Git - ~andy/fetchmail/blob - driver.c
Removed all pretentions to RPOP support.
[~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:      fetchmail
9   programmer:   Eric S. Raymond
10   description:  Generic driver for mail fetch method protocols
11
12  ***********************************************************************/
13
14 #include  <config.h>
15 #include  <stdio.h>
16 #include  <malloc.h>
17 #include  <varargs.h>
18 #include  <sys/time.h>
19 #include  <signal.h>
20
21 #include  "socket.h"
22 #include  "fetchmail.h"
23 #include  "smtp.h"
24
25 #define SMTP_PORT       25      /* standard SMTP service port */
26
27 static struct method *protocol;
28
29 static int alarmed; /* A flag to indicate that SIGALRM happened */
30 int timeout = CLIENT_TIMEOUT;
31
32 char tag[TAGLEN];
33 static int tagnum;
34 #define GENSYM  (sprintf(tag, "a%04d", ++tagnum), tag)
35
36 /*********************************************************************
37   function:      
38   description:   hack message headers so replies will work properly
39
40   arguments:
41     after        where to put the hacked header
42     before       header to hack
43     host         name of the pop header
44
45   return value:  none.
46   calls:         none.
47  *********************************************************************/
48
49 static void reply_hack(buf, host)
50 /* hack local mail IDs -- code by Eric S. Raymond 20 Jun 1996 */
51 char *buf;
52 const char *host;
53 {
54     const char *from;
55     int state = 0;
56     char mycopy[POPBUFSIZE+1];
57
58     if (strncmp("From: ", buf, 6)
59         && strncmp("To: ", buf, 4)
60         && strncmp("Reply-", buf, 6)
61         && strncmp("Cc: ", buf, 4)
62         && strncmp("Bcc: ", buf, 5)) {
63         return;
64     }
65
66     strcpy(mycopy, buf);
67     for (from = mycopy; *from; from++)
68     {
69         switch (state)
70         {
71         case 0:   /* before header colon */
72             if (*from == ':')
73                 state = 1;
74             break;
75
76         case 1:   /* we've seen the colon, we're looking for addresses */
77             if (*from == '"')
78                 state = 2;
79             else if (*from == '(')
80                 state = 3;    
81             else if (*from == '<' || isalnum(*from))
82                 state = 4;
83             break;
84
85         case 2:   /* we're in a quoted human name, copy and ignore */
86             if (*from == '"')
87                 state = 1;
88             break;
89
90         case 3:   /* we're in a parenthesized human name, copy and ignore */
91             if (*from == ')')
92                 state = 1;
93             break;
94
95         case 4:   /* the real work gets done here */
96             /*
97              * We're in something that might be an address part,
98              * either a bare unquoted/unparenthesized text or text
99              * enclosed in <> as per RFC822.
100              */
101             /* if the address part contains an @, don't mess with it */
102             if (*from == '@')
103                 state = 5;
104
105             /* If the address token is not properly terminated, ignore it. */
106             else if (*from == ' ' || *from == '\t')
107                 state = 1;
108
109             /*
110              * On proper termination with no @, insert hostname.
111              * Case '>' catches <>-enclosed mail IDs.  Case ',' catches
112              * comma-separated bare IDs.  Cases \r and \n catch the case
113              * of a single ID alone on the line.
114              */
115             else if (strchr(">,\r\n", *from))
116             {
117                 strcpy(buf, "@");
118                 strcat(buf, host);
119                 buf += strlen(buf);
120                 state = 1;
121             }
122
123             /* everything else, including alphanumerics, just passes through */
124             break;
125
126         case 5:   /* we're in a remote mail ID, no need to append hostname */
127             if (*from == '>' || *from == ',' || isspace(*from))
128                 state = 1;
129             break;
130         }
131
132         /* all characters from the old buffer get copied to the new one */
133         *buf++ = *from;
134     }
135     *buf++ = '\0';
136 }
137
138 /*********************************************************************
139   function:      nxtaddr
140   description:   Parse addresses in succession out of a specified RFC822
141                  header.  Note 1: RFC822 escaping with \ is *not* handled.
142                  Note 2: it is important that this routine not stop on \r,
143                  since we use \r as a marker for RFC822 continuations below.
144   arguments:     
145     hdr          header line to be parsed, NUL to continue in previous hdr
146
147   return value:  next address, or NUL if there is no next address
148   calls:         none
149  *********************************************************************/
150
151 static char *nxtaddr(hdr)
152 char *hdr;
153 {
154     static char *hp, *tp, address[POPBUFSIZE+1];
155     static      state;
156
157     if (hdr)
158     {
159         hp = hdr;
160         state = 0;
161     }
162
163     for (; *hp; hp++)
164     {
165         switch (state)
166         {
167         case 0:   /* before header colon */
168             if (*hp == '\n')
169                 return(NULL);
170             else if (*hp == ':')
171             {
172                 state = 1;
173                 tp = address;
174             }
175             break;
176
177         case 1:   /* we've seen the colon, now grab the address */
178             if ((*hp == '\n') || (*hp == ','))  /* end of address list */
179             {
180                 *tp++ = '\0';
181                 return(address);
182             }
183             else if (*hp == '"') /* quoted string */
184             {
185                 state = 2;
186                 *tp++ = *hp;
187             }
188             else if (*hp == '(') /* address comment -- ignore */
189                 state = 3;    
190             else if (*hp == '<') /* begin <address> */
191             {
192                 state = 4;
193                 tp = address;
194             }
195             else if (isspace(*hp)) /* ignore space */
196                 state = 1;
197             else   /* just take it */
198             {
199                 state = 1;
200                 *tp++ = *hp;
201             }
202             break;
203
204         case 2:   /* we're in a quoted string, copy verbatim */
205             if (*hp == '\n')
206                 return(NULL);
207             if (*hp != '"')
208                 *tp++ = *hp;
209             else if (*hp == '"')
210             {
211                 *tp++ = *hp;
212                 state = 1;
213             }
214             break;
215
216         case 3:   /* we're in a parenthesized comment, ignore */
217             if (*hp == '\n')
218                 return(NULL);
219             else if (*hp == ')')
220                 state = 1;
221             break;
222
223         case 4:   /* possible <>-enclosed address */
224             if (*hp == '>') /* end of address */
225             {
226                 *tp++ = '\0';
227                 state = 1;
228                 return(address);
229             }
230             else if (*hp == '<')  /* nested <> */
231                 tp = address;
232             else if (*hp == '"') /* quoted address */
233             {
234                 *tp++ = *hp;
235                 state = 5;
236             }
237             else  /* just copy address */
238                 *tp++ = *hp;
239             break;
240
241         case 5:   /* we're in a quoted address, copy verbatim */
242             if (*hp == '\n')  /* mismatched quotes */
243                 return(NULL);
244             if (*hp != '"')  /* just copy it if it isn't a quote */
245                 *tp++ = *hp;
246             else if (*hp == '"')  /* end of quoted string */
247             {
248                 *tp++ = *hp;
249                 state = 4;
250             }
251             break;
252         }
253     }
254
255     return(NULL);
256 }
257
258 /*********************************************************************
259   function:      gen_readmsg
260   description:   Read the message content 
261
262  as described in RFC 1225.
263   arguments:     
264     socket       ... to which the server is connected.
265     mboxfd       open file descriptor to which the retrieved message will
266                  be written.
267     len          length of text 
268     delimited    does the protocol use a message delimiter?
269     queryctl     host control block
270
271   return value:  zero if success else PS_* return code.
272   calls:         SockGets.
273   globals:       reads outlevel. 
274  *********************************************************************/
275
276 static int gen_readmsg (socket, mboxfd, len, delimited, queryctl)
277 int socket;
278 int mboxfd;
279 long len;
280 int delimited;
281 struct hostrec *queryctl;
282
283     char buf [MSGBUFSIZE+1]; 
284     char fromBuf[MSGBUFSIZE+1];
285     char *bufp, *headers, *unixfrom, *fromhdr, *tohdr, *cchdr, *bcchdr;
286     int n, oldlen;
287     int inheaders;
288     int lines,sizeticker;
289     time_t now;
290     /* This keeps the retrieved message count for display purposes */
291     static int msgnum = 0;  
292
293     /* set up for status message if outlevel allows it */
294     if (outlevel > O_SILENT && outlevel < O_VERBOSE)
295         fprintf(stderr,"reading message %d",++msgnum);
296
297     /* read the message content from the server */
298     inheaders = 1;
299     headers = unixfrom = fromhdr = tohdr = cchdr = bcchdr = NULL;
300     lines = 0;
301     sizeticker = 0;
302     while (delimited || len > 0) {
303         if ((n = SockGets(socket,buf,sizeof(buf))) < 0)
304             return(PS_SOCKET);
305         len -= n;
306         bufp = buf;
307         if (buf[0] == '\0' || buf[0] == '\r' || buf[0] == '\n')
308             inheaders = 0;
309         if (*bufp == '.') {
310             bufp++;
311             if (delimited && *bufp == 0)
312                 break;  /* end of message */
313         }
314         strcat(bufp, "\n");
315      
316         if (inheaders)
317         {
318             if (!queryctl->norewrite)
319                 reply_hack(bufp, queryctl->servername);
320
321             if (!lines)
322             {
323                 oldlen = strlen(bufp);
324                 headers = malloc(oldlen + 1);
325                 if (headers == NULL)
326                     return(PS_IOERR);
327                 (void) strcpy(headers, bufp);
328                 bufp = headers;
329             }
330             else
331             {
332                 int     newlen;
333
334                 /*
335                  * We deal with RFC822 continuation lines here.
336                  * Replace previous '\n' with '\r' so nxtaddr 
337                  * and reply_hack will be able to see past it.
338                  * (We know this safe because SocketGets stripped
339                  * out all carriage returns in the read loop above
340                  * and we haven't reintroduced any since then.)
341                  * We'll undo this before writing the header.
342                  */
343                 if (isspace(bufp[0]))
344                     headers[oldlen-1] = '\r';
345
346                 newlen = oldlen + strlen(bufp);
347                 headers = realloc(headers, newlen + 1);
348                 if (headers == NULL)
349                     return(PS_IOERR);
350                 strcpy(headers + oldlen, bufp);
351                 bufp = headers + oldlen;
352                 oldlen = newlen;
353             }
354
355             if (!strncmp(bufp,"From ",5))
356                 unixfrom = bufp;
357             else if (!strncasecmp("From: ", bufp, 6))
358                 fromhdr = bufp;
359             else if (!strncasecmp("To: ", bufp, 4))
360                 tohdr = bufp;
361             else if (!strncasecmp("Cc: ", bufp, 4))
362                 cchdr = bufp;
363             else if (!strncasecmp("Bcc: ", bufp, 5))
364                 bcchdr = bufp;
365
366             goto skipwrite;
367         }
368         else if (headers)
369         {
370             char        *cp;
371
372             if (!queryctl->mda[0])
373             {
374                 if (SMTP_from(mboxfd, nxtaddr(fromhdr)) != SM_OK)
375                     return(PS_SMTP);
376 #ifdef SMTP_RESEND
377                 /*
378                  * This is what we'd do if fetchmail were a real MDA
379                  * a la sendmail -- crack all the destination headers
380                  * and send to every address we can reach via SMTP.
381                  */
382                 if (tohdr && (cp = nxtaddr(tohdr)) != (char *)NULL)
383                     do {
384                         if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
385                             return(PS_SMTP);
386                     } while
387                         (cp = nxtaddr(NULL));
388                 if (cchdr && (cp = nxtaddr(cchdr)) != (char *)NULL)
389                     do {
390                         if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
391                             return(PS_SMTP);
392                     } while
393                         (cp = nxtaddr(NULL));
394                 if (bcchdr && (cp = nxtaddr(bcchdr)) != (char *)NULL)
395                     do {
396                         if (SMTP_rcpt(mboxfd, cp) == SM_UNRECOVERABLE)
397                             return(PS_SMTP);
398                     } while
399                         (cp = nxtaddr(NULL));
400 #else
401                 /*
402                  * Since we're really only fetching mail for one user
403                  * per host query, we can be simpler
404                  */
405                 if (SMTP_rcpt(mboxfd, queryctl->localname) == SM_UNRECOVERABLE)
406                     return(PS_SMTP);
407 #endif /* SMTP_RESEND */
408                 SMTP_data(mboxfd);
409                 if (outlevel == O_VERBOSE)
410                     fputs("SMTP> ", stderr);
411             }
412
413             /* change continuation markers back to regular newlines */
414             for (cp = headers; cp < headers +  oldlen; cp++)
415                 if (*cp == '\r')
416                     *cp = '\n';
417
418             /*
419              * Strictly speaking, we ought to replace all LFs
420              * with CR-LF before shipping to an SMTP listener.
421              * Since most SMTP listeners built since the mid-1980s
422              * (and *all* Unix listeners) accept lone LF as equivalent
423              * to CR-LF, we'll skip this particular contortion.
424              */
425             if (write(mboxfd,headers,oldlen) < 0)
426             {
427                 free(headers);
428                 headers = NULL;
429                 perror("gen_readmsg: writing RFC822 headers");
430                 return(PS_IOERR);
431             }
432             else if (outlevel == O_VERBOSE)
433                 fputs("#", stderr);
434             free(headers);
435             headers = NULL;
436         }
437
438         /* SMTP byte-stuffing */
439         if (*bufp == '.' && queryctl->mda[0] == 0)
440             write(mboxfd, ".", 1);
441
442         /* write this line to the file -- comment about CR-LF vs. LF applies */
443         if (write(mboxfd,bufp,strlen(bufp)) < 0)
444         {
445             perror("gen_readmsg: writing message text");
446             return(PS_IOERR);
447         }
448         else if (outlevel == O_VERBOSE)
449             fputc('*', stderr);
450
451     skipwrite:;
452
453         /* write the message size dots */
454         sizeticker += strlen(bufp);
455         while (sizeticker >= SIZETICKER)
456         {
457             if (outlevel > O_SILENT && outlevel < O_VERBOSE)
458                 fputc('.',stderr);
459             sizeticker -= SIZETICKER;
460
461             /* reset timeout so we don't choke on very long messages */
462             alarm(timeout);
463         }
464         lines++;
465     }
466
467
468     /* finish up display output */
469     if (outlevel == O_VERBOSE)
470         fprintf(stderr,"\n(%d lines of message content)\n",lines);
471     else if (outlevel > O_SILENT) 
472         fputs("\n", stderr);
473
474     if (alarmed)
475        return (0);
476     /* write message terminator */
477     if (!queryctl->mda[0])
478         if (SMTP_eom(mboxfd) != SM_OK)
479             return(PS_SMTP);
480     return(0);
481 }
482
483 /*********************************************************************
484   function:      do_protocol
485   description:   retrieve messages from the specified mail server
486                  using a given set of methods
487
488   arguments:     
489     queryctl     fully-specified options (i.e. parsed, defaults invoked,
490                  etc).
491     proto        protocol method pointer
492
493   return value:  exit code from the set of PS_.* constants defined in 
494                  fetchmail.h
495   calls:
496   globals:       reads outlevel.
497  *********************************************************************/
498
499 int do_protocol(queryctl, proto)
500 struct hostrec *queryctl;
501 struct method *proto;
502 {
503     int ok, len;
504     int mboxfd = -1;
505     char buf [POPBUFSIZE+1], host[HOSTLEN+1];
506     int socket;
507     void (*sigsave)();
508
509 #ifdef HAVE_RRESVPORT_H
510     int privport = -1;
511 #endif /* HAVE_RRESVPORT_H */
512     int num, count, deletions = 0;
513
514     alarmed = 0;
515     sigsave = signal(SIGALRM, alarm_handler);
516     alarm (timeout);
517     /* lacking methods, there are some options that may fail */
518     if (!proto->is_old)
519     {
520         /* check for unsupported options */
521         if (queryctl->flush) {
522             fprintf(stderr,
523                     "Option --flush is not supported with %s\n",
524                     proto->name);
525             alarm(0);
526             signal(SIGALRM, sigsave);
527             return(PS_SYNTAX);
528         }
529         else if (queryctl->fetchall) {
530             fprintf(stderr,
531                     "Option --all is not supported with %s\n",
532                     proto->name);
533             alarm(0);
534             signal(SIGALRM, sigsave);
535             return(PS_SYNTAX);
536         }
537     }
538
539     tagnum = 0;
540     protocol = proto;
541
542     /* open a socket to the mail server */
543     if ((socket = Socket(queryctl->servername,
544                          queryctl->port ? queryctl->port : protocol->port))<0 
545          || alarmed)
546     {
547         perror("fetchmail, connecting to host");
548         ok = PS_SOCKET;
549         goto closeUp;
550     }
551
552     /* accept greeting message from mail server */
553     ok = (protocol->parse_response)(socket, buf);
554     if (alarmed || ok != 0)
555         goto cleanUp;
556
557     /* try to get authorized to fetch mail */
558     ok = (protocol->getauth)(socket, queryctl, buf);
559     if (alarmed || ok == PS_ERROR)
560         ok = PS_AUTHFAIL;
561     if (alarmed || ok != 0)
562         goto cleanUp;
563
564     /* compute count, and get UID list if possible */
565     if ((protocol->getrange)(socket, queryctl, &count) != 0 || alarmed)
566         goto cleanUp;
567
568     /* show user how many messages we downloaded */
569     if (outlevel > O_SILENT && outlevel < O_VERBOSE)
570         if (count == 0)
571             fprintf(stderr, "No mail from %s\n", queryctl->servername);
572         else
573             fprintf(stderr,
574                     "%d message%s from %s.\n",
575                     count, count > 1 ? "s" : "", 
576                     queryctl->servername);
577
578     if (count > 0)
579     {
580         if (queryctl->mda[0] == '\0')
581             if ((mboxfd = Socket(queryctl->smtphost, SMTP_PORT)) < 0
582                 || SMTP_ok(mboxfd, NULL) != SM_OK
583                 || SMTP_helo(mboxfd, queryctl->servername) != SM_OK 
584                 || alarmed)
585             {
586                 ok = PS_SMTP;
587                 close(mboxfd);
588                 mboxfd = -1;
589                 goto cleanUp;
590             }
591     
592         /* read, forward, and delete messages */
593         for (num = 1; num <= count; num++)
594         {
595             int treat_as_new = 
596                 !protocol->is_old 
597                 || !(protocol->is_old)(socket, queryctl, num);
598
599             /* we may want to reject this message if it's old */
600             if (treat_as_new || queryctl->fetchall)
601             {
602                 /* request a message */
603                 (protocol->fetch)(socket, num, &len);
604                 if (outlevel == O_VERBOSE)
605                     if (protocol->delimited)
606                         fprintf(stderr,
607                                 "fetching message %d (delimited)\n",
608                                 num);
609                     else
610                         fprintf(stderr,
611                                 "fetching message %d (%d bytes)\n",
612                                 num, len);
613
614                 /* open the delivery pipe now if we're using an MDA */
615                 if (queryctl->mda[0])
616                     if ((mboxfd = openmailpipe(queryctl)) < 0)
617                         goto cleanUp;
618
619                 /* read the message and ship it to the output sink */
620                 ok = gen_readmsg(socket, mboxfd,
621                                  len, 
622                                  protocol->delimited,
623                                  queryctl);
624
625                 /* close the delivery pipe, we'll reopen before next message */
626                 if (queryctl->mda[0])
627                     if ((ok = closemailpipe(mboxfd)) != 0 || alarmed)
628                         goto cleanUp;
629
630                 /* tell the server we got it OK and resynchronize */
631                 if (protocol->trail)
632                     (protocol->trail)(socket, queryctl, num);
633                 if (alarmed || ok != 0)
634                     goto cleanUp;
635             }
636
637             /*
638              * At this point in flow of control, either we've bombed
639              * on a protocol error or had delivery refused by the SMTP
640              * server (unlikely -- I've never seen it) or we've seen
641              * `accepted for delivery' and the message is shipped.
642              * It's safe to delete the message on the server now.
643              */
644
645             /* maybe we delete this message now? */
646             if (protocol->delete
647                 && !queryctl->keep
648                 && (treat_as_new || queryctl->flush))
649             {
650                 deletions++;
651                 if (outlevel > O_SILENT && outlevel < O_VERBOSE) 
652                     fprintf(stderr,"flushing message %d\n", num);
653                 ok = (protocol->delete)(socket, queryctl, num);
654                 if (alarmed || ok != 0)
655                     goto cleanUp;
656             }
657         }
658
659         /* remove all messages flagged for deletion */
660         if (protocol->expunge_cmd && deletions > 0)
661         {
662             ok = gen_transact(socket, protocol->expunge_cmd);
663             if (alarmed || ok != 0)
664                 goto cleanUp;
665         }
666
667         ok = gen_transact(socket, protocol->exit_cmd);
668         if (alarmed || ok == 0)
669             ok = PS_SUCCESS;
670         close(socket);
671         goto closeUp;
672     }
673     else {
674         ok = gen_transact(socket, protocol->exit_cmd);
675         if (ok == 0)
676             ok = PS_NOMAIL;
677         close(socket);
678         goto closeUp;
679     }
680
681 cleanUp:
682     if (ok != 0 && ok != PS_SOCKET)
683     {
684         gen_transact(socket, protocol->exit_cmd);
685         close(socket);
686     }
687
688 #ifdef HAVE_RRESVPORT_H
689     if (privport != -1)
690         close(privport);        /* no big deal if this fails */
691 #endif /* HAVE_RRESVPORT_H */
692
693 closeUp:
694     if (mboxfd != -1)
695     {
696         SMTP_quit(mboxfd);
697         close(mboxfd);
698     }
699     alarm(0);
700     signal(SIGALRM, sigsave);
701     return(ok);
702 }
703
704 /*********************************************************************
705   function:      gen_send
706   description:   Assemble command in print style and send to the server
707
708   arguments:     
709     socket       socket to which the server is connected.
710     fmt          printf-style format
711
712   return value:  none.
713   calls:         SockPuts.
714   globals:       reads outlevel.
715  *********************************************************************/
716
717 void gen_send(socket, fmt, va_alist)
718 int socket;
719 const char *fmt;
720 va_dcl {
721
722   char buf [POPBUFSIZE+1];
723   va_list ap;
724
725   if (protocol->tagged)
726       (void) sprintf(buf, "%s ", GENSYM);
727   else
728       buf[0] = '\0';
729
730   va_start(ap);
731   vsprintf(buf + strlen(buf), fmt, ap);
732   va_end(ap);
733
734   SockPuts(socket, buf);
735
736   if (outlevel == O_VERBOSE)
737     fprintf(stderr,"> %s\n", buf);
738 }
739
740 /*********************************************************************
741   function:      gen_transact
742   description:   Assemble command in print style and send to the server.
743                  then accept a protocol-dependent response.
744
745   arguments:     
746     socket       socket to which the server is connected.
747     fmt          printf-style format
748
749   return value:  none.
750   calls:         SockPuts.
751   globals:       reads outlevel.
752  *********************************************************************/
753
754 int gen_transact(socket, fmt, va_alist)
755 int socket;
756 const char *fmt;
757 va_dcl {
758
759   int ok;
760   char buf [POPBUFSIZE+1];
761   va_list ap;
762
763   if (protocol->tagged)
764       (void) sprintf(buf, "%s ", GENSYM);
765   else
766       buf[0] = '\0';
767
768   va_start(ap);
769   vsprintf(buf + strlen(buf), fmt, ap);
770   va_end(ap);
771
772   SockPuts(socket, buf);
773   if (outlevel == O_VERBOSE)
774     fprintf(stderr,"> %s\n", buf);
775
776   /* we presume this does its own response echoing */
777   ok = (protocol->parse_response)(socket, buf);
778
779   return(ok);
780 }
781
782
783 /*********************************************************************
784   function:      alarm_handler
785   description:   In real life process can get stuck waiting for 
786                  something. This deadlock is avoided here by this 
787                  sending  SIGALRM
788
789   arguments:     
790     signal       hopefully SIGALRM
791
792   return value:  none.
793   calls:         none
794   globals:       sets alarmed to 1
795  *********************************************************************/
796 void 
797 alarm_handler (int signal)
798 {
799     alarmed = 1;
800     fprintf(stderr,"fetchmail: timeout after %d seconds.\n", timeout);
801 }