2 * popsbr.c -- POP client subroutines
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.
13 # include <sasl/sasl.h>
14 # include <sasl/saslutil.h>
15 # if SASL_VERSION_FULL < 0x020125
16 /* Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype,
17 which has an explicit void parameter list, according to best
18 practice. So we need to cast to avoid compile warnings.
19 Provide this prototype for earlier versions. */
20 typedef int (*sasl_callback_ft)();
21 # endif /* SASL_VERSION_FULL < 0x020125 */
22 #endif /* CYRUS_SASL */
25 #include <h/signals.h>
30 #define TRMLEN (sizeof TRM - 1)
32 static int poprint = 0;
33 static int pophack = 0;
35 char response[BUFSIZ];
41 static sasl_conn_t *conn; /* SASL connection state */
42 static int sasl_complete = 0; /* Has sasl authentication succeeded? */
43 static int maxoutbuf; /* Maximum output buffer size */
44 static sasl_ssf_t sasl_ssf = 0; /* Security strength factor */
45 static int sasl_get_user(void *, int, const char **, unsigned *);
46 static int sasl_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **);
52 static sasl_callback_t callbacks[] = {
53 { SASL_CB_USER, (sasl_callback_ft) sasl_get_user, NULL },
54 #define POP_SASL_CB_N_USER 0
55 { SASL_CB_PASS, (sasl_callback_ft) sasl_get_pass, NULL },
56 #define POP_SASL_CB_N_PASS 1
57 { SASL_CB_LOG, NULL, NULL },
58 { SASL_CB_LIST_END, NULL, NULL },
60 #define SASL_BUFFER_SIZE 262144
62 #else /* CYRUS_SASL */
63 # define sasl_fgetc fgetc
64 #endif /* CYRUS_SASL */
70 static int command(const char *, ...);
71 static int multiline(void);
74 static int pop_auth_sasl(char *, char *, char *);
75 static int sasl_fgetc(FILE *);
76 #endif /* CYRUS_SASL */
78 static int traverse (int (*)(char *), const char *, ...);
79 static int vcommand(const char *, va_list);
80 static int sasl_getline (char *, int, FILE *);
81 static int putline (char *, FILE *);
86 * This function implements the AUTH command for various SASL mechanisms
88 * We do the whole SASL dialog here. If this completes, then we've
89 * authenticated successfully and have (possibly) negotiated a security
94 pop_auth_sasl(char *user, char *host, char *mech)
96 int result, status, sasl_capability = 0;
97 unsigned int buflen, outlen;
98 char server_mechs[256], *buf, outbuf[BUFSIZ];
99 const char *chosen_mech;
100 sasl_security_properties_t secprops;
101 struct pass_context p_context;
106 * First off, we're going to send the CAPA command to see if we can
107 * even support the AUTH command, and if we do, then we'll get a
108 * list of mechanisms the server supports. If we don't support
109 * the CAPA command, then it's unlikely that we will support
113 if (command("CAPA") == NOTOK) {
114 snprintf(response, sizeof(response),
115 "The POP CAPA command failed; POP server does not "
120 while ((status = multiline()) != DONE)
125 case DONE: /* Shouldn't be possible, but just in case */
128 if (strncasecmp(response, "SASL ", 5) == 0) {
130 * We've seen the SASL capability. Grab the mech list
133 strncpy(server_mechs, response + 5, sizeof(server_mechs));
138 if (!sasl_capability) {
139 snprintf(response, sizeof(response), "POP server does not support "
145 * If we received a preferred mechanism, see if the server supports it.
148 if (mech && stringdex(mech, server_mechs) == -1) {
149 snprintf(response, sizeof(response), "Requested SASL mech \"%s\" is "
150 "not in list of supported mechanisms:\n%s",
156 * Start the SASL process. First off, initialize the SASL library.
159 callbacks[POP_SASL_CB_N_USER].context = user;
160 p_context.user = user;
161 p_context.host = host;
162 callbacks[POP_SASL_CB_N_PASS].context = &p_context;
164 result = sasl_client_init(callbacks);
166 if (result != SASL_OK) {
167 snprintf(response, sizeof(response), "SASL library initialization "
168 "failed: %s", sasl_errstring(result, NULL, NULL));
172 result = sasl_client_new("pop", host, NULL, NULL, NULL, 0, &conn);
174 if (result != SASL_OK) {
175 snprintf(response, sizeof(response), "SASL client initialization "
176 "failed: %s", sasl_errstring(result, NULL, NULL));
181 * Initialize the security properties
184 memset(&secprops, 0, sizeof(secprops));
185 secprops.maxbufsize = SASL_BUFFER_SIZE;
186 secprops.max_ssf = UINT_MAX;
188 result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
190 if (result != SASL_OK) {
191 snprintf(response, sizeof(response), "SASL security property "
192 "initialization failed: %s", sasl_errdetail(conn));
197 * Start the actual protocol. Feed the mech list into the library
198 * and get out a possible initial challenge
201 result = sasl_client_start(conn,
202 (const char *) (mech ? mech : server_mechs),
203 NULL, (const char **) &buf,
204 &buflen, &chosen_mech);
206 if (result != SASL_OK && result != SASL_CONTINUE) {
207 snprintf(response, sizeof(response), "SASL client start failed: %s",
208 sasl_errdetail(conn));
213 status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
214 if (status != SASL_OK) {
215 snprintf(response, sizeof(response), "SASL base64 encode "
216 "failed: %s", sasl_errstring(status, NULL, NULL));
220 status = command("AUTH %s %s", chosen_mech, outbuf);
222 status = command("AUTH %s", chosen_mech);
224 while (result == SASL_CONTINUE) {
229 * If we get a "+OK" prefix to our response, then we should
230 * exit out of this exchange now (because authenticated should
234 if (strncmp(response, "+OK", 3) == 0)
238 * Otherwise, make sure the server challenge is correctly formatted
241 if (strncmp(response, "+ ", 2) != 0) {
243 snprintf(response, sizeof(response),
244 "Malformed authentication message from server");
248 result = sasl_decode64(response + 2, strlen(response + 2),
249 outbuf, sizeof(outbuf), &outlen);
251 if (result != SASL_OK) {
253 snprintf(response, sizeof(response), "SASL base64 decode "
254 "failed: %s", sasl_errstring(result, NULL, NULL));
258 result = sasl_client_step(conn, outbuf, outlen, NULL,
259 (const char **) &buf, &buflen);
261 if (result != SASL_OK && result != SASL_CONTINUE) {
263 snprintf(response, sizeof(response), "SASL client negotiaton "
264 "failed: %s", sasl_errdetail(conn));
268 status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
270 if (status != SASL_OK) {
272 snprintf(response, sizeof(response), "SASL base64 encode "
273 "failed: %s", sasl_errstring(status, NULL, NULL));
277 status = command(outbuf);
281 * If we didn't get a positive final response, then error out
282 * (that probably means we failed an authorization check).
289 * We _should_ be okay now. Get a few properties now that negotiation
293 result = sasl_getprop(conn, SASL_MAXOUTBUF, (const void **) &moutbuf);
295 if (result != SASL_OK) {
296 snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated "
297 "output buffer size: %s", sasl_errdetail(conn));
301 maxoutbuf = *moutbuf;
303 result = sasl_getprop(conn, SASL_SSF, (const void **) &ssf);
307 if (result != SASL_OK) {
308 snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated "
309 "security strength factor: %s", sasl_errdetail(conn));
314 * Limit this to what we can deal with. Shouldn't matter much because
315 * this is only outgoing data (which should be small)
318 if (maxoutbuf == 0 || maxoutbuf > BUFSIZ)
327 * Callback to return the userid sent down via the user parameter
331 sasl_get_user(void *context, int id, const char **result, unsigned *len)
333 char *user = (char *) context;
335 if (! result || id != SASL_CB_USER)
336 return SASL_BADPARAM;
346 * Callback to return the password (we call ruserpass, which can get it
351 sasl_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret)
353 struct pass_context *p_context = (struct pass_context *) context;
359 if (! psecret || id != SASL_CB_PASS)
360 return SASL_BADPARAM;
362 ruserpass(p_context->user, &(p_context->host), &pass);
366 *psecret = (sasl_secret_t *) mh_xmalloc(sizeof(sasl_secret_t) + len);
368 (*psecret)->len = len;
369 strcpy((char *) (*psecret)->data, pass);
373 #endif /* CYRUS_SASL */
377 * Split string containing proxy command into an array of arguments
378 * suitable for passing to exec. Returned array must be freed. Shouldn't
379 * be possible to call this with host set to NULL.
382 parse_proxy(char *proxy, char *host)
386 int hlen = strlen(host);
388 unsigned char *cur, *pro;
391 /* skip any initial space */
392 for (pro = (unsigned char *) proxy; isspace(*pro); pro++)
395 /* calculate required size for argument array */
396 for (cur = pro; *cur; cur++) {
397 if (isspace(*cur) && cur[1] && !isspace(cur[1]))
399 else if (*cur == '%' && cur[1] == 'h') {
402 } else if (!isspace(*cur))
406 /* put together list of arguments */
407 p = pargv = mh_xmalloc(pargc * sizeof(char *));
408 c = *pargv = mh_xmalloc(plen * sizeof(char));
409 for (cur = pro; *cur; cur++) {
410 if (isspace(*cur) && cur[1] && !isspace(cur[1])) {
413 } else if (*cur == '%' && cur[1] == 'h') {
417 } else if (!isspace(*cur))
425 pop_init (char *host, char *port, char *user, char *pass, char *proxy,
426 int snoop, int sasl, char *mech)
433 #endif /* ! CYRUS_SASL */
435 if (proxy && *proxy) {
437 int inpipe[2]; /* for reading from the server */
438 int outpipe[2]; /* for sending to the server */
440 /* first give up any root priviledges we may have for rpop */
453 dup2(outpipe[0],0); /* connect read end of connection */
454 dup2(inpipe[1], 1); /* connect write end of connection */
455 if(inpipe[0]>1) close(inpipe[0]);
456 if(inpipe[1]>1) close(inpipe[1]);
457 if(outpipe[0]>1) close(outpipe[0]);
458 if(outpipe[1]>1) close(outpipe[1]);
460 /* run the proxy command */
461 argv=parse_proxy(proxy, host);
462 execvp(argv[0],argv);
472 /* okay in the parent we do some stuff */
473 close(inpipe[1]); /* child uses this */
474 close(outpipe[0]); /* child uses this */
478 /* and write on fd2 */
483 if ((fd1 = client (host, port ? port : "pop3", response,
484 sizeof(response), snoop)) == NOTOK) {
488 if ((fd2 = dup (fd1)) == NOTOK) {
491 if ((s = strerror(errno)))
492 snprintf (response, sizeof(response),
493 "unable to dup connection descriptor: %s", s);
495 snprintf (response, sizeof(response),
496 "unable to dup connection descriptor: unknown error");
501 if (pop_set (fd1, fd2, snoop) == NOTOK)
504 SIGNAL (SIGPIPE, SIG_IGN);
506 switch (sasl_getline (response, sizeof response, input)) {
509 fprintf (stderr, "<--- %s\n", response);
510 if (*response == '+') {
513 if (pop_auth_sasl(user, host, mech) != NOTOK)
516 # endif /* CYRUS_SASL */
517 if (command ("USER %s", user) != NOTOK
518 && command ("%s %s", (pophack++, "PASS"),
522 strncpy (buffer, response, sizeof(buffer));
524 strncpy (response, buffer, sizeof(response));
530 fprintf (stderr, "%s\n", response);
536 return NOTOK; /* NOTREACHED */
540 pop_set (int in, int out, int snoop)
543 if ((input = fdopen (in, "r")) == NULL
544 || (output = fdopen (out, "w")) == NULL) {
545 strncpy (response, "fdopen failed on connection descriptor", sizeof(response));
561 pop_fd (char *in, int inlen, char *out, int outlen)
563 snprintf (in, inlen, "%d", fileno (input));
564 snprintf (out, outlen, "%d", fileno (output));
570 * Find out number of messages available
571 * and their total size.
575 pop_stat (int *nmsgs, int *nbytes)
578 if (command ("STAT") == NOTOK)
581 *nmsgs = *nbytes = 0;
582 sscanf (response, "+OK %d %d", nmsgs, nbytes);
589 pop_list (int msgno, int *nmsgs, int *msgs, int *bytes)
595 if (command ("LIST %d", msgno) == NOTOK)
600 sscanf (response, "+OK %d %d %d", msgs, bytes, ids);
603 sscanf (response, "+OK %d %d", msgs, bytes);
607 if (command ("LIST") == NOTOK)
610 for (i = 0; i < *nmsgs; i++)
611 switch (multiline ()) {
621 sscanf (response, "%d %d %d",
622 msgs++, bytes++, ids++);
625 sscanf (response, "%d %d", msgs++, bytes++);
629 switch (multiline ()) {
641 pop_retr (int msgno, int (*action)(char *))
643 return traverse (action, "RETR %d", msgno);
648 traverse (int (*action)(char *), const char *fmt, ...)
652 char buffer[sizeof(response)];
655 result = vcommand (fmt, ap);
660 strncpy (buffer, response, sizeof(buffer));
663 switch (multiline ()) {
668 strncpy (response, buffer, sizeof(response));
672 (*action) (response);
681 return command ("DELE %d", msgno);
688 return command ("NOOP");
695 return command ("RSET");
700 pop_top (int msgno, int lines, int (*action)(char *))
702 return traverse (action, "TOP %d %d", msgno, lines);
711 i = command ("QUIT");
724 #endif /* CYRUS_SASL */
733 command(const char *fmt, ...)
739 result = vcommand(fmt, ap);
747 vcommand (const char *fmt, va_list ap)
749 char *cp, buffer[BUFSIZ];
751 vsnprintf (buffer, sizeof(buffer), fmt, ap);
755 fprintf(stderr, "(encrypted) ");
756 #endif /* CYRUS_SASL */
758 if ((cp = strchr (buffer, ' ')))
760 fprintf (stderr, "---> %s ********\n", buffer);
766 fprintf (stderr, "---> %s\n", buffer);
769 if (putline (buffer, output) == NOTOK)
773 if (poprint && sasl_ssf)
774 fprintf(stderr, "(decrypted) ");
775 #endif /* CYRUS_SASL */
777 switch (sasl_getline (response, sizeof response, input)) {
780 fprintf (stderr, "<--- %s\n", response);
781 return (*response == '+' ? OK : NOTOK);
786 fprintf (stderr, "%s\n", response);
790 return NOTOK; /* NOTREACHED */
797 char buffer[BUFSIZ + TRMLEN];
799 if (sasl_getline (buffer, sizeof buffer, input) != OK)
805 fprintf(stderr, "(decrypted) ");
806 #endif /* CYRUS_SASL */
807 fprintf (stderr, "<--- %s\n", response);
810 if (strncmp (buffer, TRM, TRMLEN) == 0) {
811 if (buffer[TRMLEN] == 0)
814 strncpy (response, buffer + TRMLEN, sizeof(response));
817 strncpy (response, buffer, sizeof(response));
823 * Note that these functions have been modified to deal with layer encryption
828 sasl_getline (char *s, int n, FILE *iop)
834 while (--n > 0 && (c = sasl_fgetc (iop)) != EOF && c != -2)
835 if ((*p++ = c) == '\n')
839 if (ferror (iop) && c != EOF) {
840 strncpy (response, "error on connection", sizeof(response));
843 if (c == EOF && p == s) {
844 strncpy (response, "connection closed by foreign host", sizeof(response));
858 putline (char *s, FILE *iop)
861 char outbuf[BUFSIZ], *buf;
865 if (!sasl_complete) {
866 #endif /* CYRUS_SASL */
867 fprintf (iop, "%s\r\n", s);
871 * Build an output buffer, encrypt it using sasl_encode, and
872 * squirt out the results.
874 strncpy(outbuf, s, sizeof(outbuf) - 3);
875 outbuf[sizeof(outbuf) - 3] = '\0'; /* Just in case */
876 strcat(outbuf, "\r\n");
878 result = sasl_encode(conn, outbuf, strlen(outbuf),
879 (const char **) &buf, &buflen);
881 if (result != SASL_OK) {
882 snprintf(response, sizeof(response), "SASL encoding error: %s",
883 sasl_errdetail(conn));
887 fwrite(buf, buflen, 1, iop);
889 #endif /* CYRUS_SASL */
893 strncpy (response, "lost connection", sizeof(response));
902 * Okay, our little fgetc replacement. Hopefully this is a little more
903 * efficient than the last one.
908 static unsigned char *buffer = NULL, *ptr;
909 static unsigned int size = 0;
911 unsigned int retbufsize = 0;
913 char *retbuf, tmpbuf[SASL_BUFFER_SIZE];
916 * If we have some leftover data, return that
925 * Otherwise, fill our buffer until we have some data to return.
928 while (retbufsize == 0) {
930 cc = read(fileno(f), tmpbuf, sizeof(tmpbuf));
936 snprintf(response, sizeof(response), "Error during read from "
937 "network: %s", strerror(errno));
942 * We're not allowed to call sasl_decode until sasl_complete is
943 * true, so we do these gyrations ...
946 if (!sasl_complete) {
953 result = sasl_decode(conn, tmpbuf, cc,
954 (const char **) &retbuf, &retbufsize);
956 if (result != SASL_OK) {
957 snprintf(response, sizeof(response), "Error during SASL "
958 "decoding: %s", sasl_errdetail(conn));
964 if (retbufsize > size) {
965 buffer = mh_xrealloc(buffer, retbufsize);
969 memcpy(buffer, retbuf, retbufsize);
971 cnt = retbufsize - 1;
973 return (int) buffer[0];
975 #endif /* CYRUS_SASL */