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