]> Pileus Git - ~andy/fetchmail/blob - fetchmail.c
Added port-specification option.
[~andy/fetchmail] / fetchmail.c
1 /* Copyright 1993-95 by Carl Harris, Jr. 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:       popclient.c
8   project:      popclient
9   programmer:   Carl Harris, ceharris@mal.com
10                 Extensively hacked and improved by esr.
11   description:  main driver module for popclient
12
13  ***********************************************************************/
14
15
16 #include <config.h>
17
18 #include <stdio.h>
19
20 #if defined(STDC_HEADERS)
21 #include <stdlib.h>
22 #include <string.h>
23 #endif
24
25 #if defined(HAVE_UNISTD_H)
26 #include <unistd.h>
27 #endif
28
29 #include <signal.h>
30 #include <pwd.h>
31 #include <errno.h>
32
33 #include <sys/types.h>
34 #include <sys/file.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38
39 #include "popclient.h"
40
41 /* release info */
42 #define         RELEASE_TAG     "3.1"
43
44 #ifdef HAVE_PROTOTYPES
45 /* prototypes for internal functions */
46 int showoptions (struct hostrec *queryctl);
47 int parseMDAargs (struct hostrec *queryctl);
48 int showversioninfo (void);
49 int dump_options (struct hostrec *queryctl);
50 int query_host(struct hostrec *queryctl);
51 #endif
52
53 /* controls the detail level of status/progress messages written to stderr */
54 int outlevel;           /* see the O_.* constants above */
55 int yydebug;            /* enable parse debugging */
56
57 /* daemon mode control */
58 int poll_interval;      /* poll interval in seconds */
59 char *logfile;          /* log file for daemon mode */
60 int quitmode;           /* if --quit was set */
61
62 /* miscellaneous global controls */
63 char *poprcfile;        /* path name of rc file */
64 char *idfile;           /* path name of id file */
65 int linelimit;          /* limit # lines retrieved per site */
66 int versioninfo;        /* emit only version info */
67
68 /* args for the MDA, parsed out in the usual fashion by parseMDAargs() */
69 char *mda_argv [32];
70
71 /*********************************************************************
72   function:      main
73   description:   main driver routine 
74   arguments:     
75     argc         argument count as passed by runtime startup code.
76     argv         argument strings as passed by runtime startup code.
77
78   return value:  an exit status code for the shell -- see the 
79                  PS_.* constants defined above.
80   calls:         parsecmdline, setdefaults, openuserfolder, doPOP2.
81   globals:       none.
82  *********************************************************************/
83
84 static void termhook();
85 static char *lockfile;
86 static int popstatus;
87 static struct hostrec *hostp, *hostlist = (struct hostrec *)NULL;
88
89 main (argc,argv)
90 int argc;
91 char **argv;
92
93     int mboxfd, st;
94     struct hostrec cmd_opts, def_opts;
95     int parsestatus;
96     char *servername; 
97     FILE        *tmpfp;
98     pid_t pid;
99
100     if (setdefaults(&def_opts) != 0)
101         exit(PS_UNDEFINED);
102
103     if ((parsestatus = parsecmdline(argc,argv,&cmd_opts)) < 0)
104         exit(PS_SYNTAX);
105
106     if (versioninfo)
107         showversioninfo();
108
109     if (prc_parse_file(poprcfile) != 0)
110         exit(PS_SYNTAX);
111
112     if (optind >= argc)
113         append_server_names(&argc, argv);
114
115     /* build in-core data list on all hosts */
116     while ((servername = getnextserver(argc, argv, &parsestatus)) != (char *)0)
117     {
118         if (strcmp(servername, "defaults") == 0)
119             continue;
120
121         hostp = (struct hostrec *)xmalloc(sizeof(struct hostrec));
122
123         prc_mergeoptions(servername, &cmd_opts, &def_opts, hostp);
124         strcpy(hostp->servername, servername);
125         parseMDAargs(hostp);
126         hostp->lastid[0] = '\0';
127
128         hostp->next = hostlist;
129         hostlist = hostp;
130     }
131
132     /* perhaps we just want to check options? */
133     if (versioninfo) {
134         printf("Taking options from command line and %s\n", poprcfile);
135         for (hostp = hostlist; hostp; hostp = hostp->next) {
136             printf("Options for host %s:\n", hostp->servername);
137             dump_params(hostp);
138         }
139         if (hostlist == NULL)
140             (void) printf("No mailservers set up -- perhaps %s is missing?\n",
141                           poprcfile);
142         exit(0);
143     }
144     else if (hostlist == NULL) {
145         (void) fputs("popclient: no mailservers have been specified.\n", stderr);
146         exit(PS_SYNTAX);
147     }
148
149     /* set up to do lock protocol */
150     umask(0077);
151     if ((lockfile = (char *) malloc( strlen(getenv("HOME")) + strlen("/.lockfetch-") + HOSTLEN)) == NULL) {
152         fprintf(stderr,"popclient: cannot allocate memory for .lockfetch, exiting.\n");
153         exit(PS_EXCLUDE);
154     }
155     strcpy(lockfile, getenv("HOME"));
156     strcat(lockfile,"/.lockfetch-");
157     gethostname(lockfile+strlen(lockfile),HOSTLEN);
158
159     /* perhaps user asked us to remove a lock */
160     if (quitmode)
161     {
162         FILE* fp;
163
164         if ( (fp = fopen(lockfile, "r")) == NULL ) {
165             fprintf(stderr,"popclient: no other popclient is running\n");
166             return(PS_EXCLUDE);
167         }
168   
169         fscanf(fp,"%d",&pid);
170         fprintf(stderr,"popclient: killing popclient at PID %d\n",pid);
171         if ( kill(pid,SIGTERM) < 0 )
172             fprintf(stderr,"popclient: error killing the process %d.\n",pid);
173         else
174             fprintf(stderr,"popclient: popclient at %d is dead.\n", pid);
175   
176         fclose(fp);
177         remove(lockfile);
178         exit(0);
179     }
180
181
182     /* beyond here we don't want more than one popclient running per user */
183     if ( (tmpfp = fopen(lockfile, "r")) != NULL ) {
184         fscanf(tmpfp,"%d",&pid);
185         fprintf(stderr,"Another session appears to be running at pid %d.\nIf you are sure that this is incorrect, remove %s file.\n",pid,lockfile);
186         fclose(tmpfp);
187         return(PS_EXCLUDE);
188     }
189
190     /* let's get stored message IDs from previous transactions */
191     if ((st = prc_filecheck(idfile)) != 0) {
192         return (st);
193     } else if ((tmpfp = fopen(idfile, "r")) != (FILE *)NULL) {
194         char buf[POPBUFSIZE+1], host[HOSTLEN+1], id[IDLEN+1];
195
196         while (fgets(buf, POPBUFSIZE, tmpfp) != (char *)NULL) {
197             if ((st = sscanf(buf, "%s %s\n", host, id)) == 2) {
198                 for (hostp = hostlist; hostp; hostp = hostp->next) {
199                     if (strcmp(host, hostp->servername) == 0)
200                         strcpy(hostp->lastid, id);
201                 }
202             }
203         }
204         fclose(tmpfp);
205     }
206
207     /*
208      * Maybe time to go to demon mode...
209      */
210     if (poll_interval)
211         daemonize(logfile, termhook);
212
213     /* if not locked, assert a lock */
214     signal(SIGABRT, termhook);
215     signal(SIGINT, termhook);
216     signal(SIGTERM, termhook);
217     signal(SIGALRM, termhook);
218     signal(SIGHUP, termhook);
219     signal(SIGPIPE, termhook);
220     signal(SIGQUIT, termhook);
221     if ( (tmpfp = fopen(lockfile,"w")) != NULL ) {
222         fprintf(tmpfp,"%d",getpid());
223         fclose(tmpfp);
224     }
225
226     /*
227      * Query all hosts. If there's only one, the error return will
228      * reflect the status of that transaction.
229      */
230     do {
231         for (hostp = hostlist; hostp; hostp = hostp->next) {
232             popstatus = query_host(hostp);
233         }
234
235         sleep(poll_interval);
236     } while
237         (poll_interval);
238
239     if (outlevel == O_VERBOSE)
240         fprintf(stderr, "normal termination, status %d\n", popstatus);
241
242     termhook(0);
243     exit(popstatus);
244 }
245
246 void termhook(int sig)
247 {
248     FILE *tmpfp;
249     int idcount = 0;
250
251     if (sig != 0)
252         fprintf(stderr, "terminated with signal %d\n", sig);
253
254     for (hostp = hostlist; hostp; hostp = hostp->next) {
255         if (hostp->lastid[0])
256             idcount++;
257     }
258
259     /* write updated last-seen IDs */
260     if (!idcount)
261         unlink(idfile);
262     else if ((tmpfp = fopen(idfile, "w")) != (FILE *)NULL) {
263         for (hostp = hostlist; hostp; hostp = hostp->next) {
264             if (hostp->lastid[0])
265                 fprintf(tmpfp, "%s %s\n", hostp->servername, hostp->lastid);
266         }
267         fclose(tmpfp);
268     }
269
270     unlink(lockfile);
271     exit(popstatus);
272 }
273
274 /*********************************************************************
275   function:      showproto
276   description:   protocol index to name mapping
277   arguments:
278     proto        protocol index
279   return value:  string name of protocol
280   calls:         none.
281   globals:       none.
282  *********************************************************************/
283
284 char *showproto(proto)
285 int proto;
286 {
287     switch (proto)
288     {
289     case P_AUTO: return("auto"); break;
290     case P_POP2: return("POP2"); break;
291     case P_POP3: return("POP3"); break;
292     case P_IMAP: return("IMAP"); break;
293     case P_APOP: return("APOP"); break;
294     case P_RPOP: return("RPOP"); break;
295     default: return("unknown?!?"); break;
296     }
297 }
298
299 /*
300  * Sequence of protocols to try when autoprobing
301  */
302 static const int autoprobe[] = {P_POP3, P_IMAP, P_POP2};
303
304 int query_host(queryctl)
305 /* perform fetch transaction with single host */
306 struct hostrec *queryctl;
307 {
308     int i, st;
309
310     if (outlevel != O_SILENT)
311     {
312         time_t now;
313
314         time(&now);
315         fprintf(stderr, "popclient: querying %s (protocol %s) at %s",
316             queryctl->servername, showproto(queryctl->protocol), ctime(&now));
317     }
318     switch (queryctl->protocol) {
319     case P_AUTO:
320         for (i = 0; i < sizeof(autoprobe)/sizeof(autoprobe[0]); i++)
321         {
322             queryctl->protocol = autoprobe[i];
323             if ((st = query_host(queryctl)) == PS_SUCCESS || st == PS_NOMAIL)
324                 break;
325         }
326         queryctl->protocol = P_AUTO;
327         return(st);
328         break;
329     case P_POP2:
330         return(doPOP2(queryctl));
331         break;
332     case P_POP3:
333     case P_APOP:
334         return(doPOP3bis(queryctl));
335         break;
336     case P_IMAP:
337         return(doIMAP(queryctl));
338         break;
339     default:
340         fprintf(stderr,"popclient: unsupported protocol selected.\n");
341         return(PS_PROTOCOL);
342     }
343 }
344  
345 /*********************************************************************
346   function:      showversioninfo
347   description:   display program release and compiler info
348   arguments:     none.
349   return value:  none.
350   calls:         none.
351   globals:       none.
352  *********************************************************************/
353
354 int showversioninfo()
355 {
356     printf("This is popclient release %s\n",RELEASE_TAG);
357 }
358
359 /*********************************************************************
360   function:      dump_params
361   description:   display program options in English
362   arguments:
363     queryctl      merged options
364
365   return value:  none.
366   calls:         none.
367   globals:       linelimit, outlimit.
368 *********************************************************************/
369
370 int dump_params (queryctl)
371 struct hostrec *queryctl;
372 {
373     char *cp;
374
375     printf("  Username = '%s'\n", queryctl->remotename);
376     printf("  Password = '%s'\n", queryctl->password);
377     printf("  Protocol is %s", showproto(queryctl->protocol));
378     if (queryctl->port)
379         printf(" (using port %d)", queryctl->port);
380     else if (outlevel == O_VERBOSE)
381         printf(" (using default port)");
382     putchar('\n');
383
384     printf("  Fetched messages will%s be kept on the server (--keep %s).\n",
385            queryctl->keep ? "" : " not",
386            queryctl->keep ? "on" : "off");
387     printf("  %s messages will be retrieved (--all %s).\n",
388            queryctl->fetchall ? "All" : "Only new",
389            queryctl->fetchall ? "on" : "off");
390     printf("  Old messages will%s be flushed before message retrieval (--flush %s).\n",
391            queryctl->flush ? "" : " not",
392            queryctl->flush ? "on" : "off");
393     printf("  Rewrite of host-local addresses is %sabled (--norewrite %s)\n",
394            queryctl->norewrite ? "dis" : "en",
395            queryctl->norewrite ? "on" : "off");
396
397
398     switch(queryctl->output)
399     {
400     case TO_SMTP:
401         printf("  Messages will be SMTP-forwarded to '%s'\n", queryctl->smtphost);
402         break;
403     case TO_FOLDER:
404         printf("  Messages will be appended to '%s'\n", queryctl->userfolder);
405         break;
406     case TO_MDA:
407         printf("  Messages will be delivered with");
408         for (cp = queryctl->mda; *cp; cp += strlen(cp) + 1) {
409             printf(" %s", cp);
410         }
411         putchar('\n');
412         break;
413     case TO_STDOUT:
414         printf("  Messages will be dumped to standard output\n");
415     default:
416         printf("  Message destination unknown?!?\n");
417     }
418     if (outlevel == O_VERBOSE)
419     {
420         if (queryctl->smtphost[0] != '\0' && queryctl->output != TO_SMTP)
421             printf("  (SMTP host would have been '%s')\n", queryctl->smtphost);
422         if (queryctl->output != TO_FOLDER)
423             printf("  (Mail folder would have been '%s')\n", queryctl->userfolder);
424         if (queryctl->output != TO_MDA)
425         {
426             printf("  (MDA would have been");
427             for (cp = queryctl->mda; *cp; cp += strlen(cp) + 1) {
428                 printf(" %s", cp);
429             }
430             printf(")\n");
431         }
432     }
433
434     if (linelimit == 0)
435         printf("  No limit on retrieved message length.\n");
436     else
437         printf("  Text retrieved per message will be at most %d bytes.\n",
438                linelimit);
439     if (queryctl->lastid[0])
440         printf("  ID of last message retrieved %s\n", queryctl->lastid);
441 }
442
443 /*********************************************************************
444   function:      openuserfolder
445   description:   open the file to which the retrieved messages will
446                  be appended.  Write-lock the folder if possible.
447
448   arguments:     
449     queryctl     fully-determined options (i.e. parsed, defaults invoked,
450                  etc).
451
452   return value:  file descriptor for the open file, else -1.
453   calls:         none.
454   globals:       none.
455  *********************************************************************/
456
457 int openuserfolder (queryctl)
458 struct hostrec *queryctl;
459 {
460     int fd;
461
462     if (queryctl->output == TO_STDOUT)
463         return(1);
464     else    /* queryctl->output == TO_FOLDER */
465         if ((fd = open(queryctl->userfolder,O_CREAT|O_WRONLY|O_APPEND,0600)) >= 0) {
466 #ifdef HAVE_FLOCK
467             if (flock(fd, LOCK_EX) == -1)
468             {
469                 close(fd);
470                 fd = -1;
471             }
472 #endif /* HAVE_FLOCK */
473             return(fd);
474         }
475         else {
476             perror("popclient: openuserfolder: open()");
477             return(-1);
478         }
479   
480 }
481
482
483
484 /*********************************************************************
485   function:      openmailpipe
486   description:   open a one-way pipe to the mail delivery agent.
487   arguments:     
488     queryctl     fully-determined options (i.e. parsed, defaults invoked,
489                  etc).
490
491   return value:  open file descriptor for the pipe or -1.
492   calls:         none.
493   globals:       reads mda_argv.
494  *********************************************************************/
495
496 int openmailpipe (queryctl)
497 struct hostrec *queryctl;
498 {
499     int pipefd [2];
500     int childpid;
501     char binmailargs [80];
502
503     if (pipe(pipefd) < 0) {
504         perror("popclient: openmailpipe: pipe");
505         return(-1);
506     }
507     if ((childpid = fork()) < 0) {
508         perror("popclient: openmailpipe: fork");
509         return(-1);
510     }
511     else if (childpid == 0) {
512
513         /* in child process space */
514         close(pipefd[1]);  /* close the 'write' end of the pipe */
515         close(0);          /* get rid of inherited stdin */
516         if (dup(pipefd[0]) != 0) {
517             fputs("popclient: openmailpipe: dup() failed\n",stderr);
518             exit(1);
519         }
520
521         execv(queryctl->mda, mda_argv+1);
522
523         /* if we got here, an error occurred */
524         perror("popclient: openmailpipe: exec");
525         return(-1);
526
527     }
528
529     /* in the parent process space */
530     close(pipefd[0]);  /* close the 'read' end of the pipe */
531     return(pipefd[1]);
532 }
533
534
535
536 /*********************************************************************
537   function:      closeuserfolder
538   description:   close the user-specified mail folder.
539   arguments:     
540     fd           mail folder descriptor.
541
542   return value:  zero if success else -1.
543   calls:         none.
544   globals:       none.
545  *********************************************************************/
546
547 int closeuserfolder(fd)
548 int fd;
549 {
550     int err;
551
552     if (fd != 1) {   /* not stdout */
553         err = close(fd);
554     }   
555     else
556         err = 0;
557   
558     if (err)
559         perror("popclient: closeuserfolder: close");
560
561     return(err);
562 }
563
564
565
566 /*********************************************************************
567   function:      closemailpipe
568   description:   close pipe to the mail delivery agent.
569   arguments:     
570     queryctl     fully-determined options record
571     fd           pipe descriptor.
572
573   return value:  0 if success, else -1.
574   calls:         none.
575   globals:       none.
576  *********************************************************************/
577
578 int closemailpipe (fd)
579 int fd;
580 {
581     int err;
582     int childpid;
583
584     if (outlevel == O_VERBOSE)
585         fprintf(stderr, "about to close pipe %d\n", fd);
586
587     err = close(fd);
588 #if defined(STDC_HEADERS)
589     childpid = wait(NULL);
590 #else
591     childpid = wait((int *) 0);
592 #endif
593     if (err)
594         perror("popclient: closemailpipe: close");
595
596     if (outlevel == O_VERBOSE)
597         fprintf(stderr, "closed pipe %d\n", fd);
598   
599     return(err);
600 }
601
602
603
604 /*********************************************************************
605   function:      parseMDAargs
606   description:   parse the argument string given in agent option into
607                  a regular *argv[] array.
608   arguments:
609     queryctl     fully-determined options record pointer.
610
611   return value:  none.
612   calls:         none.
613   globals:       writes mda_argv.
614  *********************************************************************/
615
616 int parseMDAargs (queryctl)
617 struct hostrec *queryctl;
618 {
619     int argi;
620     char *argp;
621
622     /* first put the last segment of the MDA pathname in argv[0] */
623     argp = strrchr(queryctl->mda, '/');
624     mda_argv[0] = argp ? (argp + 1) : queryctl->mda;
625   
626     argp = queryctl->mda;
627     while (*argp != '\0' && isspace(*argp))     /* skip null first arg */
628         argp++;                                 
629
630     /* now punch nulls into the delimiting whitespace in the args */
631     for (argi = 1;  
632          *argp != '\0';
633          argi++) {
634
635         mda_argv[argi] = argp;     /* store pointer to this argument */
636
637         /* find end of this argument */
638         while (!(*argp == '\0' || isspace(*argp)))
639             argp++;
640
641         /* punch in a null terminator */
642         if (*argp != '\0')
643             *(argp++) = '\0';  
644     }
645     mda_argv[argi] = (char *) 0;
646
647 }
648
649