]> diplodocus.org Git - nmh/blob - uip/popsbr.c
datetime.c: Replace some int with bool.
[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 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 * sizeof(char));
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(10);
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 &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 fprintf (stderr, "%s\n", response);
275 netsec_shutdown(nsc);
276 nsc = NULL;
277 return NOTOK;
278 }
279
280 return NOTOK; /* NOTREACHED */
281 }
282
283
284 /*
285 * Our SASL callback; we are given SASL tokens and then have to format
286 * them according to the protocol requirements, and then process incoming
287 * messages and feed them back into the SASL library.
288 */
289
290 static int
291 pop_sasl_callback(enum sasl_message_type mtype, unsigned const char *indata,
292 unsigned int indatalen, unsigned char **outdata,
293 unsigned int *outdatalen, char **errstr)
294 {
295 int rc, snoopoffset;
296 char *mech, *line;
297 size_t len, b64len;
298
299 switch (mtype) {
300 case NETSEC_SASL_START:
301 /*
302 * Generate our AUTH message, but there is a wrinkle.
303 *
304 * Technically, according to RFC 5034, if your command INCLUDING
305 * an initial response exceeds 255 octets (including CRLF), you
306 * can't issue this all in one go, but have to just issue the
307 * AUTH command, wait for a blank initial response, and then
308 * send your data.
309 */
310
311 mech = netsec_get_sasl_mechanism(nsc);
312
313 if (indatalen) {
314 char *b64data;
315 b64data = mh_xmalloc(BASE64SIZE(indatalen));
316 writeBase64raw(indata, indatalen, (unsigned char *) b64data);
317 b64len = strlen(b64data);
318
319 /* Formula here is AUTH + SP + mech + SP + out + CR + LF */
320 len = b64len + 8 + strlen(mech);
321 if (len > 255) {
322 rc = netsec_printf(nsc, errstr, "AUTH %s\r\n", mech);
323 if (rc)
324 return NOTOK;
325 if (netsec_flush(nsc, errstr) != OK)
326 return NOTOK;
327 line = netsec_readline(nsc, &len, errstr);
328 if (! line)
329 return NOTOK;
330 /*
331 * If the protocol is being followed correctly, should just
332 * be a "+ ", nothing else.
333 */
334 if (len != 2 || strcmp(line, "+ ") != 0) {
335 netsec_err(errstr, "Did not get expected blank response "
336 "for initial challenge response");
337 return NOTOK;
338 }
339 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
340 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
341 netsec_set_snoop_callback(nsc, NULL, NULL);
342 free(b64data);
343 if (rc != OK)
344 return NOTOK;
345 if (netsec_flush(nsc, errstr) != OK)
346 return NOTOK;
347 } else {
348 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder,
349 &snoopoffset);
350 snoopoffset = 6 + strlen(mech);
351 rc = netsec_printf(nsc, errstr, "AUTH %s %s\r\n", mech,
352 b64data);
353 free(b64data);
354 netsec_set_snoop_callback(nsc, NULL, NULL);
355 if (rc != OK)
356 return NOTOK;
357 if (netsec_flush(nsc, errstr) != OK)
358 return NOTOK;
359 }
360 } else {
361 if (netsec_printf(nsc, errstr, "AUTH %s\r\n", mech) != OK)
362 return NOTOK;
363 if (netsec_flush(nsc, errstr) != OK)
364 return NOTOK;
365 }
366
367 break;
368
369 /*
370 * We should get one line back, with our base64 data. Decode that
371 * and feed it back into the SASL library.
372 */
373 case NETSEC_SASL_READ:
374 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, &snoopoffset);
375 snoopoffset = 2;
376 line = netsec_readline(nsc, &len, errstr);
377 netsec_set_snoop_callback(nsc, NULL, NULL);
378
379 if (line == NULL)
380 return NOTOK;
381 if (len < 2 || (len == 2 && strcmp(line, "+ ") != 0)) {
382 netsec_err(errstr, "Invalid format for SASL response");
383 return NOTOK;
384 }
385
386 if (len == 2) {
387 *outdata = NULL;
388 *outdatalen = 0;
389 } else {
390 rc = decodeBase64(line + 2, outdata, &len, 0, NULL);
391 *outdatalen = len;
392 if (rc != OK) {
393 netsec_err(errstr, "Unable to decode base64 response");
394 return NOTOK;
395 }
396 }
397 break;
398
399 /*
400 * Our encoding is pretty simple, so this is easy.
401 */
402
403 case NETSEC_SASL_WRITE:
404 if (indatalen == 0) {
405 rc = netsec_printf(nsc, errstr, "\r\n");
406 } else {
407 unsigned char *b64data;
408 b64data = mh_xmalloc(BASE64SIZE(indatalen));
409 writeBase64raw(indata, indatalen, b64data);
410 netsec_set_snoop_callback(nsc, netsec_b64_snoop_decoder, NULL);
411 rc = netsec_printf(nsc, errstr, "%s\r\n", b64data);
412 netsec_set_snoop_callback(nsc, NULL, NULL);
413 free(b64data);
414 }
415
416 if (rc != OK)
417 return NOTOK;
418
419 if (netsec_flush(nsc, errstr) != OK)
420 return NOTOK;
421 break;
422
423 /*
424 * Finish the protocol; we're looking for an +OK
425 */
426
427 case NETSEC_SASL_FINISH:
428 line = netsec_readline(nsc, &len, errstr);
429 if (line == NULL)
430 return NOTOK;
431
432 if (!has_prefix(line, "+OK")) {
433 netsec_err(errstr, "Authentication failed: %s", line);
434 return NOTOK;
435 }
436 break;
437
438 /*
439 * Cancel the SASL exchange in the middle of the commands; for
440 * POP, that's a single "*".
441 *
442 * It's unclear to me if I should be returning errors up; I finally
443 * decided the answer should be "yes", and if the upper layer wants to
444 * ignore them that's their choice.
445 */
446
447 case NETSEC_SASL_CANCEL:
448 rc = netsec_printf(nsc, errstr, "*\r\n");
449 if (rc == OK)
450 rc = netsec_flush(nsc, errstr);
451 if (rc != OK)
452 return NOTOK;
453 break;
454 }
455
456 return OK;
457 }
458
459 /*
460 * Find out number of messages available
461 * and their total size.
462 */
463
464 int
465 pop_stat (int *nmsgs, int *nbytes)
466 {
467
468 if (command ("STAT") == NOTOK)
469 return NOTOK;
470
471 *nmsgs = *nbytes = 0;
472 sscanf (response, "+OK %d %d", nmsgs, nbytes);
473
474 return OK;
475 }
476
477
478 int
479 pop_retr (int msgno, int (*action)(void *, char *), void *closure)
480 {
481 return traverse (action, closure, "RETR %d", msgno);
482 }
483
484
485 static int
486 traverse (int (*action)(void *, char *), void *closure, const char *fmt, ...)
487 {
488 int result, snoopstate;
489 va_list ap;
490 char buffer[sizeof(response)];
491
492 va_start(ap, fmt);
493 result = vcommand (fmt, ap);
494 va_end(ap);
495
496 if (result == NOTOK)
497 return NOTOK;
498 strncpy (buffer, response, sizeof(buffer));
499
500 if ((snoopstate = netsec_get_snoop(nsc)))
501 netsec_set_snoop(nsc, 0);
502
503 for (;;) {
504 result = multiline();
505 if (result == OK) {
506 result = (*action)(closure, response);
507 if (result == OK)
508 continue;
509 } else if (result == DONE) {
510 strncpy(response, buffer, sizeof(response));
511 result = OK;
512 }
513 break;
514 }
515
516 netsec_set_snoop(nsc, snoopstate);
517 return result;
518 }
519
520
521 int
522 pop_dele (int msgno)
523 {
524 return command ("DELE %d", msgno);
525 }
526
527
528 int
529 pop_quit (void)
530 {
531 int i;
532
533 i = command ("QUIT");
534 pop_done ();
535
536 return i;
537 }
538
539
540 int
541 pop_done (void)
542 {
543 if (nsc)
544 netsec_shutdown(nsc);
545
546 return OK;
547 }
548
549
550 int
551 command(const char *fmt, ...)
552 {
553 va_list ap;
554 int result;
555
556 va_start(ap, fmt);
557 result = vcommand(fmt, ap);
558 va_end(ap);
559
560 return result;
561 }
562
563
564 static int
565 vcommand (const char *fmt, va_list ap)
566 {
567 /* char *cp; */
568 char *errstr;
569
570 if (netsec_vprintf(nsc, &errstr, fmt, ap) != OK) {
571 strncpy(response, errstr, sizeof(response));
572 response[sizeof(response) - 1] = '\0';
573 free(errstr);
574 return NOTOK;
575 }
576
577 if (netsec_printf(nsc, &errstr, "\r\n") != OK) {
578 strncpy(response, errstr, sizeof(response));
579 response[sizeof(response) - 1] = '\0';
580 free(errstr);
581 return NOTOK;
582 }
583
584 if (netsec_flush(nsc, &errstr) != OK) {
585 strncpy(response, errstr, sizeof(response));
586 response[sizeof(response) - 1] = '\0';
587 free(errstr);
588 return NOTOK;
589 }
590
591 switch (pop_getline (response, sizeof response, nsc)) {
592 case OK:
593 if (poprint)
594 fprintf (stderr, "<--- %s\n", response);
595 return (*response == '+' ? OK : NOTOK);
596
597 case NOTOK:
598 case DONE:
599 if (poprint)
600 fprintf (stderr, "%s\n", response);
601 return NOTOK;
602 }
603
604 return NOTOK; /* NOTREACHED */
605 }
606
607
608 int
609 multiline (void)
610 {
611 char buffer[BUFSIZ + LEN(TRM)];
612
613 if (pop_getline (buffer, sizeof buffer, nsc) != OK)
614 return NOTOK;
615 if (has_prefix(buffer, TRM)) {
616 if (buffer[LEN(TRM)] == 0)
617 return DONE;
618 strncpy (response, buffer + LEN(TRM), sizeof(response));
619 }
620 else
621 strncpy (response, buffer, sizeof(response));
622
623 return OK;
624 }
625
626 /*
627 * This is now just a thin wrapper around netsec_readline().
628 */
629
630 static int
631 pop_getline (char *s, int n, netsec_context *ns)
632 {
633 /* int c = -2; */
634 char *p;
635 size_t len, destlen;
636 /* int rc; */
637 char *errstr;
638
639 p = netsec_readline(ns, &len, &errstr);
640
641 if (p == NULL) {
642 strncpy(response, errstr, sizeof(response));
643 response[sizeof(response) - 1] = '\0';
644 free(errstr);
645 return NOTOK;
646 }
647
648 /*
649 * If we had an error, it should have been returned already. Since
650 * netsec_readline() strips off the CR-LF ending, just copy the existing
651 * buffer into response now.
652 *
653 * We get a length back from netsec_readline, but the rest of the POP
654 * code doesn't handle it; the assumptions are that everything from
655 * the network can be represented as C strings. That should get fixed
656 * someday.
657 */
658
659 destlen = min(len, (size_t)(n - 1));
660
661 memcpy(s, p, destlen);
662 s[destlen] = '\0';
663
664 return OK;
665 }