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.
14 # include <sasl/sasl.h>
15 # include <sasl/saslutil.h>
16 # if SASL_VERSION_FULL < 0x020125
17 /* Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype,
18 which has an explicit void parameter list, according to best
19 practice. So we need to cast to avoid compile warnings.
20 Provide this prototype for earlier versions. */
21 typedef int (*sasl_callback_ft
)();
22 # endif /* SASL_VERSION_FULL < 0x020125 */
23 #endif /* CYRUS_SASL */
26 #include <h/signals.h>
29 #define TRMLEN (sizeof TRM - 1)
31 static int poprint
= 0;
32 static int pophack
= 0;
34 char response
[BUFSIZ
];
40 static sasl_conn_t
*conn
; /* SASL connection state */
41 static int sasl_complete
= 0; /* Has sasl authentication succeeded? */
42 static int maxoutbuf
; /* Maximum output buffer size */
43 static sasl_ssf_t sasl_ssf
= 0; /* Security strength factor */
44 static int sasl_get_user(void *, int, const char **, unsigned *);
45 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 *);
85 check_mech(char *server_mechs
, size_t server_mechs_size
, char *mech
)
87 int status
, sasl_capability
= 0;
90 * First off, we're going to send the CAPA command to see if we can
91 * even support the AUTH command, and if we do, then we'll get a
92 * list of mechanisms the server supports. If we don't support
93 * the CAPA command, then it's unlikely that we will support
97 if (command("CAPA") == NOTOK
) {
98 snprintf(response
, sizeof(response
),
99 "The POP CAPA command failed; POP server does not "
104 while ((status
= multiline()) != DONE
)
109 case DONE
: /* Shouldn't be possible, but just in case */
112 if (strncasecmp(response
, "SASL ", 5) == 0) {
114 * We've seen the SASL capability. Grab the mech list
117 strncpy(server_mechs
, response
+ 5, server_mechs_size
);
122 if (!sasl_capability
) {
123 snprintf(response
, sizeof(response
), "POP server does not support "
129 * If we received a preferred mechanism, see if the server supports it.
132 if (mech
&& stringdex(mech
, server_mechs
) == -1) {
133 snprintf(response
, sizeof(response
), "Requested SASL mech \"%s\" is "
134 "not in list of supported mechanisms:\n%s",
144 * This function implements the AUTH command for various SASL mechanisms
146 * We do the whole SASL dialog here. If this completes, then we've
147 * authenticated successfully and have (possibly) negotiated a security
151 #define CHECKB64SIZE(insize, outbuf, outsize) \
152 { size_t wantout = (((insize + 2) / 3) * 4) + 32; \
153 if (wantout > outsize) { \
154 outbuf = mh_xrealloc(outbuf, outsize = wantout); \
159 pop_auth_sasl(char *user
, char *host
, char *mech
)
162 unsigned int buflen
, outlen
;
163 char server_mechs
[256], *buf
, *outbuf
= NULL
;
164 size_t outbufsize
= 0;
165 const char *chosen_mech
;
166 sasl_security_properties_t secprops
;
167 struct pass_context p_context
;
171 if ((status
= check_mech(server_mechs
, sizeof(server_mechs
), mech
)) != OK
) {
176 * Start the SASL process. First off, initialize the SASL library.
179 callbacks
[POP_SASL_CB_N_USER
].context
= user
;
180 p_context
.user
= user
;
181 p_context
.host
= host
;
182 callbacks
[POP_SASL_CB_N_PASS
].context
= &p_context
;
184 result
= sasl_client_init(callbacks
);
186 if (result
!= SASL_OK
) {
187 snprintf(response
, sizeof(response
), "SASL library initialization "
188 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
192 result
= sasl_client_new("pop", host
, NULL
, NULL
, NULL
, 0, &conn
);
194 if (result
!= SASL_OK
) {
195 snprintf(response
, sizeof(response
), "SASL client initialization "
196 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
201 * Initialize the security properties
204 memset(&secprops
, 0, sizeof(secprops
));
205 secprops
.maxbufsize
= SASL_BUFFER_SIZE
;
206 secprops
.max_ssf
= UINT_MAX
;
208 result
= sasl_setprop(conn
, SASL_SEC_PROPS
, &secprops
);
210 if (result
!= SASL_OK
) {
211 snprintf(response
, sizeof(response
), "SASL security property "
212 "initialization failed: %s", sasl_errdetail(conn
));
217 * Start the actual protocol. Feed the mech list into the library
218 * and get out a possible initial challenge
221 result
= sasl_client_start(conn
,
222 (const char *) (mech
? mech
: server_mechs
),
223 NULL
, (const char **) &buf
,
224 &buflen
, &chosen_mech
);
226 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
227 snprintf(response
, sizeof(response
), "SASL client start failed: %s",
228 sasl_errdetail(conn
));
233 CHECKB64SIZE(buflen
, outbuf
, outbufsize
);
234 status
= sasl_encode64(buf
, buflen
, outbuf
, outbufsize
, NULL
);
235 if (status
!= SASL_OK
) {
236 snprintf(response
, sizeof(response
), "SASL base64 encode "
237 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
243 status
= command("AUTH %s %s", chosen_mech
, outbuf
);
245 status
= command("AUTH %s", chosen_mech
);
247 while (result
== SASL_CONTINUE
) {
250 if (status
== NOTOK
) {
257 * If we get a "+OK" prefix to our response, then we should
258 * exit out of this exchange now (because authenticated should
262 if (strncmp(response
, "+OK", 3) == 0)
266 * Otherwise, make sure the server challenge is correctly formatted
269 if (strncmp(response
, "+ ", 2) != 0) {
271 snprintf(response
, sizeof(response
),
272 "Malformed authentication message from server");
279 * For decode, it will always be shorter, so just make sure
280 * that outbuf is as at least as big as the encoded response.
283 inlen
= strlen(response
+ 2);
285 if (inlen
> outbufsize
) {
286 outbuf
= mh_xrealloc(outbuf
, outbufsize
= inlen
);
289 result
= sasl_decode64(response
+ 2, strlen(response
+ 2),
290 outbuf
, outbufsize
, &outlen
);
292 if (result
!= SASL_OK
) {
294 snprintf(response
, sizeof(response
), "SASL base64 decode "
295 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
301 result
= sasl_client_step(conn
, outbuf
, outlen
, NULL
,
302 (const char **) &buf
, &buflen
);
304 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
306 snprintf(response
, sizeof(response
), "SASL client negotiaton "
307 "failed: %s", sasl_errdetail(conn
));
313 CHECKB64SIZE(buflen
, outbuf
, outbufsize
);
315 status
= sasl_encode64(buf
, buflen
, outbuf
, outbufsize
, NULL
);
317 if (status
!= SASL_OK
) {
319 snprintf(response
, sizeof(response
), "SASL base64 encode "
320 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
326 status
= command(outbuf
);
333 * If we didn't get a positive final response, then error out
334 * (that probably means we failed an authorization check).
341 * We _should_ be okay now. Get a few properties now that negotiation
345 result
= sasl_getprop(conn
, SASL_MAXOUTBUF
, (const void **) &moutbuf
);
347 if (result
!= SASL_OK
) {
348 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
349 "output buffer size: %s", sasl_errdetail(conn
));
353 maxoutbuf
= *moutbuf
;
355 result
= sasl_getprop(conn
, SASL_SSF
, (const void **) &ssf
);
359 if (result
!= SASL_OK
) {
360 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
361 "security strength factor: %s", sasl_errdetail(conn
));
366 * Limit this to what we can deal with. Shouldn't matter much because
367 * this is only outgoing data (which should be small)
370 if (maxoutbuf
== 0 || maxoutbuf
> BUFSIZ
)
379 * Callback to return the userid sent down via the user parameter
383 sasl_get_user(void *context
, int id
, const char **result
, unsigned *len
)
385 char *user
= (char *) context
;
387 if (! result
|| id
!= SASL_CB_USER
)
388 return SASL_BADPARAM
;
398 * Callback to return the password (we call ruserpass, which can get it
403 sasl_get_pass(sasl_conn_t
*conn
, void *context
, int id
, sasl_secret_t
**psecret
)
405 struct pass_context
*p_context
= (struct pass_context
*) context
;
406 struct nmh_creds creds
= { 0, 0, 0 };
411 if (! psecret
|| id
!= SASL_CB_PASS
)
412 return SASL_BADPARAM
;
414 if (creds
.password
== NULL
) {
416 * Pass the 0 third argument to nmh_get_credentials() so
417 * that the default password isn't used. With legacy/.netrc
418 * credentials support, we'll only get here if the -user
419 * switch to send(1)/post(8) wasn't used.
421 if (nmh_get_credentials (p_context
->host
, p_context
->user
, 0, &creds
)
423 return SASL_BADPARAM
;
427 len
= strlen (creds
.password
);
429 *psecret
= (sasl_secret_t
*) mh_xmalloc(sizeof(sasl_secret_t
) + len
);
431 (*psecret
)->len
= len
;
432 strcpy((char *) (*psecret
)->data
, creds
.password
);
436 #endif /* CYRUS_SASL */
439 pop_auth_xoauth(const char *client_res
)
441 char server_mechs
[256];
442 int status
= check_mech(server_mechs
, sizeof(server_mechs
), "XOAUTH");
444 if (status
!= OK
) return status
;
446 if ((status
= command("AUTH XOAUTH2 %s", client_res
)) != OK
) {
449 if (strncmp(response
, "+OK", 3) == 0) {
453 /* response contains base64-encoded JSON, which is always the same.
454 * See mts/smtp/smtp.c for more notes on that. */
455 /* Then we're supposed to send an empty response ("\r\n"). */
460 * Split string containing proxy command into an array of arguments
461 * suitable for passing to exec. Returned array must be freed. Shouldn't
462 * be possible to call this with host set to NULL.
465 parse_proxy(char *proxy
, char *host
)
469 int hlen
= strlen(host
);
471 unsigned char *cur
, *pro
;
474 /* skip any initial space */
475 for (pro
= (unsigned char *) proxy
; isspace(*pro
); pro
++)
478 /* calculate required size for argument array */
479 for (cur
= pro
; *cur
; cur
++) {
480 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1]))
482 else if (*cur
== '%' && cur
[1] == 'h') {
485 } else if (!isspace(*cur
))
489 /* put together list of arguments */
490 p
= pargv
= mh_xmalloc(pargc
* sizeof(char *));
491 c
= *pargv
= mh_xmalloc(plen
* sizeof(char));
492 for (cur
= pro
; *cur
; cur
++) {
493 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1])) {
496 } else if (*cur
== '%' && cur
[1] == 'h') {
500 } else if (!isspace(*cur
))
509 pop_init (char *host
, char *port
, char *user
, char *pass
, char *proxy
,
510 int snoop
, int sasl
, char *mech
, const char *oauth_svc
)
514 const char *xoauth_client_res
= NULL
;
518 #endif /* ! CYRUS_SASL */
521 if (oauth_svc
!= NULL
) {
522 xoauth_client_res
= mh_oauth_do_xoauth(user
, oauth_svc
,
523 snoop
? stderr
: NULL
);
526 NMH_UNUSED (oauth_svc
);
527 NMH_UNUSED (xoauth_client_res
);
528 #endif /* OAUTH_SUPPORT */
530 if (proxy
&& *proxy
) {
532 int inpipe
[2]; /* for reading from the server */
533 int outpipe
[2]; /* for sending to the server */
535 if (pipe(inpipe
) < 0) {
536 adios ("inpipe", "pipe");
538 if (pipe(outpipe
) < 0) {
539 adios ("outpipe", "pipe");
549 dup2(outpipe
[0],0); /* connect read end of connection */
550 dup2(inpipe
[1], 1); /* connect write end of connection */
551 if(inpipe
[0]>1) close(inpipe
[0]);
552 if(inpipe
[1]>1) close(inpipe
[1]);
553 if(outpipe
[0]>1) close(outpipe
[0]);
554 if(outpipe
[1]>1) close(outpipe
[1]);
556 /* run the proxy command */
557 argv
=parse_proxy(proxy
, host
);
558 execvp(argv
[0],argv
);
568 /* okay in the parent we do some stuff */
569 close(inpipe
[1]); /* child uses this */
570 close(outpipe
[0]); /* child uses this */
574 /* and write on fd2 */
579 if ((fd1
= client (host
, port
? port
: "pop3", response
,
580 sizeof(response
), snoop
)) == NOTOK
) {
584 if ((fd2
= dup (fd1
)) == NOTOK
) {
587 if ((s
= strerror(errno
)))
588 snprintf (response
, sizeof(response
),
589 "unable to dup connection descriptor: %s", s
);
591 snprintf (response
, sizeof(response
),
592 "unable to dup connection descriptor: unknown error");
597 if (pop_set (fd1
, fd2
, snoop
) == NOTOK
)
600 SIGNAL (SIGPIPE
, SIG_IGN
);
602 switch (sasl_getline (response
, sizeof response
, input
)) {
605 fprintf (stderr
, "<--- %s\n", response
);
606 if (*response
== '+') {
609 if (pop_auth_sasl(user
, host
, mech
) != NOTOK
)
612 # endif /* CYRUS_SASL */
614 if (xoauth_client_res
!= NULL
) {
615 if (pop_auth_xoauth(xoauth_client_res
) != NOTOK
)
618 # endif /* OAUTH_SUPPORT */
619 if (command ("USER %s", user
) != NOTOK
620 && command ("%s %s", (pophack
++, "PASS"),
624 strncpy (buffer
, response
, sizeof(buffer
));
626 strncpy (response
, buffer
, sizeof(response
));
632 fprintf (stderr
, "%s\n", response
);
638 return NOTOK
; /* NOTREACHED */
642 pop_set (int in
, int out
, int snoop
)
645 if ((input
= fdopen (in
, "r")) == NULL
646 || (output
= fdopen (out
, "w")) == NULL
) {
647 strncpy (response
, "fdopen failed on connection descriptor", sizeof(response
));
663 pop_fd (char *in
, int inlen
, char *out
, int outlen
)
665 snprintf (in
, inlen
, "%d", fileno (input
));
666 snprintf (out
, outlen
, "%d", fileno (output
));
672 * Find out number of messages available
673 * and their total size.
677 pop_stat (int *nmsgs
, int *nbytes
)
680 if (command ("STAT") == NOTOK
)
683 *nmsgs
= *nbytes
= 0;
684 sscanf (response
, "+OK %d %d", nmsgs
, nbytes
);
691 pop_list (int msgno
, int *nmsgs
, int *msgs
, int *bytes
)
697 if (command ("LIST %d", msgno
) == NOTOK
)
702 sscanf (response
, "+OK %d %d %d", msgs
, bytes
, ids
);
705 sscanf (response
, "+OK %d %d", msgs
, bytes
);
709 if (command ("LIST") == NOTOK
)
712 for (i
= 0; i
< *nmsgs
; i
++)
713 switch (multiline ()) {
723 sscanf (response
, "%d %d %d",
724 msgs
++, bytes
++, ids
++);
727 sscanf (response
, "%d %d", msgs
++, bytes
++);
731 switch (multiline ()) {
743 pop_retr (int msgno
, int (*action
)(char *))
745 return traverse (action
, "RETR %d", msgno
);
750 traverse (int (*action
)(char *), const char *fmt
, ...)
754 char buffer
[sizeof(response
)];
757 result
= vcommand (fmt
, ap
);
762 strncpy (buffer
, response
, sizeof(buffer
));
765 switch (multiline ()) {
770 strncpy (response
, buffer
, sizeof(response
));
774 (*action
) (response
);
783 return command ("DELE %d", msgno
);
790 return command ("NOOP");
797 return command ("RSET");
802 pop_top (int msgno
, int lines
, int (*action
)(char *))
804 return traverse (action
, "TOP %d %d", msgno
, lines
);
813 i
= command ("QUIT");
826 #endif /* CYRUS_SASL */
835 command(const char *fmt
, ...)
841 result
= vcommand(fmt
, ap
);
849 vcommand (const char *fmt
, va_list ap
)
851 char *cp
, buffer
[65536];
853 vsnprintf (buffer
, sizeof(buffer
), fmt
, ap
);
858 fprintf(stderr
, "(encrypted) ");
859 #endif /* CYRUS_SASL */
861 if ((cp
= strchr (buffer
, ' ')))
863 fprintf (stderr
, "---> %s ********\n", buffer
);
869 fprintf (stderr
, "---> %s\n", buffer
);
872 if (putline (buffer
, output
) == NOTOK
)
876 if (poprint
&& sasl_ssf
)
877 fprintf(stderr
, "(decrypted) ");
878 #endif /* CYRUS_SASL */
880 switch (sasl_getline (response
, sizeof response
, input
)) {
883 fprintf (stderr
, "<--- %s\n", response
);
884 return (*response
== '+' ? OK
: NOTOK
);
889 fprintf (stderr
, "%s\n", response
);
893 return NOTOK
; /* NOTREACHED */
900 char buffer
[BUFSIZ
+ TRMLEN
];
902 if (sasl_getline (buffer
, sizeof buffer
, input
) != OK
)
908 fprintf(stderr
, "(decrypted) ");
909 #endif /* CYRUS_SASL */
910 fprintf (stderr
, "<--- %s\n", response
);
913 if (strncmp (buffer
, TRM
, TRMLEN
) == 0) {
914 if (buffer
[TRMLEN
] == 0)
917 strncpy (response
, buffer
+ TRMLEN
, sizeof(response
));
920 strncpy (response
, buffer
, sizeof(response
));
926 * Note that these functions have been modified to deal with layer encryption
931 sasl_getline (char *s
, int n
, FILE *iop
)
937 while (--n
> 0 && (c
= sasl_fgetc (iop
)) != EOF
&& c
!= -2)
938 if ((*p
++ = c
) == '\n')
942 if (ferror (iop
) && c
!= EOF
) {
943 strncpy (response
, "error on connection", sizeof(response
));
946 if (c
== EOF
&& p
== s
) {
947 strncpy (response
, "connection closed by foreign host", sizeof(response
));
953 if (p
> s
&& *--p
== '\r')
961 putline (char *s
, FILE *iop
)
964 char outbuf
[BUFSIZ
], *buf
;
968 if (!sasl_complete
) {
969 #endif /* CYRUS_SASL */
970 fprintf (iop
, "%s\r\n", s
);
974 * Build an output buffer, encrypt it using sasl_encode, and
975 * squirt out the results.
977 strncpy(outbuf
, s
, sizeof(outbuf
) - 3);
978 outbuf
[sizeof(outbuf
) - 3] = '\0'; /* Just in case */
979 strcat(outbuf
, "\r\n");
981 result
= sasl_encode(conn
, outbuf
, strlen(outbuf
),
982 (const char **) &buf
, &buflen
);
984 if (result
!= SASL_OK
) {
985 snprintf(response
, sizeof(response
), "SASL encoding error: %s",
986 sasl_errdetail(conn
));
990 if (fwrite(buf
, buflen
, 1, iop
) < 1) {
991 advise ("putline", "fwrite");
994 #endif /* CYRUS_SASL */
998 strncpy (response
, "lost connection", sizeof(response
));
1007 * Okay, our little fgetc replacement. Hopefully this is a little more
1008 * efficient than the last one.
1013 static unsigned char *buffer
= NULL
, *ptr
;
1014 static unsigned int size
= 0;
1016 unsigned int retbufsize
= 0;
1018 char *retbuf
, tmpbuf
[SASL_BUFFER_SIZE
];
1021 * If we have some leftover data, return that
1026 return (int) *ptr
++;
1030 * Otherwise, fill our buffer until we have some data to return.
1033 while (retbufsize
== 0) {
1035 cc
= read(fileno(f
), tmpbuf
, sizeof(tmpbuf
));
1041 snprintf(response
, sizeof(response
), "Error during read from "
1042 "network: %s", strerror(errno
));
1047 * We're not allowed to call sasl_decode until sasl_complete is
1048 * true, so we do these gyrations ...
1051 if (!sasl_complete
) {
1058 result
= sasl_decode(conn
, tmpbuf
, cc
,
1059 (const char **) &retbuf
, &retbufsize
);
1061 if (result
!= SASL_OK
) {
1062 snprintf(response
, sizeof(response
), "Error during SASL "
1063 "decoding: %s", sasl_errdetail(conn
));
1069 if (retbufsize
> size
) {
1070 buffer
= mh_xrealloc(buffer
, retbufsize
);
1074 memcpy(buffer
, retbuf
, retbufsize
);
1076 cnt
= retbufsize
- 1;
1078 return (int) buffer
[0];
1080 #endif /* CYRUS_SASL */