]> diplodocus.org Git - nmh/blob - uip/popsbr.c
Alter mh-chart(7)'s NAME to be lowercase.
[nmh] / uip / popsbr.c
1 /*
2 * popsbr.c -- POP client subroutines
3 *
4 * This code is Copyright (c) 2002, by the authors of nmh. See the
5 * COPYRIGHT file in the root directory of the nmh distribution for
6 * complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <h/utils.h>
11 #include <h/oauth.h>
12 #include <h/netsec.h>
13
14 #include <h/popsbr.h>
15 #include <h/signals.h>
16
17 #define TRM "."
18
19 static int poprint = 0;
20
21 char response[BUFSIZ];
22 static netsec_context *nsc = NULL;
23
24 /*
25 * static prototypes
26 */
27
28 static int command(const char *, ...);
29 static int multiline(void);
30
31 static int traverse (int (*)(char *), const char *, ...);
32 static int vcommand(const char *, va_list);
33 static int pop_getline (char *, int, netsec_context *);
34 static int pop_sasl_callback(enum sasl_message_type, unsigned const char *,
35 unsigned int, unsigned char **, unsigned int *,
36 char **);
37
38 static int
39 check_mech(char *server_mechs, size_t server_mechs_size)
40 {
41 int status, sasl_capability = 0;
42
43 /*
44 * First off, we're going to send the CAPA command to see if we can
45 * even support the AUTH command, and if we do, then we'll get a
46 * list of mechanisms the server supports. If we don't support
47 * the CAPA command, then it's unlikely that we will support
48 * SASL
49 */
50
51 if (command("CAPA") == NOTOK) {
52 snprintf(response, sizeof(response),
53 "The POP CAPA command failed; POP server does not "
54 "support SASL");
55 return NOTOK;
56 }
57
58 while ((status = multiline()) != DONE)
59 switch (status) {
60 case NOTOK:
61 return NOTOK;
62 break;
63 case DONE: /* Shouldn't be possible, but just in case */
64 break;
65 case OK:
66 if (strncasecmp(response, "SASL ", 5) == 0) {
67 /*
68 * We've seen the SASL capability. Grab the mech list
69 */
70 sasl_capability++;
71 strncpy(server_mechs, response + 5, server_mechs_size);
72 }
73 break;
74 }
75
76 if (!sasl_capability) {
77 snprintf(response, sizeof(response), "POP server does not support "
78 "SASL");
79 return NOTOK;
80 }
81
82 return OK;
83 }
84
85 /*
86 * Split string containing proxy command into an array of arguments
87 * suitable for passing to exec. Returned array must be freed. Shouldn't
88 * be possible to call this with host set to NULL.
89 */
90 char **
91 parse_proxy(char *proxy, char *host)
92 {
93 char **pargv, **p;
94 int pargc = 2;
95 int hlen = strlen(host);
96 int plen = 1;
97 unsigned char *cur, *pro;
98 char *c;
99
100 /* skip any initial space */
101 for (pro = (unsigned char *) proxy; isspace(*pro); pro++)
102 continue;
103
104 /* calculate required size for argument array */
105 for (cur = pro; *cur; cur++) {
106 if (isspace(*cur) && cur[1] && !isspace(cur[1]))
107 plen++, pargc++;
108 else if (*cur == '%' && cur[1] == 'h') {
109 plen += hlen;
110 cur++;
111 } else if (!isspace(*cur))
112 plen++;
113 }
114
115 /* put together list of arguments */
116 p = pargv = mh_xmalloc(pargc * sizeof(char *));
117 c = *pargv = mh_xmalloc(plen * sizeof(char));
118 for (cur = pro; *cur; cur++) {
119 if (isspace(*cur) && cur[1] && !isspace(cur[1])) {
120 *c++ = '\0';
121 *++p = c;
122 } else if (*cur == '%' && cur[1] == 'h') {
123 strcpy (c, host);
124 c += hlen;
125 cur++;
126 } else if (!isspace(*cur))
127 *c++ = *cur;
128 }
129 *c = '\0';
130 *++p = NULL;
131 return pargv;
132 }
133
134 int
135 pop_init (char *host, char *port, char *user, char *proxy, int snoop,
136 int sasl, char *mech, int tls, const char *oauth_svc)
137 {
138 int fd1, fd2;
139 char buffer[BUFSIZ];
140 char *errstr;
141
142 nsc = netsec_init();
143
144 if (user)
145 netsec_set_userid(nsc, user);
146
147 netsec_set_hostname(nsc, host);
148
149 if (oauth_svc != NULL) {
150 if (netsec_set_oauth_service(nsc, oauth_svc) != OK) {
151 snprintf(response, sizeof(response), "OAuth2 not supported");
152 return NOTOK;
153 }
154 }
155
156 if (proxy && *proxy) {
157 int pid;
158 int inpipe[2]; /* for reading from the server */
159 int outpipe[2]; /* for sending to the server */
160
161 if (pipe(inpipe) < 0) {
162 adios ("inpipe", "pipe");
163 }
164 if (pipe(outpipe) < 0) {
165 adios ("outpipe", "pipe");
166 }
167
168 pid=fork();
169 if (pid==0) {
170 char **argv;
171
172 /* in child */
173 close(0);
174 close(1);
175 dup2(outpipe[0],0); /* connect read end of connection */
176 dup2(inpipe[1], 1); /* connect write end of connection */
177 if(inpipe[0]>1) close(inpipe[0]);
178 if(inpipe[1]>1) close(inpipe[1]);
179 if(outpipe[0]>1) close(outpipe[0]);
180 if(outpipe[1]>1) close(outpipe[1]);
181
182 /* run the proxy command */
183 argv=parse_proxy(proxy, host);
184 execvp(argv[0],argv);
185
186 perror(argv[0]);
187 close(0);
188 close(1);
189 free(*argv);
190 free(argv);
191 exit(10);
192 }
193
194 /* okay in the parent we do some stuff */
195 close(inpipe[1]); /* child uses this */
196 close(outpipe[0]); /* child uses this */
197
198 /* we read on fd1 */
199 fd1=inpipe[0];
200 /* and write on fd2 */
201 fd2=outpipe[1];
202
203 } else {
204 if ((fd1 = client (host, port ? port : "pop3", response,
205 sizeof(response), snoop)) == NOTOK) {
206 return NOTOK;
207 }
208 fd2 = fd1;
209 }
210
211 SIGNAL (SIGPIPE, SIG_IGN);
212
213 netsec_set_fd(nsc, fd1, fd2);
214 netsec_set_snoop(nsc, snoop);
215
216 if (tls & P_INITTLS) {
217 if (netsec_set_tls(nsc, 1, tls & P_NOVERIFY, &errstr) != OK) {
218 snprintf(response, sizeof(response), "%s", errstr);
219 free(errstr);
220 return NOTOK;
221 }
222
223 if (netsec_negotiate_tls(nsc, &errstr) != OK) {
224 snprintf(response, sizeof(response), "%s", errstr);
225 free(errstr);
226 return NOTOK;
227 }
228 }
229
230 if (sasl) {
231 if (netsec_set_sasl_params(nsc, "pop", mech, pop_sasl_callback,
232 &errstr) != OK) {
233 snprintf(response, sizeof(response), "%s", errstr);
234 free(errstr);
235 return NOTOK;
236 }
237 }
238
239 switch (pop_getline (response, sizeof response, nsc)) {
240 case OK:
241 if (poprint)
242 fprintf (stderr, "<--- %s\n", response);
243 if (*response == '+') {
244 nmh_creds_t creds;
245
246 if (sasl) {
247 char server_mechs[256];
248 if (check_mech(server_mechs, sizeof(server_mechs)) != OK)
249 return NOTOK;
250 if (netsec_negotiate_sasl(nsc, server_mechs,
251 &errstr) != OK) {
252 strncpy(response, errstr, sizeof(response));
253 response[sizeof(response) - 1] = '\0';
254 free(errstr);
255 return NOTOK;
256 }
257 return OK;
258 }
259
260 if (!(creds = nmh_get_credentials(host, user)))
261 return NOTOK;
262 if (command ("USER %s", nmh_cred_get_user(creds))
263 != NOTOK) {
264 if (command("PASS %s", nmh_cred_get_password(creds))
265 != NOTOK) {
266 nmh_credentials_free(creds);
267 return OK;
268 }
269 }
270 nmh_credentials_free(creds);
271 }
272 strncpy (buffer, response, sizeof(buffer));
273 command ("QUIT");
274 strncpy (response, buffer, sizeof(response));
275 /* FALLTHRU */
276
277 case NOTOK:
278 case DONE:
279 if (poprint)
280 fprintf (stderr, "%s\n", response);
281 netsec_shutdown(nsc, 1);
282 nsc = NULL;
283 return NOTOK;
284 }
285
286 return NOTOK; /* NOTREACHED */
287 }
288
289
290 /*
291 * Our SASL callback; we are given SASL tokens and then have to format
292 * them according to the protocol requirements, and then process incoming
293 * messages and feed them back into the SASL library.
294 */
295
296 static int
297 pop_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
298 unsigned int indatalen, unsigned char **outdata,
299 unsigned int *outdatalen, char **errstr)
300 {
301 int rc, snoopoffset;
302 char *mech, *line;
303 size_t len, b64len;
304
305 switch (mtype) {
306 case NETSEC_SASL_START:
307 /*
308 * Generate our AUTH message, but there is a wrinkle.
309 *
310 * Technically, according to RFC 5034, if your command INCLUDING
311 * an initial response exceeds 255 octets (including CRLF), you
312 * can't issue this all in one go, but have to just issue the
313 * AUTH command, wait for a blank initial response, and then
314 * send your data.
315 */
316
317 mech = netsec_get_sasl_mechanism(nsc);
318
319 if (indatalen) {
320 char *b64data;
321 b64data = mh_xmalloc(BASE64SIZE(indatalen));
322 writeBase64raw(indata, indatalen, (unsigned char *) b64data);
323 b64len = strlen(b64data);
324
325 /* Formula here is AUTH + SP + mech + SP + out + CR + LF */
326 len = b64len + 8 + strlen(mech);
327 if (len > 255) {
328 rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech);
329 if (rc)
330 return NOTOK;
331 if (netsec_flush(nsc, errstr) != OK)
332 return NOTOK;
333 line = netsec_readline(nsc, &len, errstr);
334 if (! line)
335 return NOTOK;
336 /*
337 * If the protocol is being followed correctly, should just
338 * be a "+ ", nothing else.
339 */
340 if (len != 2 || strcmp(line, "+ ") != 0) {
341 netsec_err(errstr, "Did not get expected blank response "
342 "for initial challenge response");
343 return NOTOK;
344 }
345 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
346 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
347 netsec_set_snoop_callback(nsc, NULL, NULL);
348 free(b64data);
349 if (rc != OK)
350 return NOTOK;
351 if (netsec_flush(nsc, errstr) != OK)
352 return NOTOK;
353 } else {
354 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder,
355 &snoopoffset);
356 snoopoffset = 6 + strlen(mech);
357 rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech,
358 b64data);
359 free(b64data);
360 netsec_set_snoop_callback(nsc, NULL, NULL);
361 if (rc != OK)
362 return NOTOK;
363 if (netsec_flush(nsc, errstr) != OK)
364 return NOTOK;
365 }
366 } else {
367 if (netsec_printf(nsc, errstr, "AUTH %s\r\n", mech) != OK)
368 return NOTOK;
369 if (netsec_flush(nsc, errstr) != OK)
370 return NOTOK;
371 }
372
373 break;
374
375 /*
376 * We should get one line back, with our base64 data. Decode that
377 * and feed it back into the SASL library.
378 */
379 case NETSEC_SASL_READ:
380 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset);
381 snoopoffset = 2;
382 line = netsec_readline(nsc, &len, errstr);
383 netsec_set_snoop_callback(nsc, NULL, NULL);
384
385 if (line == NULL)
386 return NOTOK;
387 if (len < 2 || (len == 2 && strcmp(line, "+ ") != 0)) {
388 netsec_err(errstr, "Invalid format for SASL response");
389 return NOTOK;
390 }
391
392 if (len == 2) {
393 *outdata = NULL;
394 *outdatalen = 0;
395 } else {
396 rc = decodeBase64(line + 2, outdata, &len, 0, NULL);
397 *outdatalen = len;
398 if (rc != OK) {
399 netsec_err(errstr, "Unable to decode base64 response");
400 return NOTOK;
401 }
402 }
403 break;
404
405 /*
406 * Our encoding is pretty simple, so this is easy.
407 */
408
409 case NETSEC_SASL_WRITE:
410 if (indatalen == 0) {
411 rc = netsec_printf(nsc, errstr, "\r\n");
412 } else {
413 unsigned char *b64data;
414 b64data = mh_xmalloc(BASE64SIZE(indatalen));
415 writeBase64raw(indata, indatalen, b64data);
416 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
417 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
418 netsec_set_snoop_callback(nsc, NULL, NULL);
419 free(b64data);
420 }
421
422 if (rc != OK)
423 return NOTOK;
424
425 if (netsec_flush(nsc, errstr) != OK)
426 return NOTOK;
427 break;
428
429 /*
430 * Finish the protocol; we're looking for an +OK
431 */
432
433 case NETSEC_SASL_FINISH:
434 line = netsec_readline(nsc, &len, errstr);
435 if (line == NULL)
436 return NOTOK;
437
438 if (!has_prefix(line, "+OK")) {
439 netsec_err(errstr, "Authentication failed: %s", line);
440 return NOTOK;
441 }
442 break;
443
444 /*
445 * Cancel the SASL exchange in the middle of the commands; for
446 * POP, that's a single "*".
447 *
448 * It's unclear to me if I should be returning errors up; I finally
449 * decided the answer should be "yes", and if the upper layer wants to
450 * ignore them that's their choice.
451 */
452
453 case NETSEC_SASL_CANCEL:
454 rc = netsec_printf(nsc, errstr, "*\r\n");
455 if (rc == OK)
456 rc = netsec_flush(nsc, errstr);
457 if (rc != OK)
458 return NOTOK;
459 break;
460 }
461
462 return OK;
463 }
464
465 /*
466 * Find out number of messages available
467 * and their total size.
468 */
469
470 int
471 pop_stat (int *nmsgs, int *nbytes)
472 {
473
474 if (command ("STAT") == NOTOK)
475 return NOTOK;
476
477 *nmsgs = *nbytes = 0;
478 sscanf (response, "+OK %d %d", nmsgs, nbytes);
479
480 return OK;
481 }
482
483
484 int
485 pop_list (int msgno, int *nmsgs, int *msgs, int *bytes)
486 {
487 int i;
488 int *ids = NULL;
489
490 if (msgno) {
491 if (command ("LIST %d", msgno) == NOTOK)
492 return NOTOK;
493 *msgs = *bytes = 0;
494 if (ids) {
495 *ids = 0;
496 sscanf (response, "+OK %d %d %d", msgs, bytes, ids);
497 }
498 else
499 sscanf (response, "+OK %d %d", msgs, bytes);
500 return OK;
501 }
502
503 if (command ("LIST") == NOTOK)
504 return NOTOK;
505
506 for (i = 0; i < *nmsgs; i++)
507 switch (multiline ()) {
508 case NOTOK:
509 return NOTOK;
510 case DONE:
511 *nmsgs = ++i;
512 return OK;
513 case OK:
514 *msgs = *bytes = 0;
515 if (ids) {
516 *ids = 0;
517 sscanf (response, "%d %d %d",
518 msgs++, bytes++, ids++);
519 }
520 else
521 sscanf (response, "%d %d", msgs++, bytes++);
522 break;
523 }
524 for (;;)
525 switch (multiline ()) {
526 case NOTOK:
527 return NOTOK;
528 case DONE:
529 return OK;
530 case OK:
531 break;
532 }
533 }
534
535
536 int
537 pop_retr (int msgno, int (*action)(char *))
538 {
539 return traverse (action, "RETR %d", msgno);
540 }
541
542
543 static int
544 traverse (int (*action)(char *), const char *fmt, ...)
545 {
546 int result, snoopstate;
547 va_list ap;
548 char buffer[sizeof(response)];
549
550 va_start(ap, fmt);
551 result = vcommand (fmt, ap);
552 va_end(ap);
553
554 if (result == NOTOK)
555 return NOTOK;
556 strncpy (buffer, response, sizeof(buffer));
557
558 if ((snoopstate = netsec_get_snoop(nsc)))
559 netsec_set_snoop(nsc, 0);
560
561 for (;;)
562 switch (multiline ()) {
563 case NOTOK:
564 netsec_set_snoop(nsc, snoopstate);
565 return NOTOK;
566
567 case DONE:
568 strncpy (response, buffer, sizeof(response));
569 netsec_set_snoop(nsc, snoopstate);
570 return OK;
571
572 case OK:
573 (*action) (response);
574 break;
575 }
576 }
577
578
579 int
580 pop_dele (int msgno)
581 {
582 return command ("DELE %d", msgno);
583 }
584
585
586 int
587 pop_noop (void)
588 {
589 return command ("NOOP");
590 }
591
592
593 int
594 pop_rset (void)
595 {
596 return command ("RSET");
597 }
598
599
600 int
601 pop_top (int msgno, int lines, int (*action)(char *))
602 {
603 return traverse (action, "TOP %d %d", msgno, lines);
604 }
605
606
607 int
608 pop_quit (void)
609 {
610 int i;
611
612 i = command ("QUIT");
613 pop_done ();
614
615 return i;
616 }
617
618
619 int
620 pop_done (void)
621 {
622 if (nsc)
623 netsec_shutdown(nsc, 1);
624
625 return OK;
626 }
627
628
629 int
630 command(const char *fmt, ...)
631 {
632 va_list ap;
633 int result;
634
635 va_start(ap, fmt);
636 result = vcommand(fmt, ap);
637 va_end(ap);
638
639 return result;
640 }
641
642
643 static int
644 vcommand (const char *fmt, va_list ap)
645 {
646 /* char *cp; */
647 char *errstr;
648
649 if (netsec_vprintf(nsc, &errstr, fmt, ap) != OK) {
650 strncpy(response, errstr, sizeof(response));
651 response[sizeof(response) - 1] = '\0';
652 free(errstr);
653 return NOTOK;
654 }
655
656 if (netsec_printf(nsc, &errstr, "\r\n") != OK) {
657 strncpy(response, errstr, sizeof(response));
658 response[sizeof(response) - 1] = '\0';
659 free(errstr);
660 return NOTOK;
661 }
662
663 if (netsec_flush(nsc, &errstr) != OK) {
664 strncpy(response, errstr, sizeof(response));
665 response[sizeof(response) - 1] = '\0';
666 free(errstr);
667 return NOTOK;
668 }
669
670 switch (pop_getline (response, sizeof response, nsc)) {
671 case OK:
672 if (poprint)
673 fprintf (stderr, "<--- %s\n", response);
674 return (*response == '+' ? OK : NOTOK);
675
676 case NOTOK:
677 case DONE:
678 if (poprint)
679 fprintf (stderr, "%s\n", response);
680 return NOTOK;
681 }
682
683 return NOTOK; /* NOTREACHED */
684 }
685
686
687 int
688 multiline (void)
689 {
690 char buffer[BUFSIZ + LEN(TRM)];
691
692 if (pop_getline (buffer, sizeof buffer, nsc) != OK)
693 return NOTOK;
694 if (has_prefix(buffer, TRM)) {
695 if (buffer[LEN(TRM)] == 0)
696 return DONE;
697 strncpy (response, buffer + LEN(TRM), sizeof(response));
698 }
699 else
700 strncpy (response, buffer, sizeof(response));
701
702 return OK;
703 }
704
705 /*
706 * This is now just a thin wrapper around netsec_readline().
707 */
708
709 static int
710 pop_getline (char *s, int n, netsec_context *ns)
711 {
712 /* int c = -2; */
713 char *p;
714 size_t len, destlen;
715 /* int rc; */
716 char *errstr;
717
718 p = netsec_readline(ns, &len, &errstr);
719
720 if (p == NULL) {
721 strncpy(response, errstr, sizeof(response));
722 response[sizeof(response) - 1] = '\0';
723 free(errstr);
724 return NOTOK;
725 }
726
727 /*
728 * If we had an error, it should have been returned already. Since
729 * netsec_readline() strips off the CR-LF ending, just copy the existing
730 * buffer into response now.
731 *
732 * We get a length back from netsec_readline, but the rest of the POP
733 * code doesn't handle it; the assumptions are that everything from
734 * the network can be respresented as C strings. That should get fixed
735 * someday.
736 */
737
738 destlen = len < ((size_t) (n - 1)) ? len : (size_t) (n - 1);
739
740 memcpy(s, p, destlen);
741 s[destlen] = '\0';
742
743 return OK;
744 }