]> diplodocus.org Git - nmh/blob - uip/popsbr.c
d61ce7678b1f1d8c6bced4544b286f3498fde1e4
[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 if (len < 2 || (len == 2 && strcmp(line, "+ ") != 0)) {
449 netsec_err(errstr, "Invalid format for SASL response");
450 return NOTOK;
451 }
452
453 if (len == 2) {
454 *outdata = NULL;
455 *outdatalen = 0;
456 } else {
457 rc = decodeBase64(line + 2, outdata, &len, 0);
458 *outdatalen = len;
459 if (rc != OK) {
460 netsec_err(errstr, "Unable to decode base64 response");
461 return NOTOK;
462 }
463 }
464 break;
465
466 /*
467 * Our encoding is pretty simple, so this is easy.
468 */
469
470 case NETSEC_SASL_WRITE:
471 if (indatalen == 0) {
472 rc = netsec_printf(nsc, errstr, "\r\n");
473 } else {
474 unsigned char *b64data;
475 b64data = mh_xmalloc(BASE64SIZE(indatalen));
476 writeBase64raw(indata, indatalen, b64data);
477 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
478 free(b64data);
479 }
480
481 if (rc != OK)
482 return NOTOK;
483
484 if (indatalen > 0)
485 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
486 rc = netsec_flush(nsc, errstr);
487 netsec_set_snoop_callback(nsc, NULL, NULL);
488 if (rc != OK)
489 return NOTOK;
490 break;
491
492 /*
493 * Finish the protocol; we're looking for an +OK
494 */
495
496 case NETSEC_SASL_FINISH:
497 line = netsec_readline(nsc, &len, errstr);
498 if (line == NULL)
499 return NOTOK;
500
501 if (!has_prefix(line, "+OK")) {
502 netsec_err(errstr, "Authentication failed: %s", line);
503 return NOTOK;
504 }
505 break;
506
507 /*
508 * Cancel the SASL exchange in the middle of the commands; for
509 * POP, that's a single "*".
510 *
511 * It's unclear to me if I should be returning errors up; I finally
512 * decided the answer should be "yes", and if the upper layer wants to
513 * ignore them that's their choice.
514 */
515
516 case NETSEC_SASL_CANCEL:
517 rc = netsec_printf(nsc, errstr, "*\r\n");
518 if (rc == OK)
519 rc = netsec_flush(nsc, errstr);
520 if (rc != OK)
521 return NOTOK;
522 break;
523 }
524
525 return OK;
526 }
527
528 /*
529 * Find out number of messages available
530 * and their total size.
531 */
532
533 int
534 pop_stat (int *nmsgs, int *nbytes)
535 {
536
537 if (command ("STAT") == NOTOK)
538 return NOTOK;
539
540 *nmsgs = *nbytes = 0;
541 sscanf (response, "+OK %d %d", nmsgs, nbytes);
542
543 return OK;
544 }
545
546
547 int
548 pop_retr (int msgno, int (*action)(void *, char *), void *closure)
549 {
550 return traverse (action, closure, "RETR %d", msgno);
551 }
552
553
554 static int
555 traverse (int (*action)(void *, char *), void *closure, const char *fmt, ...)
556 {
557 int result, snoopstate;
558 va_list ap;
559 char buffer[sizeof(response)];
560
561 va_start(ap, fmt);
562 result = vcommand (fmt, ap);
563 va_end(ap);
564
565 if (result == NOTOK)
566 return NOTOK;
567 strncpy (buffer, response, sizeof(buffer));
568
569 if ((snoopstate = netsec_get_snoop(nsc)))
570 netsec_set_snoop(nsc, 0);
571
572 for (;;) {
573 result = multiline();
574 if (result == OK) {
575 result = (*action)(closure, response);
576 if (result == OK)
577 continue;
578 } else if (result == DONE) {
579 strncpy(response, buffer, sizeof(response));
580 result = OK;
581 }
582 break;
583 }
584
585 netsec_set_snoop(nsc, snoopstate);
586 return result;
587 }
588
589
590 int
591 pop_dele (int msgno)
592 {
593 return command ("DELE %d", msgno);
594 }
595
596
597 int
598 pop_quit (void)
599 {
600 int i;
601
602 i = command ("QUIT");
603 pop_done ();
604
605 return i;
606 }
607
608
609 int
610 pop_done (void)
611 {
612 if (nsc)
613 netsec_shutdown(nsc);
614
615 return OK;
616 }
617
618
619 static int
620 command(const char *fmt, ...)
621 {
622 va_list ap;
623 int result;
624
625 va_start(ap, fmt);
626 result = vcommand(fmt, ap);
627 va_end(ap);
628
629 return result;
630 }
631
632
633 static int
634 vcommand (const char *fmt, va_list ap)
635 {
636 /* char *cp; */
637 char *errstr;
638
639 if (netsec_vprintf(nsc, &errstr, fmt, ap) != OK) {
640 strncpy(response, errstr, sizeof(response));
641 response[sizeof(response) - 1] = '\0';
642 free(errstr);
643 return NOTOK;
644 }
645
646 if (netsec_printf(nsc, &errstr, "\r\n") != OK) {
647 strncpy(response, errstr, sizeof(response));
648 response[sizeof(response) - 1] = '\0';
649 free(errstr);
650 return NOTOK;
651 }
652
653 if (netsec_flush(nsc, &errstr) != OK) {
654 strncpy(response, errstr, sizeof(response));
655 response[sizeof(response) - 1] = '\0';
656 free(errstr);
657 return NOTOK;
658 }
659
660 switch (pop_getline (response, sizeof response, nsc)) {
661 case OK:
662 if (poprint)
663 fprintf (stderr, "<--- %s\n", response);
664 return *response == '+' ? OK : NOTOK;
665
666 case NOTOK:
667 case DONE:
668 if (poprint) {
669 fputs(response, stderr);
670 putc('\n', stderr);
671 }
672 return NOTOK;
673 }
674
675 return NOTOK; /* NOTREACHED */
676 }
677
678
679 static int
680 multiline (void)
681 {
682 char buffer[BUFSIZ + LEN(TRM)];
683
684 if (pop_getline (buffer, sizeof buffer, nsc) != OK)
685 return NOTOK;
686 if (has_prefix(buffer, TRM)) {
687 if (buffer[LEN(TRM)] == 0)
688 return DONE;
689 strncpy (response, buffer + LEN(TRM), sizeof(response));
690 }
691 else
692 strncpy (response, buffer, sizeof(response));
693
694 return OK;
695 }
696
697 /*
698 * This is now just a thin wrapper around netsec_readline().
699 */
700
701 static int
702 pop_getline (char *s, int n, netsec_context *ns)
703 {
704 /* int c = -2; */
705 char *p;
706 size_t len, destlen;
707 /* int rc; */
708 char *errstr;
709
710 p = netsec_readline(ns, &len, &errstr);
711
712 if (p == NULL) {
713 strncpy(response, errstr, sizeof(response));
714 response[sizeof(response) - 1] = '\0';
715 free(errstr);
716 return NOTOK;
717 }
718
719 /*
720 * If we had an error, it should have been returned already. Since
721 * netsec_readline() strips off the CR-LF ending, just copy the existing
722 * buffer into response now.
723 *
724 * We get a length back from netsec_readline, but the rest of the POP
725 * code doesn't handle it; the assumptions are that everything from
726 * the network can be represented as C strings. That should get fixed
727 * someday.
728 */
729
730 destlen = min(len, (size_t)(n - 1));
731
732 memcpy(s, p, destlen);
733 s[destlen] = '\0';
734
735 return OK;
736 }