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