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