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