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