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>
28 #define TRMLEN (sizeof TRM - 1)
30 static int poprint
= 0;
31 static int pophack
= 0;
33 char response
[BUFSIZ
];
39 static sasl_conn_t
*conn
; /* SASL connection state */
40 static int sasl_complete
= 0; /* Has sasl authentication succeeded? */
41 static int maxoutbuf
; /* Maximum output buffer size */
42 static sasl_ssf_t sasl_ssf
= 0; /* Security strength factor */
43 static int sasl_get_user(void *, int, const char **, unsigned *);
44 static int sasl_get_pass(sasl_conn_t
*, void *, int, sasl_secret_t
**);
51 static sasl_callback_t callbacks
[] = {
52 { SASL_CB_USER
, (sasl_callback_ft
) sasl_get_user
, NULL
},
53 #define POP_SASL_CB_N_USER 0
54 { SASL_CB_PASS
, (sasl_callback_ft
) sasl_get_pass
, NULL
},
55 #define POP_SASL_CB_N_PASS 1
56 { SASL_CB_LOG
, NULL
, NULL
},
57 { SASL_CB_LIST_END
, NULL
, NULL
},
59 #define SASL_BUFFER_SIZE 262144
61 #else /* CYRUS_SASL */
62 # define sasl_fgetc fgetc
63 #endif /* CYRUS_SASL */
69 static int command(const char *, ...);
70 static int multiline(void);
73 static int pop_auth_sasl(char *, char *, char *);
74 static int sasl_fgetc(FILE *);
75 #endif /* CYRUS_SASL */
77 static int traverse (int (*)(char *), const char *, ...);
78 static int vcommand(const char *, va_list);
79 static int sasl_getline (char *, int, FILE *);
80 static int putline (char *, FILE *);
85 * This function implements the AUTH command for various SASL mechanisms
87 * We do the whole SASL dialog here. If this completes, then we've
88 * authenticated successfully and have (possibly) negotiated a security
92 #define CHECKB64SIZE(insize, outbuf, outsize) \
93 { size_t wantout = (((insize + 2) / 3) * 4) + 32; \
94 if (wantout > outsize) { \
95 outbuf = mh_xrealloc(outbuf, outsize = wantout); \
100 pop_auth_sasl(char *user
, char *host
, char *mech
)
102 int result
, status
, sasl_capability
= 0;
103 unsigned int buflen
, outlen
;
104 char server_mechs
[256], *buf
, *outbuf
= NULL
;
105 size_t outbufsize
= 0;
106 const char *chosen_mech
;
107 sasl_security_properties_t secprops
;
108 struct pass_context p_context
;
113 * First off, we're going to send the CAPA command to see if we can
114 * even support the AUTH command, and if we do, then we'll get a
115 * list of mechanisms the server supports. If we don't support
116 * the CAPA command, then it's unlikely that we will support
120 if (command("CAPA") == NOTOK
) {
121 snprintf(response
, sizeof(response
),
122 "The POP CAPA command failed; POP server does not "
127 while ((status
= multiline()) != DONE
)
132 case DONE
: /* Shouldn't be possible, but just in case */
135 if (strncasecmp(response
, "SASL ", 5) == 0) {
137 * We've seen the SASL capability. Grab the mech list
140 strncpy(server_mechs
, response
+ 5, sizeof(server_mechs
));
145 if (!sasl_capability
) {
146 snprintf(response
, sizeof(response
), "POP server does not support "
152 * If we received a preferred mechanism, see if the server supports it.
155 if (mech
&& stringdex(mech
, server_mechs
) == -1) {
156 snprintf(response
, sizeof(response
), "Requested SASL mech \"%s\" is "
157 "not in list of supported mechanisms:\n%s",
163 * Start the SASL process. First off, initialize the SASL library.
166 callbacks
[POP_SASL_CB_N_USER
].context
= user
;
167 p_context
.user
= user
;
168 p_context
.host
= host
;
169 callbacks
[POP_SASL_CB_N_PASS
].context
= &p_context
;
171 result
= sasl_client_init(callbacks
);
173 if (result
!= SASL_OK
) {
174 snprintf(response
, sizeof(response
), "SASL library initialization "
175 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
179 result
= sasl_client_new("pop", host
, NULL
, NULL
, NULL
, 0, &conn
);
181 if (result
!= SASL_OK
) {
182 snprintf(response
, sizeof(response
), "SASL client initialization "
183 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
188 * Initialize the security properties
191 memset(&secprops
, 0, sizeof(secprops
));
192 secprops
.maxbufsize
= SASL_BUFFER_SIZE
;
193 secprops
.max_ssf
= UINT_MAX
;
195 result
= sasl_setprop(conn
, SASL_SEC_PROPS
, &secprops
);
197 if (result
!= SASL_OK
) {
198 snprintf(response
, sizeof(response
), "SASL security property "
199 "initialization failed: %s", sasl_errdetail(conn
));
204 * Start the actual protocol. Feed the mech list into the library
205 * and get out a possible initial challenge
208 result
= sasl_client_start(conn
,
209 (const char *) (mech
? mech
: server_mechs
),
210 NULL
, (const char **) &buf
,
211 &buflen
, &chosen_mech
);
213 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
214 snprintf(response
, sizeof(response
), "SASL client start failed: %s",
215 sasl_errdetail(conn
));
220 CHECKB64SIZE(buflen
, outbuf
, outbufsize
);
221 status
= sasl_encode64(buf
, buflen
, outbuf
, outbufsize
, NULL
);
222 if (status
!= SASL_OK
) {
223 snprintf(response
, sizeof(response
), "SASL base64 encode "
224 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
230 status
= command("AUTH %s %s", chosen_mech
, outbuf
);
232 status
= command("AUTH %s", chosen_mech
);
234 while (result
== SASL_CONTINUE
) {
237 if (status
== NOTOK
) {
244 * If we get a "+OK" prefix to our response, then we should
245 * exit out of this exchange now (because authenticated should
249 if (strncmp(response
, "+OK", 3) == 0)
253 * Otherwise, make sure the server challenge is correctly formatted
256 if (strncmp(response
, "+ ", 2) != 0) {
258 snprintf(response
, sizeof(response
),
259 "Malformed authentication message from server");
266 * For decode, it will always be shorter, so just make sure
267 * that outbuf is as at least as big as the encoded response.
270 inlen
= strlen(response
+ 2);
272 if (inlen
> outbufsize
) {
273 outbuf
= mh_xrealloc(outbuf
, outbufsize
= inlen
);
276 result
= sasl_decode64(response
+ 2, strlen(response
+ 2),
277 outbuf
, outbufsize
, &outlen
);
279 if (result
!= SASL_OK
) {
281 snprintf(response
, sizeof(response
), "SASL base64 decode "
282 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
288 result
= sasl_client_step(conn
, outbuf
, outlen
, NULL
,
289 (const char **) &buf
, &buflen
);
291 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
293 snprintf(response
, sizeof(response
), "SASL client negotiaton "
294 "failed: %s", sasl_errdetail(conn
));
300 CHECKB64SIZE(buflen
, outbuf
, outbufsize
);
302 status
= sasl_encode64(buf
, buflen
, outbuf
, outbufsize
, NULL
);
304 if (status
!= SASL_OK
) {
306 snprintf(response
, sizeof(response
), "SASL base64 encode "
307 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
313 status
= command(outbuf
);
320 * If we didn't get a positive final response, then error out
321 * (that probably means we failed an authorization check).
328 * We _should_ be okay now. Get a few properties now that negotiation
332 result
= sasl_getprop(conn
, SASL_MAXOUTBUF
, (const void **) &moutbuf
);
334 if (result
!= SASL_OK
) {
335 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
336 "output buffer size: %s", sasl_errdetail(conn
));
340 maxoutbuf
= *moutbuf
;
342 result
= sasl_getprop(conn
, SASL_SSF
, (const void **) &ssf
);
346 if (result
!= SASL_OK
) {
347 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
348 "security strength factor: %s", sasl_errdetail(conn
));
353 * Limit this to what we can deal with. Shouldn't matter much because
354 * this is only outgoing data (which should be small)
357 if (maxoutbuf
== 0 || maxoutbuf
> BUFSIZ
)
366 * Callback to return the userid sent down via the user parameter
370 sasl_get_user(void *context
, int id
, const char **result
, unsigned *len
)
372 char *user
= (char *) context
;
374 if (! result
|| id
!= SASL_CB_USER
)
375 return SASL_BADPARAM
;
385 * Callback to return the password (we call ruserpass, which can get it
390 sasl_get_pass(sasl_conn_t
*conn
, void *context
, int id
, sasl_secret_t
**psecret
)
392 struct pass_context
*p_context
= (struct pass_context
*) context
;
393 struct nmh_creds creds
= { 0, 0, 0 };
398 if (! psecret
|| id
!= SASL_CB_PASS
)
399 return SASL_BADPARAM
;
401 if (creds
.password
== NULL
) {
403 * Pass the 0 third argument to nmh_get_credentials() so
404 * that the default password isn't used. With legacy/.netrc
405 * credentials support, we'll only get here if the -user
406 * switch to send(1)/post(8) wasn't used.
408 if (nmh_get_credentials (p_context
->host
, p_context
->user
, 0, &creds
)
410 return SASL_BADPARAM
;
414 len
= strlen (creds
.password
);
416 *psecret
= (sasl_secret_t
*) mh_xmalloc(sizeof(sasl_secret_t
) + len
);
418 (*psecret
)->len
= len
;
419 strcpy((char *) (*psecret
)->data
, creds
.password
);
423 #endif /* CYRUS_SASL */
427 * Split string containing proxy command into an array of arguments
428 * suitable for passing to exec. Returned array must be freed. Shouldn't
429 * be possible to call this with host set to NULL.
432 parse_proxy(char *proxy
, char *host
)
436 int hlen
= strlen(host
);
438 unsigned char *cur
, *pro
;
441 /* skip any initial space */
442 for (pro
= (unsigned char *) proxy
; isspace(*pro
); pro
++)
445 /* calculate required size for argument array */
446 for (cur
= pro
; *cur
; cur
++) {
447 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1]))
449 else if (*cur
== '%' && cur
[1] == 'h') {
452 } else if (!isspace(*cur
))
456 /* put together list of arguments */
457 p
= pargv
= mh_xmalloc(pargc
* sizeof(char *));
458 c
= *pargv
= mh_xmalloc(plen
* sizeof(char));
459 for (cur
= pro
; *cur
; cur
++) {
460 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1])) {
463 } else if (*cur
== '%' && cur
[1] == 'h') {
467 } else if (!isspace(*cur
))
475 pop_init (char *host
, char *port
, char *user
, char *pass
, char *proxy
,
476 int snoop
, int sasl
, char *mech
)
483 #endif /* ! CYRUS_SASL */
485 if (proxy
&& *proxy
) {
487 int inpipe
[2]; /* for reading from the server */
488 int outpipe
[2]; /* for sending to the server */
500 dup2(outpipe
[0],0); /* connect read end of connection */
501 dup2(inpipe
[1], 1); /* connect write end of connection */
502 if(inpipe
[0]>1) close(inpipe
[0]);
503 if(inpipe
[1]>1) close(inpipe
[1]);
504 if(outpipe
[0]>1) close(outpipe
[0]);
505 if(outpipe
[1]>1) close(outpipe
[1]);
507 /* run the proxy command */
508 argv
=parse_proxy(proxy
, host
);
509 execvp(argv
[0],argv
);
519 /* okay in the parent we do some stuff */
520 close(inpipe
[1]); /* child uses this */
521 close(outpipe
[0]); /* child uses this */
525 /* and write on fd2 */
530 if ((fd1
= client (host
, port
? port
: "pop3", response
,
531 sizeof(response
), snoop
)) == NOTOK
) {
535 if ((fd2
= dup (fd1
)) == NOTOK
) {
538 if ((s
= strerror(errno
)))
539 snprintf (response
, sizeof(response
),
540 "unable to dup connection descriptor: %s", s
);
542 snprintf (response
, sizeof(response
),
543 "unable to dup connection descriptor: unknown error");
548 if (pop_set (fd1
, fd2
, snoop
) == NOTOK
)
551 SIGNAL (SIGPIPE
, SIG_IGN
);
553 switch (sasl_getline (response
, sizeof response
, input
)) {
556 fprintf (stderr
, "<--- %s\n", response
);
557 if (*response
== '+') {
560 if (pop_auth_sasl(user
, host
, mech
) != NOTOK
)
563 # endif /* CYRUS_SASL */
564 if (command ("USER %s", user
) != NOTOK
565 && command ("%s %s", (pophack
++, "PASS"),
569 strncpy (buffer
, response
, sizeof(buffer
));
571 strncpy (response
, buffer
, sizeof(response
));
577 fprintf (stderr
, "%s\n", response
);
583 return NOTOK
; /* NOTREACHED */
587 pop_set (int in
, int out
, int snoop
)
590 if ((input
= fdopen (in
, "r")) == NULL
591 || (output
= fdopen (out
, "w")) == NULL
) {
592 strncpy (response
, "fdopen failed on connection descriptor", sizeof(response
));
608 pop_fd (char *in
, int inlen
, char *out
, int outlen
)
610 snprintf (in
, inlen
, "%d", fileno (input
));
611 snprintf (out
, outlen
, "%d", fileno (output
));
617 * Find out number of messages available
618 * and their total size.
622 pop_stat (int *nmsgs
, int *nbytes
)
625 if (command ("STAT") == NOTOK
)
628 *nmsgs
= *nbytes
= 0;
629 sscanf (response
, "+OK %d %d", nmsgs
, nbytes
);
636 pop_list (int msgno
, int *nmsgs
, int *msgs
, int *bytes
)
642 if (command ("LIST %d", msgno
) == NOTOK
)
647 sscanf (response
, "+OK %d %d %d", msgs
, bytes
, ids
);
650 sscanf (response
, "+OK %d %d", msgs
, bytes
);
654 if (command ("LIST") == NOTOK
)
657 for (i
= 0; i
< *nmsgs
; i
++)
658 switch (multiline ()) {
668 sscanf (response
, "%d %d %d",
669 msgs
++, bytes
++, ids
++);
672 sscanf (response
, "%d %d", msgs
++, bytes
++);
676 switch (multiline ()) {
688 pop_retr (int msgno
, int (*action
)(char *))
690 return traverse (action
, "RETR %d", msgno
);
695 traverse (int (*action
)(char *), const char *fmt
, ...)
699 char buffer
[sizeof(response
)];
702 result
= vcommand (fmt
, ap
);
707 strncpy (buffer
, response
, sizeof(buffer
));
710 switch (multiline ()) {
715 strncpy (response
, buffer
, sizeof(response
));
719 (*action
) (response
);
728 return command ("DELE %d", msgno
);
735 return command ("NOOP");
742 return command ("RSET");
747 pop_top (int msgno
, int lines
, int (*action
)(char *))
749 return traverse (action
, "TOP %d %d", msgno
, lines
);
758 i
= command ("QUIT");
771 #endif /* CYRUS_SASL */
780 command(const char *fmt
, ...)
786 result
= vcommand(fmt
, ap
);
794 vcommand (const char *fmt
, va_list ap
)
796 char *cp
, buffer
[65536];
798 vsnprintf (buffer
, sizeof(buffer
), fmt
, ap
);
803 fprintf(stderr
, "(encrypted) ");
804 #endif /* CYRUS_SASL */
806 if ((cp
= strchr (buffer
, ' ')))
808 fprintf (stderr
, "---> %s ********\n", buffer
);
814 fprintf (stderr
, "---> %s\n", buffer
);
817 if (putline (buffer
, output
) == NOTOK
)
821 if (poprint
&& sasl_ssf
)
822 fprintf(stderr
, "(decrypted) ");
823 #endif /* CYRUS_SASL */
825 switch (sasl_getline (response
, sizeof response
, input
)) {
828 fprintf (stderr
, "<--- %s\n", response
);
829 return (*response
== '+' ? OK
: NOTOK
);
834 fprintf (stderr
, "%s\n", response
);
838 return NOTOK
; /* NOTREACHED */
845 char buffer
[BUFSIZ
+ TRMLEN
];
847 if (sasl_getline (buffer
, sizeof buffer
, input
) != OK
)
853 fprintf(stderr
, "(decrypted) ");
854 #endif /* CYRUS_SASL */
855 fprintf (stderr
, "<--- %s\n", response
);
858 if (strncmp (buffer
, TRM
, TRMLEN
) == 0) {
859 if (buffer
[TRMLEN
] == 0)
862 strncpy (response
, buffer
+ TRMLEN
, sizeof(response
));
865 strncpy (response
, buffer
, sizeof(response
));
871 * Note that these functions have been modified to deal with layer encryption
876 sasl_getline (char *s
, int n
, FILE *iop
)
882 while (--n
> 0 && (c
= sasl_fgetc (iop
)) != EOF
&& c
!= -2)
883 if ((*p
++ = c
) == '\n')
887 if (ferror (iop
) && c
!= EOF
) {
888 strncpy (response
, "error on connection", sizeof(response
));
891 if (c
== EOF
&& p
== s
) {
892 strncpy (response
, "connection closed by foreign host", sizeof(response
));
906 putline (char *s
, FILE *iop
)
909 char outbuf
[BUFSIZ
], *buf
;
913 if (!sasl_complete
) {
914 #endif /* CYRUS_SASL */
915 fprintf (iop
, "%s\r\n", s
);
919 * Build an output buffer, encrypt it using sasl_encode, and
920 * squirt out the results.
922 strncpy(outbuf
, s
, sizeof(outbuf
) - 3);
923 outbuf
[sizeof(outbuf
) - 3] = '\0'; /* Just in case */
924 strcat(outbuf
, "\r\n");
926 result
= sasl_encode(conn
, outbuf
, strlen(outbuf
),
927 (const char **) &buf
, &buflen
);
929 if (result
!= SASL_OK
) {
930 snprintf(response
, sizeof(response
), "SASL encoding error: %s",
931 sasl_errdetail(conn
));
935 fwrite(buf
, buflen
, 1, iop
);
937 #endif /* CYRUS_SASL */
941 strncpy (response
, "lost connection", sizeof(response
));
950 * Okay, our little fgetc replacement. Hopefully this is a little more
951 * efficient than the last one.
956 static unsigned char *buffer
= NULL
, *ptr
;
957 static unsigned int size
= 0;
959 unsigned int retbufsize
= 0;
961 char *retbuf
, tmpbuf
[SASL_BUFFER_SIZE
];
964 * If we have some leftover data, return that
973 * Otherwise, fill our buffer until we have some data to return.
976 while (retbufsize
== 0) {
978 cc
= read(fileno(f
), tmpbuf
, sizeof(tmpbuf
));
984 snprintf(response
, sizeof(response
), "Error during read from "
985 "network: %s", strerror(errno
));
990 * We're not allowed to call sasl_decode until sasl_complete is
991 * true, so we do these gyrations ...
994 if (!sasl_complete
) {
1001 result
= sasl_decode(conn
, tmpbuf
, cc
,
1002 (const char **) &retbuf
, &retbufsize
);
1004 if (result
!= SASL_OK
) {
1005 snprintf(response
, sizeof(response
), "Error during SASL "
1006 "decoding: %s", sasl_errdetail(conn
));
1012 if (retbufsize
> size
) {
1013 buffer
= mh_xrealloc(buffer
, retbufsize
);
1017 memcpy(buffer
, retbuf
, retbufsize
);
1019 cnt
= retbufsize
- 1;
1021 return (int) buffer
[0];
1023 #endif /* CYRUS_SASL */