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 */
490 if (pipe(inpipe
) < 0) {
491 adios ("inpipe", "pipe");
493 if (pipe(outpipe
) < 0) {
494 adios ("outpipe", "pipe");
504 dup2(outpipe
[0],0); /* connect read end of connection */
505 dup2(inpipe
[1], 1); /* connect write end of connection */
506 if(inpipe
[0]>1) close(inpipe
[0]);
507 if(inpipe
[1]>1) close(inpipe
[1]);
508 if(outpipe
[0]>1) close(outpipe
[0]);
509 if(outpipe
[1]>1) close(outpipe
[1]);
511 /* run the proxy command */
512 argv
=parse_proxy(proxy
, host
);
513 execvp(argv
[0],argv
);
523 /* okay in the parent we do some stuff */
524 close(inpipe
[1]); /* child uses this */
525 close(outpipe
[0]); /* child uses this */
529 /* and write on fd2 */
534 if ((fd1
= client (host
, port
? port
: "pop3", response
,
535 sizeof(response
), snoop
)) == NOTOK
) {
539 if ((fd2
= dup (fd1
)) == NOTOK
) {
542 if ((s
= strerror(errno
)))
543 snprintf (response
, sizeof(response
),
544 "unable to dup connection descriptor: %s", s
);
546 snprintf (response
, sizeof(response
),
547 "unable to dup connection descriptor: unknown error");
552 if (pop_set (fd1
, fd2
, snoop
) == NOTOK
)
555 SIGNAL (SIGPIPE
, SIG_IGN
);
557 switch (sasl_getline (response
, sizeof response
, input
)) {
560 fprintf (stderr
, "<--- %s\n", response
);
561 if (*response
== '+') {
564 if (pop_auth_sasl(user
, host
, mech
) != NOTOK
)
567 # endif /* CYRUS_SASL */
568 if (command ("USER %s", user
) != NOTOK
569 && command ("%s %s", (pophack
++, "PASS"),
573 strncpy (buffer
, response
, sizeof(buffer
));
575 strncpy (response
, buffer
, sizeof(response
));
581 fprintf (stderr
, "%s\n", response
);
587 return NOTOK
; /* NOTREACHED */
591 pop_set (int in
, int out
, int snoop
)
594 if ((input
= fdopen (in
, "r")) == NULL
595 || (output
= fdopen (out
, "w")) == NULL
) {
596 strncpy (response
, "fdopen failed on connection descriptor", sizeof(response
));
612 pop_fd (char *in
, int inlen
, char *out
, int outlen
)
614 snprintf (in
, inlen
, "%d", fileno (input
));
615 snprintf (out
, outlen
, "%d", fileno (output
));
621 * Find out number of messages available
622 * and their total size.
626 pop_stat (int *nmsgs
, int *nbytes
)
629 if (command ("STAT") == NOTOK
)
632 *nmsgs
= *nbytes
= 0;
633 sscanf (response
, "+OK %d %d", nmsgs
, nbytes
);
640 pop_list (int msgno
, int *nmsgs
, int *msgs
, int *bytes
)
646 if (command ("LIST %d", msgno
) == NOTOK
)
651 sscanf (response
, "+OK %d %d %d", msgs
, bytes
, ids
);
654 sscanf (response
, "+OK %d %d", msgs
, bytes
);
658 if (command ("LIST") == NOTOK
)
661 for (i
= 0; i
< *nmsgs
; i
++)
662 switch (multiline ()) {
672 sscanf (response
, "%d %d %d",
673 msgs
++, bytes
++, ids
++);
676 sscanf (response
, "%d %d", msgs
++, bytes
++);
680 switch (multiline ()) {
692 pop_retr (int msgno
, int (*action
)(char *))
694 return traverse (action
, "RETR %d", msgno
);
699 traverse (int (*action
)(char *), const char *fmt
, ...)
703 char buffer
[sizeof(response
)];
706 result
= vcommand (fmt
, ap
);
711 strncpy (buffer
, response
, sizeof(buffer
));
714 switch (multiline ()) {
719 strncpy (response
, buffer
, sizeof(response
));
723 (*action
) (response
);
732 return command ("DELE %d", msgno
);
739 return command ("NOOP");
746 return command ("RSET");
751 pop_top (int msgno
, int lines
, int (*action
)(char *))
753 return traverse (action
, "TOP %d %d", msgno
, lines
);
762 i
= command ("QUIT");
775 #endif /* CYRUS_SASL */
784 command(const char *fmt
, ...)
790 result
= vcommand(fmt
, ap
);
798 vcommand (const char *fmt
, va_list ap
)
800 char *cp
, buffer
[65536];
802 vsnprintf (buffer
, sizeof(buffer
), fmt
, ap
);
807 fprintf(stderr
, "(encrypted) ");
808 #endif /* CYRUS_SASL */
810 if ((cp
= strchr (buffer
, ' ')))
812 fprintf (stderr
, "---> %s ********\n", buffer
);
818 fprintf (stderr
, "---> %s\n", buffer
);
821 if (putline (buffer
, output
) == NOTOK
)
825 if (poprint
&& sasl_ssf
)
826 fprintf(stderr
, "(decrypted) ");
827 #endif /* CYRUS_SASL */
829 switch (sasl_getline (response
, sizeof response
, input
)) {
832 fprintf (stderr
, "<--- %s\n", response
);
833 return (*response
== '+' ? OK
: NOTOK
);
838 fprintf (stderr
, "%s\n", response
);
842 return NOTOK
; /* NOTREACHED */
849 char buffer
[BUFSIZ
+ TRMLEN
];
851 if (sasl_getline (buffer
, sizeof buffer
, input
) != OK
)
857 fprintf(stderr
, "(decrypted) ");
858 #endif /* CYRUS_SASL */
859 fprintf (stderr
, "<--- %s\n", response
);
862 if (strncmp (buffer
, TRM
, TRMLEN
) == 0) {
863 if (buffer
[TRMLEN
] == 0)
866 strncpy (response
, buffer
+ TRMLEN
, sizeof(response
));
869 strncpy (response
, buffer
, sizeof(response
));
875 * Note that these functions have been modified to deal with layer encryption
880 sasl_getline (char *s
, int n
, FILE *iop
)
886 while (--n
> 0 && (c
= sasl_fgetc (iop
)) != EOF
&& c
!= -2)
887 if ((*p
++ = c
) == '\n')
891 if (ferror (iop
) && c
!= EOF
) {
892 strncpy (response
, "error on connection", sizeof(response
));
895 if (c
== EOF
&& p
== s
) {
896 strncpy (response
, "connection closed by foreign host", sizeof(response
));
910 putline (char *s
, FILE *iop
)
913 char outbuf
[BUFSIZ
], *buf
;
917 if (!sasl_complete
) {
918 #endif /* CYRUS_SASL */
919 fprintf (iop
, "%s\r\n", s
);
923 * Build an output buffer, encrypt it using sasl_encode, and
924 * squirt out the results.
926 strncpy(outbuf
, s
, sizeof(outbuf
) - 3);
927 outbuf
[sizeof(outbuf
) - 3] = '\0'; /* Just in case */
928 strcat(outbuf
, "\r\n");
930 result
= sasl_encode(conn
, outbuf
, strlen(outbuf
),
931 (const char **) &buf
, &buflen
);
933 if (result
!= SASL_OK
) {
934 snprintf(response
, sizeof(response
), "SASL encoding error: %s",
935 sasl_errdetail(conn
));
939 fwrite(buf
, buflen
, 1, iop
);
941 #endif /* CYRUS_SASL */
945 strncpy (response
, "lost connection", sizeof(response
));
954 * Okay, our little fgetc replacement. Hopefully this is a little more
955 * efficient than the last one.
960 static unsigned char *buffer
= NULL
, *ptr
;
961 static unsigned int size
= 0;
963 unsigned int retbufsize
= 0;
965 char *retbuf
, tmpbuf
[SASL_BUFFER_SIZE
];
968 * If we have some leftover data, return that
977 * Otherwise, fill our buffer until we have some data to return.
980 while (retbufsize
== 0) {
982 cc
= read(fileno(f
), tmpbuf
, sizeof(tmpbuf
));
988 snprintf(response
, sizeof(response
), "Error during read from "
989 "network: %s", strerror(errno
));
994 * We're not allowed to call sasl_decode until sasl_complete is
995 * true, so we do these gyrations ...
998 if (!sasl_complete
) {
1005 result
= sasl_decode(conn
, tmpbuf
, cc
,
1006 (const char **) &retbuf
, &retbufsize
);
1008 if (result
!= SASL_OK
) {
1009 snprintf(response
, sizeof(response
), "Error during SASL "
1010 "decoding: %s", sasl_errdetail(conn
));
1016 if (retbufsize
> size
) {
1017 buffer
= mh_xrealloc(buffer
, retbufsize
);
1021 memcpy(buffer
, retbuf
, retbufsize
);
1023 cnt
= retbufsize
- 1;
1025 return (int) buffer
[0];
1027 #endif /* CYRUS_SASL */