6b026c6ecf57161d331ff0ba643198ec47c4a0cf
[mmh] / uip / popsbr.c
1
2 /*
3  * popsbr.c -- POP client subroutines
4  *
5  * $Id$
6  */
7
8 #include <h/mh.h>
9
10 #if defined(NNTP) && !defined(PSHSBR)
11 # undef NNTP
12 #endif
13
14 #ifdef NNTP                     /* building pshsbr.o from popsbr.c */
15 # include <h/nntp.h>
16 #endif /* NNTP */
17
18 #if !defined(NNTP) && defined(APOP)
19 # include <h/md5.h>
20 #endif
21
22 #include <h/popsbr.h>
23 #include <h/signals.h>
24 #include <signal.h>
25
26 #define TRM     "."
27 #define TRMLEN  (sizeof TRM - 1)
28
29 extern int errno;
30
31 static int poprint = 0;
32 static int pophack = 0;
33
34 char response[BUFSIZ];
35
36 static FILE *input;
37 static FILE *output;
38
39 #define targ_t char *
40
41 #if !defined(NNTP) && defined(MPOP)
42 # define command pop_command
43 # define multiline pop_multiline
44 #endif
45
46 #ifdef NNTP
47 # ifdef BPOP    /* stupid */
48 static int xtnd_last = -1;
49 static int xtnd_first = 0;
50 static char xtnd_name[512];     /* INCREDIBLE HACK!! */
51 # endif
52 #endif /* NNTP */
53
54 /*
55  * static prototypes
56  */
57 #if !defined(NNTP) && defined(APOP)
58 static char *pop_auth (char *, char *);
59 #endif
60
61 #if defined(NNTP) || !defined(MPOP)
62 /* otherwise they are not static functions */
63 static int command(const char *, ...);
64 static int multiline(void);
65 #endif
66
67 static int traverse (int (*)(), const char *, ...);
68 static int vcommand(const char *, va_list);
69 static int getline (char *, int, FILE *);
70 static int putline (char *, FILE *);
71
72
73 #if !defined(NNTP) && defined(APOP)
74 static char *
75 pop_auth (char *user, char *pass)
76 {
77     int len, buflen;
78     char *cp, *lp;
79     unsigned char *dp, *ep, digest[16];
80     MD5_CTX mdContext;
81     static char buffer[BUFSIZ];
82
83     if ((cp = strchr (response, '<')) == NULL
84             || (lp = strchr (cp, '>')) == NULL) {
85         snprintf (buffer, sizeof(buffer), "APOP not available: %s", response);
86         strncpy (response, buffer, sizeof(response));
87         return NULL;
88     }
89
90     *++lp = NULL;
91     snprintf (buffer, sizeof(buffer), "%s%s", cp, pass);
92
93     MD5Init (&mdContext);
94     MD5Update (&mdContext, (unsigned char *) buffer,
95                (unsigned int) strlen (buffer));
96     MD5Final (digest, &mdContext);
97
98     cp = buffer;
99     buflen = sizeof(buffer);
100
101     snprintf (cp, buflen, "%s ", user);
102     len = strlen (cp);
103     cp += len;
104     buflen -= len;
105
106     for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]); dp < ep; ) {
107         snprintf (cp, buflen, "%02x", *dp++ & 0xff);
108         cp += 2;
109         buflen -= 2;
110     }
111     *cp = NULL;
112
113     return buffer;
114 }
115 #endif  /* !NNTP && APOP */
116
117
118 int
119 pop_init (char *host, char *user, char *pass, int snoop, int rpop)
120 {
121     int fd1, fd2;
122     char buffer[BUFSIZ];
123
124 #ifdef APOP
125     int apop;
126
127     if ((apop = rpop) < 0)
128         rpop = 0;
129 #endif
130
131 #ifndef NNTP
132 # ifndef KPOP
133     if ((fd1 = client (host, "tcp", POPSERVICE, rpop, response, sizeof(response))) == NOTOK)
134 # else  /* KPOP */
135     snprintf (buffer, sizeof(buffer), "%s/%s", KPOP_PRINCIPAL, POPSERVICE);
136     if ((fd1 = client (host, "tcp", buffer, rpop, response, sizeof(response))) == NOTOK)
137 # endif
138 #else   /* NNTP */
139     if ((fd1 = client (host, "tcp", "nntp", rpop, response, sizeof(response))) == NOTOK)
140 #endif
141         return NOTOK;
142
143     if ((fd2 = dup (fd1)) == NOTOK) {
144         char *s;
145
146         if ((s = strerror(errno)))
147             snprintf (response, sizeof(response),
148                 "unable to dup connection descriptor: %s", s);
149         else
150             snprintf (response, sizeof(response),
151                 "unable to dup connection descriptor: unknown error");
152         close (fd1);
153         return NOTOK;
154     }
155 #ifndef NNTP
156     if (pop_set (fd1, fd2, snoop) == NOTOK)
157 #else   /* NNTP */
158     if (pop_set (fd1, fd2, snoop, (char *)0) == NOTOK)
159 #endif  /* NNTP */
160         return NOTOK;
161
162     SIGNAL (SIGPIPE, SIG_IGN);
163
164     switch (getline (response, sizeof response, input)) {
165         case OK: 
166             if (poprint)
167                 fprintf (stderr, "<--- %s\n", response);
168 #ifndef NNTP
169             if (*response == '+') {
170 # ifndef KPOP
171 #  ifdef APOP
172                 if (apop < 0) {
173                     char *cp = pop_auth (user, pass);
174
175                     if (cp && command ("APOP %s", cp) != NOTOK)
176                         return OK;
177                 }
178                 else
179 #  endif /* APOP */
180                 if (command ("USER %s", user) != NOTOK
181                     && command ("%s %s", rpop ? "RPOP" : (pophack++, "PASS"),
182                                         pass) != NOTOK)
183                 return OK;
184 # else /* KPOP */
185                 if (command ("USER %s", user) != NOTOK
186                     && command ("PASS %s", pass) != NOTOK)
187                 return OK;
188 # endif
189             }
190 #else /* NNTP */
191             if (*response < CHAR_ERR) {
192                 command ("MODE READER");
193                 return OK;
194             }
195 #endif
196             strncpy (buffer, response, sizeof(buffer));
197             command ("QUIT");
198             strncpy (response, buffer, sizeof(response));
199                                 /* and fall */
200
201         case NOTOK: 
202         case DONE: 
203             if (poprint)            
204                 fprintf (stderr, "%s\n", response);
205             fclose (input);
206             fclose (output);
207             return NOTOK;
208     }
209
210     return NOTOK;       /* NOTREACHED */
211 }
212
213 #ifdef NNTP
214 int
215 pop_set (int in, int out, int snoop, char *myname)
216 #else
217 int
218 pop_set (int in, int out, int snoop)
219 #endif
220 {
221
222 #ifdef NNTP
223     if (myname && *myname) {
224         /* interface from bbc to msh */
225         strncpy (xtnd_name, myname, sizeof(xtnd_name));
226     }
227 #endif  /* NNTP */
228
229     if ((input = fdopen (in, "r")) == NULL
230             || (output = fdopen (out, "w")) == NULL) {
231         strncpy (response, "fdopen failed on connection descriptor", sizeof(response));
232         if (input)
233             fclose (input);
234         else
235             close (in);
236         close (out);
237         return NOTOK;
238     }
239
240     poprint = snoop;
241
242     return OK;
243 }
244
245
246 int
247 pop_fd (char *in, int inlen, char *out, int outlen)
248 {
249     snprintf (in, inlen, "%d", fileno (input));
250     snprintf (out, outlen, "%d", fileno (output));
251     return OK;
252 }
253
254
255 /*
256  * Find out number of messages available
257  * and their total size.
258  */
259
260 int
261 pop_stat (int *nmsgs, int *nbytes)
262 {
263 #ifdef NNTP
264     char **ap;
265 #endif /* NNTP */
266
267 #ifndef NNTP
268     if (command ("STAT") == NOTOK)
269         return NOTOK;
270
271     *nmsgs = *nbytes = 0;
272     sscanf (response, "+OK %d %d", nmsgs, nbytes);
273
274 #else /* NNTP */
275     if (xtnd_last < 0) {        /* in msh, xtnd_name is set from myname */
276         if (command("GROUP %s", xtnd_name) == NOTOK)
277             return NOTOK;
278
279         ap = brkstring (response, " ", "\n"); /* "211 nart first last ggg" */
280         xtnd_first = atoi (ap[2]);
281         xtnd_last  = atoi (ap[3]);
282     }
283
284     /* nmsgs is not the real nart, but an incredible simuation */
285     if (xtnd_last > 0)
286         *nmsgs = xtnd_last - xtnd_first + 1;    /* because of holes... */
287     else
288         *nmsgs = 0;
289     *nbytes = xtnd_first;       /* for subtracting offset in msh() */
290 #endif /* NNTP */
291
292     return OK;
293 }
294
295 #ifdef NNTP
296 int
297 pop_exists (int (*action)())
298 {
299 #ifdef XMSGS            /* hacked into NNTP 1.5 */
300     if (traverse (action, "XMSGS %d-%d", (targ_t) xtnd_first, (targ_t) xtnd_last) == OK)
301         return OK;
302 #endif
303     /* provided by INN 1.4 */
304     if (traverse (action, "LISTGROUP") == OK)
305         return OK;
306     return traverse (action, "XHDR NONAME %d-%d", (targ_t) xtnd_first, (targ_t) xtnd_last);
307 }
308 #endif  /* NNTP */
309
310
311 #ifdef BPOP
312 int
313 pop_list (int msgno, int *nmsgs, int *msgs, int *bytes, int *ids)
314 #else
315 int
316 pop_list (int msgno, int *nmsgs, int *msgs, int *bytes)
317 #endif
318 {
319     int i;
320 #ifndef BPOP
321     int *ids = NULL;
322 #endif
323
324     if (msgno) {
325 #ifndef NNTP
326         if (command ("LIST %d", msgno) == NOTOK)
327             return NOTOK;
328         *msgs = *bytes = 0;
329         if (ids) {
330             *ids = 0;
331             sscanf (response, "+OK %d %d %d", msgs, bytes, ids);
332         }
333         else
334             sscanf (response, "+OK %d %d", msgs, bytes);
335 #else /* NNTP */
336         *msgs = *bytes = 0;
337         if (command ("STAT %d", msgno) == NOTOK) 
338             return NOTOK;
339         if (ids) {
340             *ids = msgno;
341         }
342 #endif /* NNTP */
343         return OK;
344     }
345
346 #ifndef NNTP
347     if (command ("LIST") == NOTOK)
348         return NOTOK;
349
350     for (i = 0; i < *nmsgs; i++)
351         switch (multiline ()) {
352             case NOTOK: 
353                 return NOTOK;
354             case DONE: 
355                 *nmsgs = ++i;
356                 return OK;
357             case OK: 
358                 *msgs = *bytes = 0;
359                 if (ids) {
360                     *ids = 0;
361                     sscanf (response, "%d %d %d",
362                             msgs++, bytes++, ids++);
363                 }
364                 else
365                     sscanf (response, "%d %d", msgs++, bytes++);
366                 break;
367         }
368     for (;;)
369         switch (multiline ()) {
370             case NOTOK: 
371                 return NOTOK;
372             case DONE: 
373                 return OK;
374             case OK: 
375                 break;
376         }
377 #else /* NNTP */
378     return NOTOK;
379 #endif /* NNTP */
380 }
381
382
383 int
384 pop_retr (int msgno, int (*action)())
385 {
386 #ifndef NNTP
387     return traverse (action, "RETR %d", (targ_t) msgno);
388 #else /* NNTP */
389     return traverse (action, "ARTICLE %d", (targ_t) msgno);
390 #endif /* NNTP */
391 }
392
393
394 static int
395 traverse (int (*action)(), const char *fmt, ...)
396 {
397     int result;
398     va_list ap;
399     char buffer[sizeof(response)];
400
401     va_start(ap, fmt);
402     result = vcommand (fmt, ap);
403     va_end(ap);
404
405     if (result == NOTOK)
406         return NOTOK;
407     strncpy (buffer, response, sizeof(buffer));
408
409     for (;;)
410         switch (multiline ()) {
411             case NOTOK: 
412                 return NOTOK;
413
414             case DONE: 
415                 strncpy (response, buffer, sizeof(response));
416                 return OK;
417
418             case OK: 
419                 (*action) (response);
420                 break;
421         }
422 }
423
424
425 int
426 pop_dele (int msgno)
427 {
428     return command ("DELE %d", msgno);
429 }
430
431
432 int
433 pop_noop (void)
434 {
435     return command ("NOOP");
436 }
437
438
439 #if defined(MPOP) && !defined(NNTP)
440 int
441 pop_last (void)
442 {
443     return command ("LAST");
444 }
445 #endif
446
447
448 int
449 pop_rset (void)
450 {
451     return command ("RSET");
452 }
453
454
455 int
456 pop_top (int msgno, int lines, int (*action)())
457 {
458 #ifndef NNTP
459     return traverse (action, "TOP %d %d", (targ_t) msgno, (targ_t) lines);
460 #else   /* NNTP */
461     return traverse (action, "HEAD %d", (targ_t) msgno);
462 #endif /* NNTP */
463 }
464
465
466 #ifdef BPOP
467 int
468 pop_xtnd (int (*action)(), char *fmt, ...)
469 {
470     int result;
471     va_list ap;
472     char buffer[BUFSIZ];
473
474 #ifdef NNTP
475     char **ap;
476 #endif
477
478     va_start(ap, fmt);
479 #ifndef NNTP
480     /* needs to be fixed... va_end needs to be added */
481     snprintf (buffer, sizeof(buffer), "XTND %s", fmt);
482     result = traverse (action, buffer, a, b, c, d);
483     va_end(ap);
484     return result;
485 #else /* NNTP */
486     snprintf (buffer, sizeof(buffer), fmt, a, b, c, d);
487     ap = brkstring (buffer, " ", "\n"); /* a hack, i know... */
488
489     if (!strcasecmp(ap[0], "x-bboards")) {      /* XTND "X-BBOARDS group */
490         /* most of these parameters are meaningless under NNTP. 
491          * bbc.c was modified to set AKA and LEADERS as appropriate,
492          * the rest are left blank.
493          */
494         return OK;
495     }
496     if (!strcasecmp (ap[0], "archive") && ap[1]) {
497         snprintf (xtnd_name, sizeof(xtnd_name), "%s", ap[1]);   /* save the name */
498         xtnd_last = 0;
499         xtnd_first = 1;         /* setup to fail in pop_stat */
500         return OK;
501     }
502     if (!strcasecmp (ap[0], "bboards")) {
503
504         if (ap[1]) {                    /* XTND "BBOARDS group" */
505             snprintf (xtnd_name, sizeof(xtnd_name), "%s", ap[1]);       /* save the name */
506             if (command("GROUP %s", xtnd_name) == NOTOK)
507                 return NOTOK;
508
509             /* action must ignore extra args */
510             strncpy (buffer, response, sizeof(buffer));
511             ap = brkstring (response, " ", "\n");/* "211 nart first last g" */
512             xtnd_first = atoi (ap[2]);
513             xtnd_last  = atoi (ap[3]);
514
515             (*action) (buffer);         
516             return OK;
517
518         } else {                /* XTND "BBOARDS" */
519             return traverse (action, "LIST", a, b, c, d);
520         }
521     }
522     return NOTOK;       /* unknown XTND command */
523 #endif /* NNTP */
524 }
525 #endif BPOP
526
527
528 int
529 pop_quit (void)
530 {
531     int i;
532
533     i = command ("QUIT");
534     pop_done ();
535
536     return i;
537 }
538
539
540 int
541 pop_done (void)
542 {
543     fclose (input);
544     fclose (output);
545
546     return OK;
547 }
548
549
550 #if !defined(MPOP) || defined(NNTP)
551 static
552 #endif
553 int
554 command(const char *fmt, ...)
555 {
556     va_list ap;
557     int result;
558
559     va_start(ap, fmt);
560     result = vcommand(fmt, ap);
561     va_end(ap);
562
563     return result;
564 }
565
566
567 static int
568 vcommand (const char *fmt, va_list ap)
569 {
570     char *cp, buffer[BUFSIZ];
571
572     vsnprintf (buffer, sizeof(buffer), fmt, ap);
573     if (poprint)
574         if (pophack) {
575             if ((cp = strchr (buffer, ' ')))
576                 *cp = 0;
577             fprintf (stderr, "---> %s ********\n", buffer);
578             if (cp)
579                 *cp = ' ';
580             pophack = 0;
581         }
582         else
583             fprintf (stderr, "---> %s\n", buffer);
584
585     if (putline (buffer, output) == NOTOK)
586         return NOTOK;
587
588     switch (getline (response, sizeof response, input)) {
589         case OK: 
590             if (poprint)
591                 fprintf (stderr, "<--- %s\n", response);
592 #ifndef NNTP
593             return (*response == '+' ? OK : NOTOK);
594 #else   /* NNTP */
595             return (*response < CHAR_ERR ? OK : NOTOK);
596 #endif  /* NNTP */
597
598         case NOTOK: 
599         case DONE: 
600             if (poprint)            
601                 fprintf (stderr, "%s\n", response);
602             return NOTOK;
603     }
604
605     return NOTOK;       /* NOTREACHED */
606 }
607
608
609 #if defined(MPOP) && !defined(NNTP)
610 int
611 multiline (void)
612 #else
613 static int
614 multiline (void)
615 #endif
616 {
617     char buffer[BUFSIZ + TRMLEN];
618
619     if (getline (buffer, sizeof buffer, input) != OK)
620         return NOTOK;
621 #ifdef DEBUG
622     if (poprint)
623         fprintf (stderr, "<--- %s\n", response);
624 #endif DEBUG
625     if (strncmp (buffer, TRM, TRMLEN) == 0) {
626         if (buffer[TRMLEN] == 0)
627             return DONE;
628         else
629             strncpy (response, buffer + TRMLEN, sizeof(response));
630     }
631     else
632         strncpy (response, buffer, sizeof(response));
633
634     return OK;
635 }
636
637
638 static int
639 getline (char *s, int n, FILE *iop)
640 {
641     int c;
642     char *p;
643
644     p = s;
645     while (--n > 0 && (c = fgetc (iop)) != EOF)
646         if ((*p++ = c) == '\n')
647             break;
648     if (ferror (iop) && c != EOF) {
649         strncpy (response, "error on connection", sizeof(response));
650         return NOTOK;
651     }
652     if (c == EOF && p == s) {
653         strncpy (response, "connection closed by foreign host", sizeof(response));
654         return DONE;
655     }
656     *p = 0;
657     if (*--p == '\n')
658         *p = 0;
659     if (*--p == '\r')
660         *p = 0;
661
662     return OK;
663 }
664
665
666 static int
667 putline (char *s, FILE *iop)
668 {
669     fprintf (iop, "%s\r\n", s);
670     fflush (iop);
671     if (ferror (iop)) {
672         strncpy (response, "lost connection", sizeof(response));
673         return NOTOK;
674     }
675
676     return OK;
677 }