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