]> diplodocus.org Git - nmh/blob - uip/popsbr.c
Move things around so you can still initialize netsec with SASL
[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 #define TRMLEN (sizeof TRM - 1)
19
20 static int poprint = 0;
21 static int pophack = 0;
22
23 char response[BUFSIZ];
24 static netsec_context *nsc = NULL;
25
26 /*
27 * static prototypes
28 */
29
30 static int command(const char *, ...);
31 static int multiline(void);
32
33 static int traverse (int (*)(char *), const char *, ...);
34 static int vcommand(const char *, va_list);
35 static int pop_getline (char *, int, netsec_context *);
36 static int pop_sasl_callback(enum sasl_message_type, unsigned const char *,
37 unsigned int, unsigned char **, unsigned int *,
38 char **);
39
40 static int
41 check_mech(char *server_mechs, size_t server_mechs_size)
42 {
43 int status, sasl_capability = 0;
44
45 /*
46 * First off, we're going to send the CAPA command to see if we can
47 * even support the AUTH command, and if we do, then we'll get a
48 * list of mechanisms the server supports. If we don't support
49 * the CAPA command, then it's unlikely that we will support
50 * SASL
51 */
52
53 if (command("CAPA") == NOTOK) {
54 snprintf(response, sizeof(response),
55 "The POP CAPA command failed; POP server does not "
56 "support SASL");
57 return NOTOK;
58 }
59
60 while ((status = multiline()) != DONE)
61 switch (status) {
62 case NOTOK:
63 return NOTOK;
64 break;
65 case DONE: /* Shouldn't be possible, but just in case */
66 break;
67 case OK:
68 if (strncasecmp(response, "SASL ", 5) == 0) {
69 /*
70 * We've seen the SASL capability. Grab the mech list
71 */
72 sasl_capability++;
73 strncpy(server_mechs, response + 5, server_mechs_size);
74 }
75 break;
76 }
77
78 if (!sasl_capability) {
79 snprintf(response, sizeof(response), "POP server does not support "
80 "SASL");
81 return NOTOK;
82 }
83
84 return OK;
85 }
86
87 /*
88 * Split string containing proxy command into an array of arguments
89 * suitable for passing to exec. Returned array must be freed. Shouldn't
90 * be possible to call this with host set to NULL.
91 */
92 char **
93 parse_proxy(char *proxy, char *host)
94 {
95 char **pargv, **p;
96 int pargc = 2;
97 int hlen = strlen(host);
98 int plen = 1;
99 unsigned char *cur, *pro;
100 char *c;
101
102 /* skip any initial space */
103 for (pro = (unsigned char *) proxy; isspace(*pro); pro++)
104 continue;
105
106 /* calculate required size for argument array */
107 for (cur = pro; *cur; cur++) {
108 if (isspace(*cur) && cur[1] && !isspace(cur[1]))
109 plen++, pargc++;
110 else if (*cur == '%' && cur[1] == 'h') {
111 plen += hlen;
112 cur++;
113 } else if (!isspace(*cur))
114 plen++;
115 }
116
117 /* put together list of arguments */
118 p = pargv = mh_xmalloc(pargc * sizeof(char *));
119 c = *pargv = mh_xmalloc(plen * sizeof(char));
120 for (cur = pro; *cur; cur++) {
121 if (isspace(*cur) && cur[1] && !isspace(cur[1])) {
122 *c++ = '\0';
123 *++p = c;
124 } else if (*cur == '%' && cur[1] == 'h') {
125 strcpy (c, host);
126 c += hlen;
127 cur++;
128 } else if (!isspace(*cur))
129 *c++ = *cur;
130 }
131 *c = '\0';
132 *++p = NULL;
133 return pargv;
134 }
135
136 int
137 pop_init (char *host, char *port, char *user, char *pass, char *proxy,
138 int snoop, int sasl, char *mech, int tls, const char *oauth_svc)
139 {
140 int fd1, fd2;
141 char buffer[BUFSIZ];
142 char *errstr;
143
144 nsc = netsec_init();
145
146 if (user)
147 netsec_set_userid(nsc, user);
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) {
217 if (netsec_set_tls(nsc, 1, &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, host, "pop", mech,
232 pop_sasl_callback, &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 if (sasl) {
245 char server_mechs[256];
246 if (check_mech(server_mechs, sizeof(server_mechs)) != OK)
247 return NOTOK;
248 if (netsec_negotiate_sasl(nsc, server_mechs,
249 &errstr) != OK) {
250 strncpy(response, errstr, sizeof(response));
251 response[sizeof(response) - 1] = '\0';
252 free(errstr);
253 return NOTOK;
254 }
255 return OK;
256 } else
257 if (command ("USER %s", user) != NOTOK
258 && command ("%s %s", (pophack++, "PASS"),
259 pass) != NOTOK)
260 return OK;
261 }
262 strncpy (buffer, response, sizeof(buffer));
263 command ("QUIT");
264 strncpy (response, buffer, sizeof(response));
265 /* and fall */
266
267 case NOTOK:
268 case DONE:
269 if (poprint)
270 fprintf (stderr, "%s\n", response);
271 netsec_shutdown(nsc, 1);
272 nsc = NULL;
273 return NOTOK;
274 }
275
276 return NOTOK; /* NOTREACHED */
277 }
278
279
280 /*
281 * Our SASL callback; we are given SASL tokens and then have to format
282 * them according to the protocol requirements, and then process incoming
283 * messages and feed them back into the SASL library.
284 */
285
286 static int
287 pop_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
288 unsigned int indatalen, unsigned char **outdata,
289 unsigned int *outdatalen, char **errstr)
290 {
291 int rc, snoopoffset;
292 char *mech, *line;
293 size_t len, b64len;
294
295 switch (mtype) {
296 case NETSEC_SASL_START:
297 /*
298 * Generate our AUTH message, but there is a wrinkle.
299 *
300 * Technically, according to RFC 5034, if your command INCLUDING
301 * an initial response exceeds 255 octets (including CRLF), you
302 * can't issue this all in one go, but have to just issue the
303 * AUTH command, wait for a blank initial response, and then
304 * send your data.
305 */
306
307 mech = netsec_get_sasl_mechanism(nsc);
308
309 if (indatalen) {
310 char *b64data;
311 b64data = mh_xmalloc(BASE64SIZE(indatalen));
312 writeBase64raw(indata, indatalen, (unsigned char *) b64data);
313 b64len = strlen(b64data);
314
315 /* Formula here is AUTH + SP + mech + SP + out + CR + LF */
316 len = b64len + 8 + strlen(mech);
317 if (len > 255) {
318 rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech);
319 if (rc)
320 return NOTOK;
321 if (netsec_flush(nsc, errstr) != OK)
322 return NOTOK;
323 line = netsec_readline(nsc, &len, errstr);
324 if (! line)
325 return NOTOK;
326 /*
327 * If the protocol is being followed correctly, should just
328 * be a "+ ", nothing else.
329 */
330 if (len != 2 || strcmp(line, "+ ") != 0) {
331 netsec_err(errstr, "Did not get expected blank response "
332 "for initial challenge response");
333 return NOTOK;
334 }
335 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
336 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
337 netsec_set_snoop_callback(nsc, NULL, NULL);
338 free(b64data);
339 if (rc != OK)
340 return NOTOK;
341 if (netsec_flush(nsc, errstr) != OK)
342 return NOTOK;
343 } else {
344 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder,
345 &snoopoffset);
346 snoopoffset = 6 + strlen(mech);
347 rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech,
348 b64data);
349 free(b64data);
350 netsec_set_snoop_callback(nsc, NULL, NULL);
351 if (rc != OK)
352 return NOTOK;
353 if (netsec_flush(nsc, errstr) != OK)
354 return NOTOK;
355 }
356 } else {
357 if (netsec_printf(nsc, errstr, "AUTH %s\r\n", mech) != OK)
358 return NOTOK;
359 if (netsec_flush(nsc, errstr) != OK)
360 return NOTOK;
361 }
362
363 break;
364
365 /*
366 * We should get one line back, with our base64 data. Decode that
367 * and feed it back into the SASL library.
368 */
369 case NETSEC_SASL_READ:
370 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset);
371 snoopoffset = 2;
372 line = netsec_readline(nsc, &len, errstr);
373 netsec_set_snoop_callback(nsc, NULL, NULL);
374
375 if (line == NULL)
376 return NOTOK;
377 if (len < 2 || (len == 2 && strcmp(line, "+ ") != 0)) {
378 netsec_err(errstr, "Invalid format for SASL response");
379 return NOTOK;
380 }
381
382 if (len == 2) {
383 *outdata = NULL;
384 *outdatalen = 0;
385 } else {
386 rc = decodeBase64(line + 2, outdata, &len, 0, NULL);
387 *outdatalen = len;
388 if (rc != OK) {
389 netsec_err(errstr, "Unable to decode base64 response");
390 return NOTOK;
391 }
392 }
393 break;
394
395 /*
396 * Our encoding is pretty simple, so this is easy.
397 */
398
399 case NETSEC_SASL_WRITE:
400 if (indatalen == 0) {
401 rc = netsec_printf(nsc, errstr, "\r\n");
402 } else {
403 unsigned char *b64data;
404 b64data = mh_xmalloc(BASE64SIZE(indatalen));
405 writeBase64raw(indata, indatalen, b64data);
406 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
407 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
408 netsec_set_snoop_callback(nsc, NULL, NULL);
409 free(b64data);
410 }
411
412 if (rc != OK)
413 return NOTOK;
414
415 if (netsec_flush(nsc, errstr) != OK)
416 return NOTOK;
417 break;
418
419 /*
420 * Finish the protocol; we're looking for an +OK
421 */
422
423 case NETSEC_SASL_FINISH:
424 line = netsec_readline(nsc, &len, errstr);
425 if (line == NULL)
426 return NOTOK;
427
428 if (strncmp(line, "+OK", 3) != 0) {
429 netsec_err(errstr, "Authentication failed: %s", line);
430 return NOTOK;
431 }
432 break;
433
434 /*
435 * Cancel the SASL exchange in the middle of the commands; for
436 * POP, that's a single "*".
437 *
438 * It's unclear to me if I should be returning errors up; I finally
439 * decided the answer should be "yes", and if the upper layer wants to
440 * ignore them that's their choice.
441 */
442
443 case NETSEC_SASL_CANCEL:
444 rc = netsec_printf(nsc, errstr, "*\r\n");
445 if (rc == OK)
446 rc = netsec_flush(nsc, errstr);
447 if (rc != OK)
448 return NOTOK;
449 break;
450 }
451
452 return OK;
453 }
454
455 /*
456 * Find out number of messages available
457 * and their total size.
458 */
459
460 int
461 pop_stat (int *nmsgs, int *nbytes)
462 {
463
464 if (command ("STAT") == NOTOK)
465 return NOTOK;
466
467 *nmsgs = *nbytes = 0;
468 sscanf (response, "+OK %d %d", nmsgs, nbytes);
469
470 return OK;
471 }
472
473
474 int
475 pop_list (int msgno, int *nmsgs, int *msgs, int *bytes)
476 {
477 int i;
478 int *ids = NULL;
479
480 if (msgno) {
481 if (command ("LIST %d", msgno) == NOTOK)
482 return NOTOK;
483 *msgs = *bytes = 0;
484 if (ids) {
485 *ids = 0;
486 sscanf (response, "+OK %d %d %d", msgs, bytes, ids);
487 }
488 else
489 sscanf (response, "+OK %d %d", msgs, bytes);
490 return OK;
491 }
492
493 if (command ("LIST") == NOTOK)
494 return NOTOK;
495
496 for (i = 0; i < *nmsgs; i++)
497 switch (multiline ()) {
498 case NOTOK:
499 return NOTOK;
500 case DONE:
501 *nmsgs = ++i;
502 return OK;
503 case OK:
504 *msgs = *bytes = 0;
505 if (ids) {
506 *ids = 0;
507 sscanf (response, "%d %d %d",
508 msgs++, bytes++, ids++);
509 }
510 else
511 sscanf (response, "%d %d", msgs++, bytes++);
512 break;
513 }
514 for (;;)
515 switch (multiline ()) {
516 case NOTOK:
517 return NOTOK;
518 case DONE:
519 return OK;
520 case OK:
521 break;
522 }
523 }
524
525
526 int
527 pop_retr (int msgno, int (*action)(char *))
528 {
529 return traverse (action, "RETR %d", msgno);
530 }
531
532
533 static int
534 traverse (int (*action)(char *), const char *fmt, ...)
535 {
536 int result, snoopstate;
537 va_list ap;
538 char buffer[sizeof(response)];
539
540 va_start(ap, fmt);
541 result = vcommand (fmt, ap);
542 va_end(ap);
543
544 if (result == NOTOK)
545 return NOTOK;
546 strncpy (buffer, response, sizeof(buffer));
547
548 if ((snoopstate = netsec_get_snoop(nsc)))
549 netsec_set_snoop(nsc, 0);
550
551 for (;;)
552 switch (multiline ()) {
553 case NOTOK:
554 netsec_set_snoop(nsc, snoopstate);
555 return NOTOK;
556
557 case DONE:
558 strncpy (response, buffer, sizeof(response));
559 netsec_set_snoop(nsc, snoopstate);
560 return OK;
561
562 case OK:
563 (*action) (response);
564 break;
565 }
566 }
567
568
569 int
570 pop_dele (int msgno)
571 {
572 return command ("DELE %d", msgno);
573 }
574
575
576 int
577 pop_noop (void)
578 {
579 return command ("NOOP");
580 }
581
582
583 int
584 pop_rset (void)
585 {
586 return command ("RSET");
587 }
588
589
590 int
591 pop_top (int msgno, int lines, int (*action)(char *))
592 {
593 return traverse (action, "TOP %d %d", msgno, lines);
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, 1);
614
615 return OK;
616 }
617
618
619 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 fprintf (stderr, "%s\n", response);
670 return NOTOK;
671 }
672
673 return NOTOK; /* NOTREACHED */
674 }
675
676
677 int
678 multiline (void)
679 {
680 char buffer[BUFSIZ + TRMLEN];
681
682 if (pop_getline (buffer, sizeof buffer, nsc) != OK)
683 return NOTOK;
684 if (strncmp (buffer, TRM, TRMLEN) == 0) {
685 if (buffer[TRMLEN] == 0)
686 return DONE;
687 else
688 strncpy (response, buffer + TRMLEN, sizeof(response));
689 }
690 else
691 strncpy (response, buffer, sizeof(response));
692
693 return OK;
694 }
695
696 /*
697 * This is now just a thin wrapper around netsec_readline().
698 */
699
700 static int
701 pop_getline (char *s, int n, netsec_context *ns)
702 {
703 /* int c = -2; */
704 char *p;
705 size_t len, destlen;
706 /* int rc; */
707 char *errstr;
708
709 p = netsec_readline(ns, &len, &errstr);
710
711 if (p == NULL) {
712 strncpy(response, errstr, sizeof(response));
713 response[sizeof(response) - 1] = '\0';
714 free(errstr);
715 return NOTOK;
716 }
717
718 /*
719 * If we had an error, it should have been returned already. Since
720 * netsec_readline() strips off the CR-LF ending, just copy the existing
721 * buffer into response now.
722 *
723 * We get a length back from netsec_readline, but the rest of the POP
724 * code doesn't handle it; the assumptions are that everything from
725 * the network can be respresented as C strings. That should get fixed
726 * someday.
727 */
728
729 destlen = len < ((size_t) (n - 1)) ? len : (size_t) (n - 1);
730
731 memcpy(s, p, destlen);
732 s[destlen] = '\0';
733
734 return OK;
735 }