1 /* Copyright 1993-95 by Carl Harris, Jr. Copyright 1996 by Eric S. Raymond
3 * For license terms, see the file COPYING in this directory.
6 /***********************************************************************
9 programmer: Carl Harris, ceharris@mal.com
10 Extensively hacked and improved by esr.
11 description: main driver module for popclient
13 ***********************************************************************/
20 #if defined(STDC_HEADERS)
25 #if defined(HAVE_UNISTD_H)
33 #include <sys/types.h>
39 #include "popclient.h"
42 #define RELEASE_TAG "3.1"
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);
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 */
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 */
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 */
68 /* args for the MDA, parsed out in the usual fashion by parseMDAargs() */
71 /*********************************************************************
73 description: main driver routine
75 argc argument count as passed by runtime startup code.
76 argv argument strings as passed by runtime startup code.
78 return value: an exit status code for the shell -- see the
79 PS_.* constants defined above.
80 calls: parsecmdline, setdefaults, openuserfolder, doPOP2.
82 *********************************************************************/
84 static void termhook();
85 static char *lockfile;
87 static struct hostrec *hostp, *hostlist = (struct hostrec *)NULL;
94 struct hostrec cmd_opts, def_opts;
100 if (setdefaults(&def_opts) != 0)
103 if ((parsestatus = parsecmdline(argc,argv,&cmd_opts)) < 0)
109 if (prc_parse_file(poprcfile) != 0)
113 append_server_names(&argc, argv);
115 /* build in-core data list on all hosts */
116 while ((servername = getnextserver(argc, argv, &parsestatus)) != (char *)0)
118 if (strcmp(servername, "defaults") == 0)
121 hostp = (struct hostrec *)xmalloc(sizeof(struct hostrec));
123 prc_mergeoptions(servername, &cmd_opts, &def_opts, hostp);
124 strcpy(hostp->servername, servername);
126 hostp->lastid[0] = '\0';
128 hostp->next = hostlist;
132 /* perhaps we just want to check options? */
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);
139 if (hostlist == NULL)
140 (void) printf("No mailservers set up -- perhaps %s is missing?\n",
144 else if (hostlist == NULL) {
145 (void) fputs("popclient: no mailservers have been specified.\n", stderr);
149 /* set up to do lock protocol */
151 if ((lockfile = (char *) malloc( strlen(getenv("HOME")) + strlen("/.lockfetch-") + HOSTLEN)) == NULL) {
152 fprintf(stderr,"popclient: cannot allocate memory for .lockfetch, exiting.\n");
155 strcpy(lockfile, getenv("HOME"));
156 strcat(lockfile,"/.lockfetch-");
157 gethostname(lockfile+strlen(lockfile),HOSTLEN);
159 /* perhaps user asked us to remove a lock */
164 if ( (fp = fopen(lockfile, "r")) == NULL ) {
165 fprintf(stderr,"popclient: no other popclient is running\n");
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);
174 fprintf(stderr,"popclient: popclient at %d is dead.\n", pid);
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);
190 /* let's get stored message IDs from previous transactions */
191 if ((st = prc_filecheck(idfile)) != 0) {
193 } else if ((tmpfp = fopen(idfile, "r")) != (FILE *)NULL) {
194 char buf[POPBUFSIZE+1], host[HOSTLEN+1], id[IDLEN+1];
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);
208 * Maybe time to go to demon mode...
211 daemonize(logfile, termhook);
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());
227 * Query all hosts. If there's only one, the error return will
228 * reflect the status of that transaction.
231 for (hostp = hostlist; hostp; hostp = hostp->next) {
232 popstatus = query_host(hostp);
235 sleep(poll_interval);
239 if (outlevel == O_VERBOSE)
240 fprintf(stderr, "normal termination, status %d\n", popstatus);
246 void termhook(int sig)
252 fprintf(stderr, "terminated with signal %d\n", sig);
254 for (hostp = hostlist; hostp; hostp = hostp->next) {
255 if (hostp->lastid[0])
259 /* write updated last-seen IDs */
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);
274 /*********************************************************************
276 description: protocol index to name mapping
279 return value: string name of protocol
282 *********************************************************************/
284 char *showproto(proto)
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;
300 * Sequence of protocols to try when autoprobing
302 static const int autoprobe[] = {P_POP3, P_IMAP, P_POP2};
304 int query_host(queryctl)
305 /* perform fetch transaction with single host */
306 struct hostrec *queryctl;
310 if (outlevel != O_SILENT)
315 fprintf(stderr, "popclient: querying %s (protocol %s) at %s",
316 queryctl->servername, showproto(queryctl->protocol), ctime(&now));
318 switch (queryctl->protocol) {
320 for (i = 0; i < sizeof(autoprobe)/sizeof(autoprobe[0]); i++)
322 queryctl->protocol = autoprobe[i];
323 if ((st = query_host(queryctl)) == PS_SUCCESS || st == PS_NOMAIL)
326 queryctl->protocol = P_AUTO;
330 return(doPOP2(queryctl));
334 return(doPOP3bis(queryctl));
337 return(doIMAP(queryctl));
340 fprintf(stderr,"popclient: unsupported protocol selected.\n");
345 /*********************************************************************
346 function: showversioninfo
347 description: display program release and compiler info
352 *********************************************************************/
354 int showversioninfo()
356 printf("This is popclient release %s\n",RELEASE_TAG);
359 /*********************************************************************
360 function: dump_params
361 description: display program options in English
363 queryctl merged options
367 globals: linelimit, outlimit.
368 *********************************************************************/
370 int dump_params (queryctl)
371 struct hostrec *queryctl;
375 printf(" Username = '%s'\n", queryctl->remotename);
376 printf(" Password = '%s'\n", queryctl->password);
377 printf(" Protocol is %s", showproto(queryctl->protocol));
379 printf(" (using port %d)", queryctl->port);
380 else if (outlevel == O_VERBOSE)
381 printf(" (using default port)");
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");
398 switch(queryctl->output)
401 printf(" Messages will be SMTP-forwarded to '%s'\n", queryctl->smtphost);
404 printf(" Messages will be appended to '%s'\n", queryctl->userfolder);
407 printf(" Messages will be delivered with");
408 for (cp = queryctl->mda; *cp; cp += strlen(cp) + 1) {
414 printf(" Messages will be dumped to standard output\n");
416 printf(" Message destination unknown?!?\n");
418 if (outlevel == O_VERBOSE)
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)
426 printf(" (MDA would have been");
427 for (cp = queryctl->mda; *cp; cp += strlen(cp) + 1) {
435 printf(" No limit on retrieved message length.\n");
437 printf(" Text retrieved per message will be at most %d bytes.\n",
439 if (queryctl->lastid[0])
440 printf(" ID of last message retrieved %s\n", queryctl->lastid);
443 /*********************************************************************
444 function: openuserfolder
445 description: open the file to which the retrieved messages will
446 be appended. Write-lock the folder if possible.
449 queryctl fully-determined options (i.e. parsed, defaults invoked,
452 return value: file descriptor for the open file, else -1.
455 *********************************************************************/
457 int openuserfolder (queryctl)
458 struct hostrec *queryctl;
462 if (queryctl->output == TO_STDOUT)
464 else /* queryctl->output == TO_FOLDER */
465 if ((fd = open(queryctl->userfolder,O_CREAT|O_WRONLY|O_APPEND,0600)) >= 0) {
467 if (flock(fd, LOCK_EX) == -1)
472 #endif /* HAVE_FLOCK */
476 perror("popclient: openuserfolder: open()");
484 /*********************************************************************
485 function: openmailpipe
486 description: open a one-way pipe to the mail delivery agent.
488 queryctl fully-determined options (i.e. parsed, defaults invoked,
491 return value: open file descriptor for the pipe or -1.
493 globals: reads mda_argv.
494 *********************************************************************/
496 int openmailpipe (queryctl)
497 struct hostrec *queryctl;
501 char binmailargs [80];
503 if (pipe(pipefd) < 0) {
504 perror("popclient: openmailpipe: pipe");
507 if ((childpid = fork()) < 0) {
508 perror("popclient: openmailpipe: fork");
511 else if (childpid == 0) {
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);
521 execv(queryctl->mda, mda_argv+1);
523 /* if we got here, an error occurred */
524 perror("popclient: openmailpipe: exec");
529 /* in the parent process space */
530 close(pipefd[0]); /* close the 'read' end of the pipe */
536 /*********************************************************************
537 function: closeuserfolder
538 description: close the user-specified mail folder.
540 fd mail folder descriptor.
542 return value: zero if success else -1.
545 *********************************************************************/
547 int closeuserfolder(fd)
552 if (fd != 1) { /* not stdout */
559 perror("popclient: closeuserfolder: close");
566 /*********************************************************************
567 function: closemailpipe
568 description: close pipe to the mail delivery agent.
570 queryctl fully-determined options record
573 return value: 0 if success, else -1.
576 *********************************************************************/
578 int closemailpipe (fd)
584 if (outlevel == O_VERBOSE)
585 fprintf(stderr, "about to close pipe %d\n", fd);
588 #if defined(STDC_HEADERS)
589 childpid = wait(NULL);
591 childpid = wait((int *) 0);
594 perror("popclient: closemailpipe: close");
596 if (outlevel == O_VERBOSE)
597 fprintf(stderr, "closed pipe %d\n", fd);
604 /*********************************************************************
605 function: parseMDAargs
606 description: parse the argument string given in agent option into
607 a regular *argv[] array.
609 queryctl fully-determined options record pointer.
613 globals: writes mda_argv.
614 *********************************************************************/
616 int parseMDAargs (queryctl)
617 struct hostrec *queryctl;
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;
626 argp = queryctl->mda;
627 while (*argp != '\0' && isspace(*argp)) /* skip null first arg */
630 /* now punch nulls into the delimiting whitespace in the args */
635 mda_argv[argi] = argp; /* store pointer to this argument */
637 /* find end of this argument */
638 while (!(*argp == '\0' || isspace(*argp)))
641 /* punch in a null terminator */
645 mda_argv[argi] = (char *) 0;