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