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