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