]> diplodocus.org Git - nmh/blob - uip/popsbr.c
638fa4116b7f4bce3fb8a1728760be3da89b6cf9
[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 (tls) {
150 if (netsec_set_tls(nsc, 1, &errstr) != OK) {
151 snprintf(response, sizeof(response), "%s", errstr);
152 free(errstr);
153 return NOTOK;
154 }
155 }
156
157 if (oauth_svc != NULL) {
158 if (netsec_set_oauth_service(nsc, oauth_svc) != OK) {
159 snprintf(response, sizeof(response), "OAuth2 not supported");
160 return NOTOK;
161 }
162 }
163
164 if (proxy && *proxy) {
165 int pid;
166 int inpipe[2]; /* for reading from the server */
167 int outpipe[2]; /* for sending to the server */
168
169 if (pipe(inpipe) < 0) {
170 adios ("inpipe", "pipe");
171 }
172 if (pipe(outpipe) < 0) {
173 adios ("outpipe", "pipe");
174 }
175
176 pid=fork();
177 if (pid==0) {
178 char **argv;
179
180 /* in child */
181 close(0);
182 close(1);
183 dup2(outpipe[0],0); /* connect read end of connection */
184 dup2(inpipe[1], 1); /* connect write end of connection */
185 if(inpipe[0]>1) close(inpipe[0]);
186 if(inpipe[1]>1) close(inpipe[1]);
187 if(outpipe[0]>1) close(outpipe[0]);
188 if(outpipe[1]>1) close(outpipe[1]);
189
190 /* run the proxy command */
191 argv=parse_proxy(proxy, host);
192 execvp(argv[0],argv);
193
194 perror(argv[0]);
195 close(0);
196 close(1);
197 free(*argv);
198 free(argv);
199 exit(10);
200 }
201
202 /* okay in the parent we do some stuff */
203 close(inpipe[1]); /* child uses this */
204 close(outpipe[0]); /* child uses this */
205
206 /* we read on fd1 */
207 fd1=inpipe[0];
208 /* and write on fd2 */
209 fd2=outpipe[1];
210
211 } else {
212 if ((fd1 = client (host, port ? port : "pop3", response,
213 sizeof(response), snoop)) == NOTOK) {
214 return NOTOK;
215 }
216 fd2 = fd1;
217 }
218
219 SIGNAL (SIGPIPE, SIG_IGN);
220
221 netsec_set_fd(nsc, fd1, fd2);
222 netsec_set_snoop(nsc, snoop);
223
224 if (tls) {
225 if (netsec_negotiate_tls(nsc, &errstr) != OK) {
226 snprintf(response, sizeof(response), "%s", errstr);
227 free(errstr);
228 return NOTOK;
229 }
230 }
231
232 if (sasl) {
233 if (netsec_set_sasl_params(nsc, host, "pop", mech,
234 pop_sasl_callback, &errstr) != OK) {
235 snprintf(response, sizeof(response), "%s", errstr);
236 free(errstr);
237 return NOTOK;
238 }
239 }
240
241 switch (pop_getline (response, sizeof response, nsc)) {
242 case OK:
243 if (poprint)
244 fprintf (stderr, "<--- %s\n", response);
245 if (*response == '+') {
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 } else
259 if (command ("USER %s", user) != NOTOK
260 && command ("%s %s", (pophack++, "PASS"),
261 pass) != NOTOK)
262 return OK;
263 }
264 strncpy (buffer, response, sizeof(buffer));
265 command ("QUIT");
266 strncpy (response, buffer, sizeof(response));
267 /* and fall */
268
269 case NOTOK:
270 case DONE:
271 if (poprint)
272 fprintf (stderr, "%s\n", response);
273 netsec_shutdown(nsc, 1);
274 nsc = NULL;
275 return NOTOK;
276 }
277
278 return NOTOK; /* NOTREACHED */
279 }
280
281
282 /*
283 * Our SASL callback; we are given SASL tokens and then have to format
284 * them according to the protocol requirements, and then process incoming
285 * messages and feed them back into the SASL library.
286 */
287
288 static int
289 pop_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
290 unsigned int indatalen, unsigned char **outdata,
291 unsigned int *outdatalen, char **errstr)
292 {
293 int rc, snoopoffset;
294 char *mech, *line;
295 size_t len, b64len;
296
297 switch (mtype) {
298 case NETSEC_SASL_START:
299 /*
300 * Generate our AUTH message, but there is a wrinkle.
301 *
302 * Technically, according to RFC 5034, if your command INCLUDING
303 * an initial response exceeds 255 octets (including CRLF), you
304 * can't issue this all in one go, but have to just issue the
305 * AUTH command, wait for a blank initial response, and then
306 * send your data.
307 */
308
309 mech = netsec_get_sasl_mechanism(nsc);
310
311 if (indatalen) {
312 char *b64data;
313 b64data = mh_xmalloc(BASE64SIZE(indatalen));
314 writeBase64raw(indata, indatalen, (unsigned char *) b64data);
315 b64len = strlen(b64data);
316
317 /* Formula here is AUTH + SP + mech + SP + out + CR + LF */
318 len = b64len + 8 + strlen(mech);
319 if (len > 255) {
320 rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech);
321 if (rc)
322 return NOTOK;
323 if (netsec_flush(nsc, errstr) != OK)
324 return NOTOK;
325 line = netsec_readline(nsc, &len, errstr);
326 if (! line)
327 return NOTOK;
328 /*
329 * If the protocol is being followed correctly, should just
330 * be a "+ ", nothing else.
331 */
332 if (len != 2 || strcmp(line, "+ ") != 0) {
333 netsec_err(errstr, "Did not get expected blank response "
334 "for initial challenge response");
335 return NOTOK;
336 }
337 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
338 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
339 netsec_set_snoop_callback(nsc, NULL, NULL);
340 free(b64data);
341 if (rc != OK)
342 return NOTOK;
343 if (netsec_flush(nsc, errstr) != OK)
344 return NOTOK;
345 } else {
346 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder,
347 &snoopoffset);
348 snoopoffset = 6 + strlen(mech);
349 rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech,
350 b64data);
351 free(b64data);
352 if (rc != OK)
353 return NOTOK;
354 if (netsec_flush(nsc, errstr) != OK)
355 return NOTOK;
356 }
357 } else {
358 if (netsec_printf(nsc, errstr, "AUTH %s\r\n", mech) != OK)
359 return NOTOK;
360 if (netsec_flush(nsc, errstr) != OK)
361 return NOTOK;
362 }
363
364 break;
365
366 /*
367 * We should get one line back, with our base64 data. Decode that
368 * and feed it back into the SASL library.
369 */
370 case NETSEC_SASL_READ:
371 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset);
372 snoopoffset = 2;
373 line = netsec_readline(nsc, &len, errstr);
374 netsec_set_snoop_callback(nsc, NULL, NULL);
375
376 if (line == NULL)
377 return NOTOK;
378 if (len < 2 || (len == 2 && strcmp(line, "+ ") != 0)) {
379 netsec_err(errstr, "Invalid format for SASL response");
380 return NOTOK;
381 }
382
383 if (len == 2) {
384 *outdata = NULL;
385 *outdatalen = 0;
386 } else {
387 rc = decodeBase64(line + 2, (const char **) outdata, &len, 0, NULL);
388 *outdatalen = len;
389 if (rc != OK)
390 return NOTOK;
391 }
392 break;
393
394 /*
395 * Our encoding is pretty simple, so this is easy.
396 */
397
398 case NETSEC_SASL_WRITE:
399 if (indatalen == 0) {
400 rc = netsec_printf(nsc, errstr, "\r\n");
401 } else {
402 unsigned char *b64data;
403 b64data = mh_xmalloc(BASE64SIZE(indatalen));
404 writeBase64raw(indata, indatalen, b64data);
405 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
406 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
407 netsec_set_snoop_callback(nsc, NULL, NULL);
408 free(b64data);
409 }
410
411 if (rc != OK)
412 return NOTOK;
413
414 if (netsec_flush(nsc, errstr) != OK)
415 return NOTOK;
416
417 return OK;
418 break;
419
420 /*
421 * Finish the protocol; we're looking for an +OK
422 */
423
424 case NETSEC_SASL_FINISH:
425 line = netsec_readline(nsc, &len, errstr);
426 if (line == NULL)
427 return NOTOK;
428
429 if (strncmp(line, "+OK", 3) != 0) {
430 netsec_err(errstr, "Authentication failed: %s", line);
431 return NOTOK;
432 }
433 break;
434
435 /*
436 * Cancel the SASL exchange in the middle of the commands; for
437 * POP, that's a single "*".
438 *
439 * It's unclear to me if I should be returning errors up; I finally
440 * decided the answer should be "yes", and if the upper layer wants to
441 * ignore them that's their choice.
442 */
443
444 case NETSEC_SASL_CANCEL:
445 rc = netsec_printf(nsc, errstr, "*\r\n");
446 if (rc == OK)
447 rc = netsec_flush(nsc, errstr);
448 if (rc != OK)
449 return NOTOK;
450 break;
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;
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 for (;;)
549 switch (multiline ()) {
550 case NOTOK:
551 return NOTOK;
552
553 case DONE:
554 strncpy (response, buffer, sizeof(response));
555 return OK;
556
557 case OK:
558 (*action) (response);
559 break;
560 }
561 }
562
563
564 int
565 pop_dele (int msgno)
566 {
567 return command ("DELE %d", msgno);
568 }
569
570
571 int
572 pop_noop (void)
573 {
574 return command ("NOOP");
575 }
576
577
578 int
579 pop_rset (void)
580 {
581 return command ("RSET");
582 }
583
584
585 int
586 pop_top (int msgno, int lines, int (*action)(char *))
587 {
588 return traverse (action, "TOP %d %d", msgno, lines);
589 }
590
591
592 int
593 pop_quit (void)
594 {
595 int i;
596
597 i = command ("QUIT");
598 pop_done ();
599
600 return i;
601 }
602
603
604 int
605 pop_done (void)
606 {
607 if (nsc)
608 netsec_shutdown(nsc, 1);
609
610 return OK;
611 }
612
613
614 int
615 command(const char *fmt, ...)
616 {
617 va_list ap;
618 int result;
619
620 va_start(ap, fmt);
621 result = vcommand(fmt, ap);
622 va_end(ap);
623
624 return result;
625 }
626
627
628 static int
629 vcommand (const char *fmt, va_list ap)
630 {
631 /* char *cp; */
632 char *errstr;
633
634 if (netsec_vprintf(nsc, &errstr, fmt, ap) != OK) {
635 strncpy(response, errstr, sizeof(response));
636 response[sizeof(response) - 1] = '\0';
637 free(errstr);
638 return NOTOK;
639 }
640
641 if (netsec_printf(nsc, &errstr, "\r\n") != OK) {
642 strncpy(response, errstr, sizeof(response));
643 response[sizeof(response) - 1] = '\0';
644 free(errstr);
645 return NOTOK;
646 }
647
648 if (netsec_flush(nsc, &errstr) != OK) {
649 strncpy(response, errstr, sizeof(response));
650 response[sizeof(response) - 1] = '\0';
651 free(errstr);
652 return NOTOK;
653 }
654
655 switch (pop_getline (response, sizeof response, nsc)) {
656 case OK:
657 if (poprint)
658 fprintf (stderr, "<--- %s\n", response);
659 return (*response == '+' ? OK : NOTOK);
660
661 case NOTOK:
662 case DONE:
663 if (poprint)
664 fprintf (stderr, "%s\n", response);
665 return NOTOK;
666 }
667
668 return NOTOK; /* NOTREACHED */
669 }
670
671
672 int
673 multiline (void)
674 {
675 char buffer[BUFSIZ + TRMLEN];
676
677 if (pop_getline (buffer, sizeof buffer, nsc) != OK)
678 return NOTOK;
679 if (strncmp (buffer, TRM, TRMLEN) == 0) {
680 if (buffer[TRMLEN] == 0)
681 return DONE;
682 else
683 strncpy (response, buffer + TRMLEN, sizeof(response));
684 }
685 else
686 strncpy (response, buffer, sizeof(response));
687
688 return OK;
689 }
690
691 /*
692 * This is now just a thin wrapper around netsec_readline().
693 */
694
695 static int
696 pop_getline (char *s, int n, netsec_context *ns)
697 {
698 /* int c = -2; */
699 char *p;
700 size_t len, destlen;
701 /* int rc; */
702 char *errstr;
703
704 p = netsec_readline(ns, &len, &errstr);
705
706 if (p == NULL) {
707 strncpy(response, errstr, sizeof(response));
708 response[sizeof(response) - 1] = '\0';
709 free(errstr);
710 return NOTOK;
711 }
712
713 /*
714 * If we had an error, it should have been returned already. Since
715 * netsec_readline() strips off the CR-LF ending, just copy the existing
716 * buffer into response now.
717 *
718 * We get a length back from netsec_readline, but the rest of the POP
719 * code doesn't handle it; the assumptions are that everything from
720 * the network can be respresented as C strings. That should get fixed
721 * someday.
722 */
723
724 destlen = len < ((size_t) (n - 1)) ? len : n - 1;
725
726 memcpy(s, p, destlen);
727 s[destlen] = '\0';
728
729 return OK;
730 }