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