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
))
476 pop_init (char *host
, char *port
, char *user
, char *pass
, char *proxy
,
477 int snoop
, int sasl
, char *mech
)
484 #endif /* ! CYRUS_SASL */
486 if (proxy
&& *proxy
) {
488 int inpipe
[2]; /* for reading from the server */
489 int outpipe
[2]; /* for sending to the server */
491 if (pipe(inpipe
) < 0) {
492 adios ("inpipe", "pipe");
494 if (pipe(outpipe
) < 0) {
495 adios ("outpipe", "pipe");
505 dup2(outpipe
[0],0); /* connect read end of connection */
506 dup2(inpipe
[1], 1); /* connect write end of connection */
507 if(inpipe
[0]>1) close(inpipe
[0]);
508 if(inpipe
[1]>1) close(inpipe
[1]);
509 if(outpipe
[0]>1) close(outpipe
[0]);
510 if(outpipe
[1]>1) close(outpipe
[1]);
512 /* run the proxy command */
513 argv
=parse_proxy(proxy
, host
);
514 execvp(argv
[0],argv
);
524 /* okay in the parent we do some stuff */
525 close(inpipe
[1]); /* child uses this */
526 close(outpipe
[0]); /* child uses this */
530 /* and write on fd2 */
535 if ((fd1
= client (host
, port
? port
: "pop3", response
,
536 sizeof(response
), snoop
)) == NOTOK
) {
540 if ((fd2
= dup (fd1
)) == NOTOK
) {
543 if ((s
= strerror(errno
)))
544 snprintf (response
, sizeof(response
),
545 "unable to dup connection descriptor: %s", s
);
547 snprintf (response
, sizeof(response
),
548 "unable to dup connection descriptor: unknown error");
553 if (pop_set (fd1
, fd2
, snoop
) == NOTOK
)
556 SIGNAL (SIGPIPE
, SIG_IGN
);
558 switch (sasl_getline (response
, sizeof response
, input
)) {
561 fprintf (stderr
, "<--- %s\n", response
);
562 if (*response
== '+') {
565 if (pop_auth_sasl(user
, host
, mech
) != NOTOK
)
568 # endif /* CYRUS_SASL */
569 if (command ("USER %s", user
) != NOTOK
570 && command ("%s %s", (pophack
++, "PASS"),
574 strncpy (buffer
, response
, sizeof(buffer
));
576 strncpy (response
, buffer
, sizeof(response
));
582 fprintf (stderr
, "%s\n", response
);
588 return NOTOK
; /* NOTREACHED */
592 pop_set (int in
, int out
, int snoop
)
595 if ((input
= fdopen (in
, "r")) == NULL
596 || (output
= fdopen (out
, "w")) == NULL
) {
597 strncpy (response
, "fdopen failed on connection descriptor", sizeof(response
));
613 pop_fd (char *in
, int inlen
, char *out
, int outlen
)
615 snprintf (in
, inlen
, "%d", fileno (input
));
616 snprintf (out
, outlen
, "%d", fileno (output
));
622 * Find out number of messages available
623 * and their total size.
627 pop_stat (int *nmsgs
, int *nbytes
)
630 if (command ("STAT") == NOTOK
)
633 *nmsgs
= *nbytes
= 0;
634 sscanf (response
, "+OK %d %d", nmsgs
, nbytes
);
641 pop_list (int msgno
, int *nmsgs
, int *msgs
, int *bytes
)
647 if (command ("LIST %d", msgno
) == NOTOK
)
652 sscanf (response
, "+OK %d %d %d", msgs
, bytes
, ids
);
655 sscanf (response
, "+OK %d %d", msgs
, bytes
);
659 if (command ("LIST") == NOTOK
)
662 for (i
= 0; i
< *nmsgs
; i
++)
663 switch (multiline ()) {
673 sscanf (response
, "%d %d %d",
674 msgs
++, bytes
++, ids
++);
677 sscanf (response
, "%d %d", msgs
++, bytes
++);
681 switch (multiline ()) {
693 pop_retr (int msgno
, int (*action
)(char *))
695 return traverse (action
, "RETR %d", msgno
);
700 traverse (int (*action
)(char *), const char *fmt
, ...)
704 char buffer
[sizeof(response
)];
707 result
= vcommand (fmt
, ap
);
712 strncpy (buffer
, response
, sizeof(buffer
));
715 switch (multiline ()) {
720 strncpy (response
, buffer
, sizeof(response
));
724 (*action
) (response
);
733 return command ("DELE %d", msgno
);
740 return command ("NOOP");
747 return command ("RSET");
752 pop_top (int msgno
, int lines
, int (*action
)(char *))
754 return traverse (action
, "TOP %d %d", msgno
, lines
);
763 i
= command ("QUIT");
776 #endif /* CYRUS_SASL */
785 command(const char *fmt
, ...)
791 result
= vcommand(fmt
, ap
);
799 vcommand (const char *fmt
, va_list ap
)
801 char *cp
, buffer
[65536];
803 vsnprintf (buffer
, sizeof(buffer
), fmt
, ap
);
808 fprintf(stderr
, "(encrypted) ");
809 #endif /* CYRUS_SASL */
811 if ((cp
= strchr (buffer
, ' ')))
813 fprintf (stderr
, "---> %s ********\n", buffer
);
819 fprintf (stderr
, "---> %s\n", buffer
);
822 if (putline (buffer
, output
) == NOTOK
)
826 if (poprint
&& sasl_ssf
)
827 fprintf(stderr
, "(decrypted) ");
828 #endif /* CYRUS_SASL */
830 switch (sasl_getline (response
, sizeof response
, input
)) {
833 fprintf (stderr
, "<--- %s\n", response
);
834 return (*response
== '+' ? OK
: NOTOK
);
839 fprintf (stderr
, "%s\n", response
);
843 return NOTOK
; /* NOTREACHED */
850 char buffer
[BUFSIZ
+ TRMLEN
];
852 if (sasl_getline (buffer
, sizeof buffer
, input
) != OK
)
858 fprintf(stderr
, "(decrypted) ");
859 #endif /* CYRUS_SASL */
860 fprintf (stderr
, "<--- %s\n", response
);
863 if (strncmp (buffer
, TRM
, TRMLEN
) == 0) {
864 if (buffer
[TRMLEN
] == 0)
867 strncpy (response
, buffer
+ TRMLEN
, sizeof(response
));
870 strncpy (response
, buffer
, sizeof(response
));
876 * Note that these functions have been modified to deal with layer encryption
881 sasl_getline (char *s
, int n
, FILE *iop
)
887 while (--n
> 0 && (c
= sasl_fgetc (iop
)) != EOF
&& c
!= -2)
888 if ((*p
++ = c
) == '\n')
892 if (ferror (iop
) && c
!= EOF
) {
893 strncpy (response
, "error on connection", sizeof(response
));
896 if (c
== EOF
&& p
== s
) {
897 strncpy (response
, "connection closed by foreign host", sizeof(response
));
903 if (p
> s
&& *--p
== '\r')
911 putline (char *s
, FILE *iop
)
914 char outbuf
[BUFSIZ
], *buf
;
918 if (!sasl_complete
) {
919 #endif /* CYRUS_SASL */
920 fprintf (iop
, "%s\r\n", s
);
924 * Build an output buffer, encrypt it using sasl_encode, and
925 * squirt out the results.
927 strncpy(outbuf
, s
, sizeof(outbuf
) - 3);
928 outbuf
[sizeof(outbuf
) - 3] = '\0'; /* Just in case */
929 strcat(outbuf
, "\r\n");
931 result
= sasl_encode(conn
, outbuf
, strlen(outbuf
),
932 (const char **) &buf
, &buflen
);
934 if (result
!= SASL_OK
) {
935 snprintf(response
, sizeof(response
), "SASL encoding error: %s",
936 sasl_errdetail(conn
));
940 if (fwrite(buf
, buflen
, 1, iop
) < 1) {
941 advise ("putline", "fwrite");
944 #endif /* CYRUS_SASL */
948 strncpy (response
, "lost connection", sizeof(response
));
957 * Okay, our little fgetc replacement. Hopefully this is a little more
958 * efficient than the last one.
963 static unsigned char *buffer
= NULL
, *ptr
;
964 static unsigned int size
= 0;
966 unsigned int retbufsize
= 0;
968 char *retbuf
, tmpbuf
[SASL_BUFFER_SIZE
];
971 * If we have some leftover data, return that
980 * Otherwise, fill our buffer until we have some data to return.
983 while (retbufsize
== 0) {
985 cc
= read(fileno(f
), tmpbuf
, sizeof(tmpbuf
));
991 snprintf(response
, sizeof(response
), "Error during read from "
992 "network: %s", strerror(errno
));
997 * We're not allowed to call sasl_decode until sasl_complete is
998 * true, so we do these gyrations ...
1001 if (!sasl_complete
) {
1008 result
= sasl_decode(conn
, tmpbuf
, cc
,
1009 (const char **) &retbuf
, &retbufsize
);
1011 if (result
!= SASL_OK
) {
1012 snprintf(response
, sizeof(response
), "Error during SASL "
1013 "decoding: %s", sasl_errdetail(conn
));
1019 if (retbufsize
> size
) {
1020 buffer
= mh_xrealloc(buffer
, retbufsize
);
1024 memcpy(buffer
, retbuf
, retbufsize
);
1026 cnt
= retbufsize
- 1;
1028 return (int) buffer
[0];
1030 #endif /* CYRUS_SASL */