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
93 pop_auth_sasl(char *user
, char *host
, char *mech
)
95 int result
, status
, sasl_capability
= 0;
96 unsigned int buflen
, outlen
;
97 char server_mechs
[256], *buf
, outbuf
[BUFSIZ
];
98 const char *chosen_mech
;
99 sasl_security_properties_t secprops
;
100 struct pass_context p_context
;
105 * First off, we're going to send the CAPA command to see if we can
106 * even support the AUTH command, and if we do, then we'll get a
107 * list of mechanisms the server supports. If we don't support
108 * the CAPA command, then it's unlikely that we will support
112 if (command("CAPA") == NOTOK
) {
113 snprintf(response
, sizeof(response
),
114 "The POP CAPA command failed; POP server does not "
119 while ((status
= multiline()) != DONE
)
124 case DONE
: /* Shouldn't be possible, but just in case */
127 if (strncasecmp(response
, "SASL ", 5) == 0) {
129 * We've seen the SASL capability. Grab the mech list
132 strncpy(server_mechs
, response
+ 5, sizeof(server_mechs
));
137 if (!sasl_capability
) {
138 snprintf(response
, sizeof(response
), "POP server does not support "
144 * If we received a preferred mechanism, see if the server supports it.
147 if (mech
&& stringdex(mech
, server_mechs
) == -1) {
148 snprintf(response
, sizeof(response
), "Requested SASL mech \"%s\" is "
149 "not in list of supported mechanisms:\n%s",
155 * Start the SASL process. First off, initialize the SASL library.
158 callbacks
[POP_SASL_CB_N_USER
].context
= user
;
159 p_context
.user
= user
;
160 p_context
.host
= host
;
161 callbacks
[POP_SASL_CB_N_PASS
].context
= &p_context
;
163 result
= sasl_client_init(callbacks
);
165 if (result
!= SASL_OK
) {
166 snprintf(response
, sizeof(response
), "SASL library initialization "
167 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
171 result
= sasl_client_new("pop", host
, NULL
, NULL
, NULL
, 0, &conn
);
173 if (result
!= SASL_OK
) {
174 snprintf(response
, sizeof(response
), "SASL client initialization "
175 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
180 * Initialize the security properties
183 memset(&secprops
, 0, sizeof(secprops
));
184 secprops
.maxbufsize
= SASL_BUFFER_SIZE
;
185 secprops
.max_ssf
= UINT_MAX
;
187 result
= sasl_setprop(conn
, SASL_SEC_PROPS
, &secprops
);
189 if (result
!= SASL_OK
) {
190 snprintf(response
, sizeof(response
), "SASL security property "
191 "initialization failed: %s", sasl_errdetail(conn
));
196 * Start the actual protocol. Feed the mech list into the library
197 * and get out a possible initial challenge
200 result
= sasl_client_start(conn
,
201 (const char *) (mech
? mech
: server_mechs
),
202 NULL
, (const char **) &buf
,
203 &buflen
, &chosen_mech
);
205 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
206 snprintf(response
, sizeof(response
), "SASL client start failed: %s",
207 sasl_errdetail(conn
));
212 status
= sasl_encode64(buf
, buflen
, outbuf
, sizeof(outbuf
), NULL
);
213 if (status
!= SASL_OK
) {
214 snprintf(response
, sizeof(response
), "SASL base64 encode "
215 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
219 status
= command("AUTH %s %s", chosen_mech
, outbuf
);
221 status
= command("AUTH %s", chosen_mech
);
223 while (result
== SASL_CONTINUE
) {
228 * If we get a "+OK" prefix to our response, then we should
229 * exit out of this exchange now (because authenticated should
233 if (strncmp(response
, "+OK", 3) == 0)
237 * Otherwise, make sure the server challenge is correctly formatted
240 if (strncmp(response
, "+ ", 2) != 0) {
242 snprintf(response
, sizeof(response
),
243 "Malformed authentication message from server");
247 result
= sasl_decode64(response
+ 2, strlen(response
+ 2),
248 outbuf
, sizeof(outbuf
), &outlen
);
250 if (result
!= SASL_OK
) {
252 snprintf(response
, sizeof(response
), "SASL base64 decode "
253 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
257 result
= sasl_client_step(conn
, outbuf
, outlen
, NULL
,
258 (const char **) &buf
, &buflen
);
260 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
262 snprintf(response
, sizeof(response
), "SASL client negotiaton "
263 "failed: %s", sasl_errdetail(conn
));
267 status
= sasl_encode64(buf
, buflen
, outbuf
, sizeof(outbuf
), NULL
);
269 if (status
!= SASL_OK
) {
271 snprintf(response
, sizeof(response
), "SASL base64 encode "
272 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
276 status
= command(outbuf
);
280 * If we didn't get a positive final response, then error out
281 * (that probably means we failed an authorization check).
288 * We _should_ be okay now. Get a few properties now that negotiation
292 result
= sasl_getprop(conn
, SASL_MAXOUTBUF
, (const void **) &moutbuf
);
294 if (result
!= SASL_OK
) {
295 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
296 "output buffer size: %s", sasl_errdetail(conn
));
300 maxoutbuf
= *moutbuf
;
302 result
= sasl_getprop(conn
, SASL_SSF
, (const void **) &ssf
);
306 if (result
!= SASL_OK
) {
307 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
308 "security strength factor: %s", sasl_errdetail(conn
));
313 * Limit this to what we can deal with. Shouldn't matter much because
314 * this is only outgoing data (which should be small)
317 if (maxoutbuf
== 0 || maxoutbuf
> BUFSIZ
)
326 * Callback to return the userid sent down via the user parameter
330 sasl_get_user(void *context
, int id
, const char **result
, unsigned *len
)
332 char *user
= (char *) context
;
334 if (! result
|| id
!= SASL_CB_USER
)
335 return SASL_BADPARAM
;
345 * Callback to return the password (we call ruserpass, which can get it
350 sasl_get_pass(sasl_conn_t
*conn
, void *context
, int id
, sasl_secret_t
**psecret
)
352 struct pass_context
*p_context
= (struct pass_context
*) context
;
353 struct nmh_creds creds
= { 0, 0, 0 };
358 if (! psecret
|| id
!= SASL_CB_PASS
)
359 return SASL_BADPARAM
;
361 if (creds
.password
== NULL
) {
363 * Pass the 0 third argument to nmh_get_credentials() so
364 * that the default password isn't used. With legacy/.netrc
365 * credentials support, we'll only get here if the -user
366 * switch to send(1)/post(8) wasn't used.
368 if (nmh_get_credentials (p_context
->host
, p_context
->user
, 0, &creds
)
370 return SASL_BADPARAM
;
374 len
= strlen (creds
.password
);
376 *psecret
= (sasl_secret_t
*) mh_xmalloc(sizeof(sasl_secret_t
) + len
);
378 (*psecret
)->len
= len
;
379 strcpy((char *) (*psecret
)->data
, creds
.password
);
383 #endif /* CYRUS_SASL */
387 * Split string containing proxy command into an array of arguments
388 * suitable for passing to exec. Returned array must be freed. Shouldn't
389 * be possible to call this with host set to NULL.
392 parse_proxy(char *proxy
, char *host
)
396 int hlen
= strlen(host
);
398 unsigned char *cur
, *pro
;
401 /* skip any initial space */
402 for (pro
= (unsigned char *) proxy
; isspace(*pro
); pro
++)
405 /* calculate required size for argument array */
406 for (cur
= pro
; *cur
; cur
++) {
407 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1]))
409 else if (*cur
== '%' && cur
[1] == 'h') {
412 } else if (!isspace(*cur
))
416 /* put together list of arguments */
417 p
= pargv
= mh_xmalloc(pargc
* sizeof(char *));
418 c
= *pargv
= mh_xmalloc(plen
* sizeof(char));
419 for (cur
= pro
; *cur
; cur
++) {
420 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1])) {
423 } else if (*cur
== '%' && cur
[1] == 'h') {
427 } else if (!isspace(*cur
))
435 pop_init (char *host
, char *port
, char *user
, char *pass
, char *proxy
,
436 int snoop
, int sasl
, char *mech
)
443 #endif /* ! CYRUS_SASL */
445 if (proxy
&& *proxy
) {
447 int inpipe
[2]; /* for reading from the server */
448 int outpipe
[2]; /* for sending to the server */
460 dup2(outpipe
[0],0); /* connect read end of connection */
461 dup2(inpipe
[1], 1); /* connect write end of connection */
462 if(inpipe
[0]>1) close(inpipe
[0]);
463 if(inpipe
[1]>1) close(inpipe
[1]);
464 if(outpipe
[0]>1) close(outpipe
[0]);
465 if(outpipe
[1]>1) close(outpipe
[1]);
467 /* run the proxy command */
468 argv
=parse_proxy(proxy
, host
);
469 execvp(argv
[0],argv
);
479 /* okay in the parent we do some stuff */
480 close(inpipe
[1]); /* child uses this */
481 close(outpipe
[0]); /* child uses this */
485 /* and write on fd2 */
490 if ((fd1
= client (host
, port
? port
: "pop3", response
,
491 sizeof(response
), snoop
)) == NOTOK
) {
495 if ((fd2
= dup (fd1
)) == NOTOK
) {
498 if ((s
= strerror(errno
)))
499 snprintf (response
, sizeof(response
),
500 "unable to dup connection descriptor: %s", s
);
502 snprintf (response
, sizeof(response
),
503 "unable to dup connection descriptor: unknown error");
508 if (pop_set (fd1
, fd2
, snoop
) == NOTOK
)
511 SIGNAL (SIGPIPE
, SIG_IGN
);
513 switch (sasl_getline (response
, sizeof response
, input
)) {
516 fprintf (stderr
, "<--- %s\n", response
);
517 if (*response
== '+') {
520 if (pop_auth_sasl(user
, host
, mech
) != NOTOK
)
523 # endif /* CYRUS_SASL */
524 if (command ("USER %s", user
) != NOTOK
525 && command ("%s %s", (pophack
++, "PASS"),
529 strncpy (buffer
, response
, sizeof(buffer
));
531 strncpy (response
, buffer
, sizeof(response
));
537 fprintf (stderr
, "%s\n", response
);
543 return NOTOK
; /* NOTREACHED */
547 pop_set (int in
, int out
, int snoop
)
550 if ((input
= fdopen (in
, "r")) == NULL
551 || (output
= fdopen (out
, "w")) == NULL
) {
552 strncpy (response
, "fdopen failed on connection descriptor", sizeof(response
));
568 pop_fd (char *in
, int inlen
, char *out
, int outlen
)
570 snprintf (in
, inlen
, "%d", fileno (input
));
571 snprintf (out
, outlen
, "%d", fileno (output
));
577 * Find out number of messages available
578 * and their total size.
582 pop_stat (int *nmsgs
, int *nbytes
)
585 if (command ("STAT") == NOTOK
)
588 *nmsgs
= *nbytes
= 0;
589 sscanf (response
, "+OK %d %d", nmsgs
, nbytes
);
596 pop_list (int msgno
, int *nmsgs
, int *msgs
, int *bytes
)
602 if (command ("LIST %d", msgno
) == NOTOK
)
607 sscanf (response
, "+OK %d %d %d", msgs
, bytes
, ids
);
610 sscanf (response
, "+OK %d %d", msgs
, bytes
);
614 if (command ("LIST") == NOTOK
)
617 for (i
= 0; i
< *nmsgs
; i
++)
618 switch (multiline ()) {
628 sscanf (response
, "%d %d %d",
629 msgs
++, bytes
++, ids
++);
632 sscanf (response
, "%d %d", msgs
++, bytes
++);
636 switch (multiline ()) {
648 pop_retr (int msgno
, int (*action
)(char *))
650 return traverse (action
, "RETR %d", msgno
);
655 traverse (int (*action
)(char *), const char *fmt
, ...)
659 char buffer
[sizeof(response
)];
662 result
= vcommand (fmt
, ap
);
667 strncpy (buffer
, response
, sizeof(buffer
));
670 switch (multiline ()) {
675 strncpy (response
, buffer
, sizeof(response
));
679 (*action
) (response
);
688 return command ("DELE %d", msgno
);
695 return command ("NOOP");
702 return command ("RSET");
707 pop_top (int msgno
, int lines
, int (*action
)(char *))
709 return traverse (action
, "TOP %d %d", msgno
, lines
);
718 i
= command ("QUIT");
731 #endif /* CYRUS_SASL */
740 command(const char *fmt
, ...)
746 result
= vcommand(fmt
, ap
);
754 vcommand (const char *fmt
, va_list ap
)
756 char *cp
, buffer
[BUFSIZ
];
758 vsnprintf (buffer
, sizeof(buffer
), fmt
, ap
);
762 fprintf(stderr
, "(encrypted) ");
763 #endif /* CYRUS_SASL */
765 if ((cp
= strchr (buffer
, ' ')))
767 fprintf (stderr
, "---> %s ********\n", buffer
);
773 fprintf (stderr
, "---> %s\n", buffer
);
776 if (putline (buffer
, output
) == NOTOK
)
780 if (poprint
&& sasl_ssf
)
781 fprintf(stderr
, "(decrypted) ");
782 #endif /* CYRUS_SASL */
784 switch (sasl_getline (response
, sizeof response
, input
)) {
787 fprintf (stderr
, "<--- %s\n", response
);
788 return (*response
== '+' ? OK
: NOTOK
);
793 fprintf (stderr
, "%s\n", response
);
797 return NOTOK
; /* NOTREACHED */
804 char buffer
[BUFSIZ
+ TRMLEN
];
806 if (sasl_getline (buffer
, sizeof buffer
, input
) != OK
)
812 fprintf(stderr
, "(decrypted) ");
813 #endif /* CYRUS_SASL */
814 fprintf (stderr
, "<--- %s\n", response
);
817 if (strncmp (buffer
, TRM
, TRMLEN
) == 0) {
818 if (buffer
[TRMLEN
] == 0)
821 strncpy (response
, buffer
+ TRMLEN
, sizeof(response
));
824 strncpy (response
, buffer
, sizeof(response
));
830 * Note that these functions have been modified to deal with layer encryption
835 sasl_getline (char *s
, int n
, FILE *iop
)
841 while (--n
> 0 && (c
= sasl_fgetc (iop
)) != EOF
&& c
!= -2)
842 if ((*p
++ = c
) == '\n')
846 if (ferror (iop
) && c
!= EOF
) {
847 strncpy (response
, "error on connection", sizeof(response
));
850 if (c
== EOF
&& p
== s
) {
851 strncpy (response
, "connection closed by foreign host", sizeof(response
));
865 putline (char *s
, FILE *iop
)
868 char outbuf
[BUFSIZ
], *buf
;
872 if (!sasl_complete
) {
873 #endif /* CYRUS_SASL */
874 fprintf (iop
, "%s\r\n", s
);
878 * Build an output buffer, encrypt it using sasl_encode, and
879 * squirt out the results.
881 strncpy(outbuf
, s
, sizeof(outbuf
) - 3);
882 outbuf
[sizeof(outbuf
) - 3] = '\0'; /* Just in case */
883 strcat(outbuf
, "\r\n");
885 result
= sasl_encode(conn
, outbuf
, strlen(outbuf
),
886 (const char **) &buf
, &buflen
);
888 if (result
!= SASL_OK
) {
889 snprintf(response
, sizeof(response
), "SASL encoding error: %s",
890 sasl_errdetail(conn
));
894 fwrite(buf
, buflen
, 1, iop
);
896 #endif /* CYRUS_SASL */
900 strncpy (response
, "lost connection", sizeof(response
));
909 * Okay, our little fgetc replacement. Hopefully this is a little more
910 * efficient than the last one.
915 static unsigned char *buffer
= NULL
, *ptr
;
916 static unsigned int size
= 0;
918 unsigned int retbufsize
= 0;
920 char *retbuf
, tmpbuf
[SASL_BUFFER_SIZE
];
923 * If we have some leftover data, return that
932 * Otherwise, fill our buffer until we have some data to return.
935 while (retbufsize
== 0) {
937 cc
= read(fileno(f
), tmpbuf
, sizeof(tmpbuf
));
943 snprintf(response
, sizeof(response
), "Error during read from "
944 "network: %s", strerror(errno
));
949 * We're not allowed to call sasl_decode until sasl_complete is
950 * true, so we do these gyrations ...
953 if (!sasl_complete
) {
960 result
= sasl_decode(conn
, tmpbuf
, cc
,
961 (const char **) &retbuf
, &retbufsize
);
963 if (result
!= SASL_OK
) {
964 snprintf(response
, sizeof(response
), "Error during SASL "
965 "decoding: %s", sasl_errdetail(conn
));
971 if (retbufsize
> size
) {
972 buffer
= mh_xrealloc(buffer
, retbufsize
);
976 memcpy(buffer
, retbuf
, retbufsize
);
978 cnt
= retbufsize
- 1;
980 return (int) buffer
[0];
982 #endif /* CYRUS_SASL */