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