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>
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 *, 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 *);
86 * This function implements the AUTH command for various SASL mechanisms
88 * We do the whole SASL dialog here. If this completes, then we've
89 * authenticated successfully and have (possibly) negotiated a security
94 pop_auth_sasl(char *user
, char *password
, char *host
, char *mech
)
96 int result
, status
, sasl_capability
= 0;
97 unsigned int buflen
, outlen
;
98 char server_mechs
[256], *buf
, outbuf
[BUFSIZ
];
99 const char *chosen_mech
;
100 sasl_security_properties_t secprops
;
101 struct pass_context p_context
;
106 * First off, we're going to send the CAPA command to see if we can
107 * even support the AUTH command, and if we do, then we'll get a
108 * list of mechanisms the server supports. If we don't support
109 * the CAPA command, then it's unlikely that we will support
113 if (command("CAPA") == NOTOK
) {
114 snprintf(response
, sizeof(response
),
115 "The POP CAPA command failed; POP server does not "
120 while ((status
= multiline()) != DONE
)
125 case DONE
: /* Shouldn't be possible, but just in case */
128 if (strncasecmp(response
, "SASL ", 5) == 0) {
130 * We've seen the SASL capability. Grab the mech list
133 strncpy(server_mechs
, response
+ 5, sizeof(server_mechs
));
138 if (!sasl_capability
) {
139 snprintf(response
, sizeof(response
), "POP server does not support "
145 * If we received a preferred mechanism, see if the server supports it.
148 if (mech
&& stringdex(mech
, server_mechs
) == -1) {
149 snprintf(response
, sizeof(response
), "Requested SASL mech \"%s\" is "
150 "not in list of supported mechanisms:\n%s",
156 * Start the SASL process. First off, initialize the SASL library.
159 callbacks
[POP_SASL_CB_N_USER
].context
= user
;
160 p_context
.user
= user
;
161 p_context
.host
= host
;
162 p_context
.password
= password
;
163 callbacks
[POP_SASL_CB_N_PASS
].context
= &p_context
;
165 result
= sasl_client_init(callbacks
);
167 if (result
!= SASL_OK
) {
168 snprintf(response
, sizeof(response
), "SASL library initialization "
169 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
173 result
= sasl_client_new("pop", host
, NULL
, NULL
, NULL
, 0, &conn
);
175 if (result
!= SASL_OK
) {
176 snprintf(response
, sizeof(response
), "SASL client initialization "
177 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
182 * Initialize the security properties
185 memset(&secprops
, 0, sizeof(secprops
));
186 secprops
.maxbufsize
= SASL_BUFFER_SIZE
;
187 secprops
.max_ssf
= UINT_MAX
;
189 result
= sasl_setprop(conn
, SASL_SEC_PROPS
, &secprops
);
191 if (result
!= SASL_OK
) {
192 snprintf(response
, sizeof(response
), "SASL security property "
193 "initialization failed: %s", sasl_errdetail(conn
));
198 * Start the actual protocol. Feed the mech list into the library
199 * and get out a possible initial challenge
202 result
= sasl_client_start(conn
,
203 (const char *) (mech
? mech
: server_mechs
),
204 NULL
, (const char **) &buf
,
205 &buflen
, &chosen_mech
);
207 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
208 snprintf(response
, sizeof(response
), "SASL client start failed: %s",
209 sasl_errdetail(conn
));
214 status
= sasl_encode64(buf
, buflen
, outbuf
, sizeof(outbuf
), NULL
);
215 if (status
!= SASL_OK
) {
216 snprintf(response
, sizeof(response
), "SASL base64 encode "
217 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
221 status
= command("AUTH %s %s", chosen_mech
, outbuf
);
223 status
= command("AUTH %s", chosen_mech
);
225 while (result
== SASL_CONTINUE
) {
230 * If we get a "+OK" prefix to our response, then we should
231 * exit out of this exchange now (because authenticated should
235 if (strncmp(response
, "+OK", 3) == 0)
239 * Otherwise, make sure the server challenge is correctly formatted
242 if (strncmp(response
, "+ ", 2) != 0) {
244 snprintf(response
, sizeof(response
),
245 "Malformed authentication message from server");
249 result
= sasl_decode64(response
+ 2, strlen(response
+ 2),
250 outbuf
, sizeof(outbuf
), &outlen
);
252 if (result
!= SASL_OK
) {
254 snprintf(response
, sizeof(response
), "SASL base64 decode "
255 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
259 result
= sasl_client_step(conn
, outbuf
, outlen
, NULL
,
260 (const char **) &buf
, &buflen
);
262 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
264 snprintf(response
, sizeof(response
), "SASL client negotiaton "
265 "failed: %s", sasl_errdetail(conn
));
269 status
= sasl_encode64(buf
, buflen
, outbuf
, sizeof(outbuf
), NULL
);
271 if (status
!= SASL_OK
) {
273 snprintf(response
, sizeof(response
), "SASL base64 encode "
274 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
278 status
= command(outbuf
);
282 * If we didn't get a positive final response, then error out
283 * (that probably means we failed an authorization check).
290 * We _should_ be okay now. Get a few properties now that negotiation
294 result
= sasl_getprop(conn
, SASL_MAXOUTBUF
, (const void **) &moutbuf
);
296 if (result
!= SASL_OK
) {
297 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
298 "output buffer size: %s", sasl_errdetail(conn
));
302 maxoutbuf
= *moutbuf
;
304 result
= sasl_getprop(conn
, SASL_SSF
, (const void **) &ssf
);
308 if (result
!= SASL_OK
) {
309 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
310 "security strength factor: %s", sasl_errdetail(conn
));
315 * Limit this to what we can deal with. Shouldn't matter much because
316 * this is only outgoing data (which should be small)
319 if (maxoutbuf
== 0 || maxoutbuf
> BUFSIZ
)
328 * Callback to return the userid sent down via the user parameter
332 sasl_get_user(void *context
, int id
, const char **result
, unsigned *len
)
334 char *user
= (char *) context
;
336 if (! result
|| id
!= SASL_CB_USER
)
337 return SASL_BADPARAM
;
347 * Callback to return the password (we call ruserpass, which can get it
352 sasl_get_pass(sasl_conn_t
*conn
, void *context
, int id
, sasl_secret_t
**psecret
)
354 struct pass_context
*p_context
= (struct pass_context
*) context
;
355 char *pass
= p_context
->password
;
360 if (! psecret
|| id
!= SASL_CB_PASS
)
361 return SASL_BADPARAM
;
365 *psecret
= (sasl_secret_t
*) mh_xmalloc(sizeof(sasl_secret_t
) + len
);
367 (*psecret
)->len
= len
;
368 strcpy((char *) (*psecret
)->data
, pass
);
372 #endif /* CYRUS_SASL */
376 * Split string containing proxy command into an array of arguments
377 * suitable for passing to exec. Returned array must be freed. Shouldn't
378 * be possible to call this with host set to NULL.
381 parse_proxy(char *proxy
, char *host
)
385 int hlen
= strlen(host
);
387 unsigned char *cur
, *pro
;
390 /* skip any initial space */
391 for (pro
= (unsigned char *) proxy
; isspace(*pro
); pro
++)
394 /* calculate required size for argument array */
395 for (cur
= pro
; *cur
; cur
++) {
396 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1]))
398 else if (*cur
== '%' && cur
[1] == 'h') {
401 } else if (!isspace(*cur
))
405 /* put together list of arguments */
406 p
= pargv
= mh_xmalloc(pargc
* sizeof(char *));
407 c
= *pargv
= mh_xmalloc(plen
* sizeof(char));
408 for (cur
= pro
; *cur
; cur
++) {
409 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1])) {
412 } else if (*cur
== '%' && cur
[1] == 'h') {
416 } else if (!isspace(*cur
))
424 pop_init (char *host
, char *port
, char *user
, char *pass
, char *proxy
,
425 int snoop
, int sasl
, char *mech
)
432 #endif /* ! CYRUS_SASL */
434 if (proxy
&& *proxy
) {
436 int inpipe
[2]; /* for reading from the server */
437 int outpipe
[2]; /* for sending to the server */
439 /* first give up any root priviledges we may have for rpop */
452 dup2(outpipe
[0],0); /* connect read end of connection */
453 dup2(inpipe
[1], 1); /* connect write end of connection */
454 if(inpipe
[0]>1) close(inpipe
[0]);
455 if(inpipe
[1]>1) close(inpipe
[1]);
456 if(outpipe
[0]>1) close(outpipe
[0]);
457 if(outpipe
[1]>1) close(outpipe
[1]);
459 /* run the proxy command */
460 argv
=parse_proxy(proxy
, host
);
461 execvp(argv
[0],argv
);
471 /* okay in the parent we do some stuff */
472 close(inpipe
[1]); /* child uses this */
473 close(outpipe
[0]); /* child uses this */
477 /* and write on fd2 */
482 if ((fd1
= client (host
, port
? port
: "pop3", response
,
483 sizeof(response
), snoop
)) == NOTOK
) {
487 if ((fd2
= dup (fd1
)) == NOTOK
) {
490 if ((s
= strerror(errno
)))
491 snprintf (response
, sizeof(response
),
492 "unable to dup connection descriptor: %s", s
);
494 snprintf (response
, sizeof(response
),
495 "unable to dup connection descriptor: unknown error");
500 if (pop_set (fd1
, fd2
, snoop
) == NOTOK
)
503 SIGNAL (SIGPIPE
, SIG_IGN
);
505 switch (sasl_getline (response
, sizeof response
, input
)) {
508 fprintf (stderr
, "<--- %s\n", response
);
509 if (*response
== '+') {
512 if (pop_auth_sasl(user
, pass
, host
, mech
) != NOTOK
)
515 # endif /* CYRUS_SASL */
516 if (command ("USER %s", user
) != NOTOK
517 && command ("%s %s", (pophack
++, "PASS"),
521 strncpy (buffer
, response
, sizeof(buffer
));
523 strncpy (response
, buffer
, sizeof(response
));
529 fprintf (stderr
, "%s\n", response
);
535 return NOTOK
; /* NOTREACHED */
539 pop_set (int in
, int out
, int snoop
)
542 if ((input
= fdopen (in
, "r")) == NULL
543 || (output
= fdopen (out
, "w")) == NULL
) {
544 strncpy (response
, "fdopen failed on connection descriptor", sizeof(response
));
560 pop_fd (char *in
, int inlen
, char *out
, int outlen
)
562 snprintf (in
, inlen
, "%d", fileno (input
));
563 snprintf (out
, outlen
, "%d", fileno (output
));
569 * Find out number of messages available
570 * and their total size.
574 pop_stat (int *nmsgs
, int *nbytes
)
577 if (command ("STAT") == NOTOK
)
580 *nmsgs
= *nbytes
= 0;
581 sscanf (response
, "+OK %d %d", nmsgs
, nbytes
);
588 pop_list (int msgno
, int *nmsgs
, int *msgs
, int *bytes
)
594 if (command ("LIST %d", msgno
) == NOTOK
)
599 sscanf (response
, "+OK %d %d %d", msgs
, bytes
, ids
);
602 sscanf (response
, "+OK %d %d", msgs
, bytes
);
606 if (command ("LIST") == NOTOK
)
609 for (i
= 0; i
< *nmsgs
; i
++)
610 switch (multiline ()) {
620 sscanf (response
, "%d %d %d",
621 msgs
++, bytes
++, ids
++);
624 sscanf (response
, "%d %d", msgs
++, bytes
++);
628 switch (multiline ()) {
640 pop_retr (int msgno
, int (*action
)(char *))
642 return traverse (action
, "RETR %d", msgno
);
647 traverse (int (*action
)(char *), const char *fmt
, ...)
651 char buffer
[sizeof(response
)];
654 result
= vcommand (fmt
, ap
);
659 strncpy (buffer
, response
, sizeof(buffer
));
662 switch (multiline ()) {
667 strncpy (response
, buffer
, sizeof(response
));
671 (*action
) (response
);
680 return command ("DELE %d", msgno
);
687 return command ("NOOP");
694 return command ("RSET");
699 pop_top (int msgno
, int lines
, int (*action
)(char *))
701 return traverse (action
, "TOP %d %d", msgno
, lines
);
710 i
= command ("QUIT");
723 #endif /* CYRUS_SASL */
732 command(const char *fmt
, ...)
738 result
= vcommand(fmt
, ap
);
746 vcommand (const char *fmt
, va_list ap
)
748 char *cp
, buffer
[BUFSIZ
];
750 vsnprintf (buffer
, sizeof(buffer
), fmt
, ap
);
754 fprintf(stderr
, "(encrypted) ");
755 #endif /* CYRUS_SASL */
757 if ((cp
= strchr (buffer
, ' ')))
759 fprintf (stderr
, "---> %s ********\n", buffer
);
765 fprintf (stderr
, "---> %s\n", buffer
);
768 if (putline (buffer
, output
) == NOTOK
)
772 if (poprint
&& sasl_ssf
)
773 fprintf(stderr
, "(decrypted) ");
774 #endif /* CYRUS_SASL */
776 switch (sasl_getline (response
, sizeof response
, input
)) {
779 fprintf (stderr
, "<--- %s\n", response
);
780 return (*response
== '+' ? OK
: NOTOK
);
785 fprintf (stderr
, "%s\n", response
);
789 return NOTOK
; /* NOTREACHED */
796 char buffer
[BUFSIZ
+ TRMLEN
];
798 if (sasl_getline (buffer
, sizeof buffer
, input
) != OK
)
804 fprintf(stderr
, "(decrypted) ");
805 #endif /* CYRUS_SASL */
806 fprintf (stderr
, "<--- %s\n", response
);
809 if (strncmp (buffer
, TRM
, TRMLEN
) == 0) {
810 if (buffer
[TRMLEN
] == 0)
813 strncpy (response
, buffer
+ TRMLEN
, sizeof(response
));
816 strncpy (response
, buffer
, sizeof(response
));
822 * Note that these functions have been modified to deal with layer encryption
827 sasl_getline (char *s
, int n
, FILE *iop
)
833 while (--n
> 0 && (c
= sasl_fgetc (iop
)) != EOF
&& c
!= -2)
834 if ((*p
++ = c
) == '\n')
838 if (ferror (iop
) && c
!= EOF
) {
839 strncpy (response
, "error on connection", sizeof(response
));
842 if (c
== EOF
&& p
== s
) {
843 strncpy (response
, "connection closed by foreign host", sizeof(response
));
857 putline (char *s
, FILE *iop
)
860 char outbuf
[BUFSIZ
], *buf
;
864 if (!sasl_complete
) {
865 #endif /* CYRUS_SASL */
866 fprintf (iop
, "%s\r\n", s
);
870 * Build an output buffer, encrypt it using sasl_encode, and
871 * squirt out the results.
873 strncpy(outbuf
, s
, sizeof(outbuf
) - 3);
874 outbuf
[sizeof(outbuf
) - 3] = '\0'; /* Just in case */
875 strcat(outbuf
, "\r\n");
877 result
= sasl_encode(conn
, outbuf
, strlen(outbuf
),
878 (const char **) &buf
, &buflen
);
880 if (result
!= SASL_OK
) {
881 snprintf(response
, sizeof(response
), "SASL encoding error: %s",
882 sasl_errdetail(conn
));
886 fwrite(buf
, buflen
, 1, iop
);
888 #endif /* CYRUS_SASL */
892 strncpy (response
, "lost connection", sizeof(response
));
901 * Okay, our little fgetc replacement. Hopefully this is a little more
902 * efficient than the last one.
907 static unsigned char *buffer
= NULL
, *ptr
;
908 static unsigned int size
= 0;
910 unsigned int retbufsize
= 0;
912 char *retbuf
, tmpbuf
[SASL_BUFFER_SIZE
];
915 * If we have some leftover data, return that
924 * Otherwise, fill our buffer until we have some data to return.
927 while (retbufsize
== 0) {
929 cc
= read(fileno(f
), tmpbuf
, sizeof(tmpbuf
));
935 snprintf(response
, sizeof(response
), "Error during read from "
936 "network: %s", strerror(errno
));
941 * We're not allowed to call sasl_decode until sasl_complete is
942 * true, so we do these gyrations ...
945 if (!sasl_complete
) {
952 result
= sasl_decode(conn
, tmpbuf
, cc
,
953 (const char **) &retbuf
, &retbufsize
);
955 if (result
!= SASL_OK
) {
956 snprintf(response
, sizeof(response
), "Error during SASL "
957 "decoding: %s", sasl_errdetail(conn
));
963 if (retbufsize
> size
) {
964 buffer
= mh_xrealloc(buffer
, retbufsize
);
968 memcpy(buffer
, retbuf
, retbufsize
);
970 cnt
= retbufsize
- 1;
972 return (int) buffer
[0];
974 #endif /* CYRUS_SASL */