]> diplodocus.org Git - nmh/blob - uip/popsbr.c
26f4fa0cef8b64f911f44a89af88011356b49241
[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 }
217
218 SIGNAL (SIGPIPE, SIG_IGN);
219
220 netsec_set_fd(nsc, fd1);
221 netsec_set_snoop(nsc, snoop);
222
223 if (tls) {
224 if (netsec_negotiate_tls(nsc, &errstr) != OK) {
225 snprintf(response, sizeof(response), "%s", errstr);
226 free(errstr);
227 return NOTOK;
228 }
229 }
230
231 if (sasl) {
232 if (netsec_set_sasl_params(nsc, host, "pop", mech,
233 pop_sasl_callback, &errstr) != OK) {
234 snprintf(response, sizeof(response), "%s", errstr);
235 free(errstr);
236 return NOTOK;
237 }
238 }
239
240 switch (pop_getline (response, sizeof response, nsc)) {
241 case OK:
242 if (poprint)
243 fprintf (stderr, "<--- %s\n", response);
244 if (*response == '+') {
245 if (sasl) {
246 char server_mechs[256];
247 if (check_mech(server_mechs, sizeof(server_mechs)) != OK)
248 return NOTOK;
249 if (netsec_negotiate_sasl(nsc, server_mechs,
250 &errstr) != OK) {
251 strncpy(response, errstr, sizeof(response));
252 response[sizeof(response) - 1] = '\0';
253 free(errstr);
254 return NOTOK;
255 }
256 return OK;
257 } else
258 if (command ("USER %s", user) != NOTOK
259 && command ("%s %s", (pophack++, "PASS"),
260 pass) != NOTOK)
261 return OK;
262 }
263 strncpy (buffer, response, sizeof(buffer));
264 command ("QUIT");
265 strncpy (response, buffer, sizeof(response));
266 /* and fall */
267
268 case NOTOK:
269 case DONE:
270 if (poprint)
271 fprintf (stderr, "%s\n", response);
272 netsec_shutdown(nsc, 1);
273 nsc = NULL;
274 return NOTOK;
275 }
276
277 return NOTOK; /* NOTREACHED */
278 }
279
280
281 /*
282 * Our SASL callback; we are given SASL tokens and then have to format
283 * them according to the protocol requirements, and then process incoming
284 * messages and feed them back into the SASL library.
285 */
286
287 static int
288 pop_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
289 unsigned int indatalen, unsigned char **outdata,
290 unsigned int *outdatalen, char **errstr)
291 {
292 int rc, snoopoffset;
293 char *mech, *line;
294 size_t len, b64len;
295
296 switch (mtype) {
297 case NETSEC_SASL_START:
298 /*
299 * Generate our AUTH message, but there is a wrinkle.
300 *
301 * Technically, according to RFC 5034, if your command INCLUDING
302 * an initial response exceeds 255 octets (including CRLF), you
303 * can't issue this all in one go, but have to just issue the
304 * AUTH command, wait for a blank initial response, and then
305 * send your data.
306 */
307
308 mech = netsec_get_sasl_mechanism(nsc);
309
310 if (indatalen) {
311 char *b64data;
312 b64data = mh_xmalloc(BASE64SIZE(indatalen));
313 writeBase64raw(indata, indatalen, (unsigned char *) b64data);
314 b64len = strlen(b64data);
315
316 /* Formula here is AUTH + SP + mech + SP + out + CR + LF */
317 len = b64len + 8 + strlen(mech);
318 if (len > 255) {
319 rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech);
320 if (rc)
321 return NOTOK;
322 if (netsec_flush(nsc, errstr) != OK)
323 return NOTOK;
324 line = netsec_readline(nsc, &len, errstr);
325 if (! line)
326 return NOTOK;
327 /*
328 * If the protocol is being followed correctly, should just
329 * be a "+ ", nothing else.
330 */
331 if (len != 2 || strcmp(line, "+ ") != 0) {
332 netsec_err(errstr, "Did not get expected blank response "
333 "for initial challenge response");
334 return NOTOK;
335 }
336 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
337 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
338 netsec_set_snoop_callback(nsc, NULL, NULL);
339 free(b64data);
340 if (rc != OK)
341 return NOTOK;
342 if (netsec_flush(nsc, errstr) != OK)
343 return NOTOK;
344 } else {
345 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder,
346 &snoopoffset);
347 snoopoffset = 6 + strlen(mech);
348 rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech,
349 b64data);
350 free(b64data);
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, (const char **) outdata, &len, 0, NULL);
387 *outdatalen = len;
388 if (rc != OK)
389 return NOTOK;
390 }
391 break;
392
393 /*
394 * Our encoding is pretty simple, so this is easy.
395 */
396
397 case NETSEC_SASL_WRITE:
398 if (indatalen == 0) {
399 rc = netsec_printf(nsc, errstr, "\r\n");
400 } else {
401 unsigned char *b64data;
402 b64data = mh_xmalloc(BASE64SIZE(indatalen));
403 writeBase64raw(indata, indatalen, b64data);
404 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
405 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
406 netsec_set_snoop_callback(nsc, NULL, NULL);
407 free(b64data);
408 }
409
410 if (rc != OK)
411 return NOTOK;
412
413 if (netsec_flush(nsc, errstr) != OK)
414 return NOTOK;
415
416 return OK;
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 return OK;
452 }
453
454 /*
455 * Find out number of messages available
456 * and their total size.
457 */
458
459 int
460 pop_stat (int *nmsgs, int *nbytes)
461 {
462
463 if (command ("STAT") == NOTOK)
464 return NOTOK;
465
466 *nmsgs = *nbytes = 0;
467 sscanf (response, "+OK %d %d", nmsgs, nbytes);
468
469 return OK;
470 }
471
472
473 int
474 pop_list (int msgno, int *nmsgs, int *msgs, int *bytes)
475 {
476 int i;
477 int *ids = NULL;
478
479 if (msgno) {
480 if (command ("LIST %d", msgno) == NOTOK)
481 return NOTOK;
482 *msgs = *bytes = 0;
483 if (ids) {
484 *ids = 0;
485 sscanf (response, "+OK %d %d %d", msgs, bytes, ids);
486 }
487 else
488 sscanf (response, "+OK %d %d", msgs, bytes);
489 return OK;
490 }
491
492 if (command ("LIST") == NOTOK)
493 return NOTOK;
494
495 for (i = 0; i < *nmsgs; i++)
496 switch (multiline ()) {
497 case NOTOK:
498 return NOTOK;
499 case DONE:
500 *nmsgs = ++i;
501 return OK;
502 case OK:
503 *msgs = *bytes = 0;
504 if (ids) {
505 *ids = 0;
506 sscanf (response, "%d %d %d",
507 msgs++, bytes++, ids++);
508 }
509 else
510 sscanf (response, "%d %d", msgs++, bytes++);
511 break;
512 }
513 for (;;)
514 switch (multiline ()) {
515 case NOTOK:
516 return NOTOK;
517 case DONE:
518 return OK;
519 case OK:
520 break;
521 }
522 }
523
524
525 int
526 pop_retr (int msgno, int (*action)(char *))
527 {
528 return traverse (action, "RETR %d", msgno);
529 }
530
531
532 static int
533 traverse (int (*action)(char *), const char *fmt, ...)
534 {
535 int result;
536 va_list ap;
537 char buffer[sizeof(response)];
538
539 va_start(ap, fmt);
540 result = vcommand (fmt, ap);
541 va_end(ap);
542
543 if (result == NOTOK)
544 return NOTOK;
545 strncpy (buffer, response, sizeof(buffer));
546
547 for (;;)
548 switch (multiline ()) {
549 case NOTOK:
550 return NOTOK;
551
552 case DONE:
553 strncpy (response, buffer, sizeof(response));
554 return OK;
555
556 case OK:
557 (*action) (response);
558 break;
559 }
560 }
561
562
563 int
564 pop_dele (int msgno)
565 {
566 return command ("DELE %d", msgno);
567 }
568
569
570 int
571 pop_noop (void)
572 {
573 return command ("NOOP");
574 }
575
576
577 int
578 pop_rset (void)
579 {
580 return command ("RSET");
581 }
582
583
584 int
585 pop_top (int msgno, int lines, int (*action)(char *))
586 {
587 return traverse (action, "TOP %d %d", msgno, lines);
588 }
589
590
591 int
592 pop_quit (void)
593 {
594 int i;
595
596 i = command ("QUIT");
597 pop_done ();
598
599 return i;
600 }
601
602
603 int
604 pop_done (void)
605 {
606 if (nsc)
607 netsec_shutdown(nsc, 1);
608
609 return OK;
610 }
611
612
613 int
614 command(const char *fmt, ...)
615 {
616 va_list ap;
617 int result;
618
619 va_start(ap, fmt);
620 result = vcommand(fmt, ap);
621 va_end(ap);
622
623 return result;
624 }
625
626
627 static int
628 vcommand (const char *fmt, va_list ap)
629 {
630 /* char *cp; */
631 char *errstr;
632
633 if (netsec_vprintf(nsc, &errstr, fmt, ap) != OK) {
634 strncpy(response, errstr, sizeof(response));
635 response[sizeof(response) - 1] = '\0';
636 free(errstr);
637 return NOTOK;
638 }
639
640 if (netsec_printf(nsc, &errstr, "\r\n") != OK) {
641 strncpy(response, errstr, sizeof(response));
642 response[sizeof(response) - 1] = '\0';
643 free(errstr);
644 return NOTOK;
645 }
646
647 if (netsec_flush(nsc, &errstr) != OK) {
648 strncpy(response, errstr, sizeof(response));
649 response[sizeof(response) - 1] = '\0';
650 free(errstr);
651 return NOTOK;
652 }
653
654 switch (pop_getline (response, sizeof response, nsc)) {
655 case OK:
656 if (poprint)
657 fprintf (stderr, "<--- %s\n", response);
658 return (*response == '+' ? OK : NOTOK);
659
660 case NOTOK:
661 case DONE:
662 if (poprint)
663 fprintf (stderr, "%s\n", response);
664 return NOTOK;
665 }
666
667 return NOTOK; /* NOTREACHED */
668 }
669
670
671 int
672 multiline (void)
673 {
674 char buffer[BUFSIZ + TRMLEN];
675
676 if (pop_getline (buffer, sizeof buffer, nsc) != OK)
677 return NOTOK;
678 if (strncmp (buffer, TRM, TRMLEN) == 0) {
679 if (buffer[TRMLEN] == 0)
680 return DONE;
681 else
682 strncpy (response, buffer + TRMLEN, sizeof(response));
683 }
684 else
685 strncpy (response, buffer, sizeof(response));
686
687 return OK;
688 }
689
690 /*
691 * This is now just a thin wrapper around netsec_readline().
692 */
693
694 static int
695 pop_getline (char *s, int n, netsec_context *ns)
696 {
697 /* int c = -2; */
698 char *p;
699 size_t len, destlen;
700 /* int rc; */
701 char *errstr;
702
703 p = netsec_readline(ns, &len, &errstr);
704
705 if (p == NULL) {
706 strncpy(response, errstr, sizeof(response));
707 response[sizeof(response) - 1] = '\0';
708 free(errstr);
709 return NOTOK;
710 }
711
712 /*
713 * If we had an error, it should have been returned already. Since
714 * netsec_readline() strips off the CR-LF ending, just copy the existing
715 * buffer into response now.
716 *
717 * We get a length back from netsec_readline, but the rest of the POP
718 * code doesn't handle it; the assumptions are that everything from
719 * the network can be respresented as C strings. That should get fixed
720 * someday.
721 */
722
723 destlen = len < ((size_t) (n - 1)) ? len : n - 1;
724
725 memcpy(s, p, destlen);
726 s[destlen] = '\0';
727
728 return OK;
729 }