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 *, 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 *password
, 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 p_context
.password
= password
;
162 callbacks
[POP_SASL_CB_N_PASS
].context
= &p_context
;
164 result
= sasl_client_init(callbacks
);
166 if (result
!= SASL_OK
) {
167 snprintf(response
, sizeof(response
), "SASL library initialization "
168 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
172 result
= sasl_client_new("pop", host
, NULL
, NULL
, NULL
, 0, &conn
);
174 if (result
!= SASL_OK
) {
175 snprintf(response
, sizeof(response
), "SASL client initialization "
176 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
181 * Initialize the security properties
184 memset(&secprops
, 0, sizeof(secprops
));
185 secprops
.maxbufsize
= SASL_BUFFER_SIZE
;
186 secprops
.max_ssf
= UINT_MAX
;
188 result
= sasl_setprop(conn
, SASL_SEC_PROPS
, &secprops
);
190 if (result
!= SASL_OK
) {
191 snprintf(response
, sizeof(response
), "SASL security property "
192 "initialization failed: %s", sasl_errdetail(conn
));
197 * Start the actual protocol. Feed the mech list into the library
198 * and get out a possible initial challenge
201 result
= sasl_client_start(conn
,
202 (const char *) (mech
? mech
: server_mechs
),
203 NULL
, (const char **) &buf
,
204 &buflen
, &chosen_mech
);
206 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
207 snprintf(response
, sizeof(response
), "SASL client start failed: %s",
208 sasl_errdetail(conn
));
213 status
= sasl_encode64(buf
, buflen
, outbuf
, sizeof(outbuf
), NULL
);
214 if (status
!= SASL_OK
) {
215 snprintf(response
, sizeof(response
), "SASL base64 encode "
216 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
220 status
= command("AUTH %s %s", chosen_mech
, outbuf
);
222 status
= command("AUTH %s", chosen_mech
);
224 while (result
== SASL_CONTINUE
) {
229 * If we get a "+OK" prefix to our response, then we should
230 * exit out of this exchange now (because authenticated should
234 if (strncmp(response
, "+OK", 3) == 0)
238 * Otherwise, make sure the server challenge is correctly formatted
241 if (strncmp(response
, "+ ", 2) != 0) {
243 snprintf(response
, sizeof(response
),
244 "Malformed authentication message from server");
248 result
= sasl_decode64(response
+ 2, strlen(response
+ 2),
249 outbuf
, sizeof(outbuf
), &outlen
);
251 if (result
!= SASL_OK
) {
253 snprintf(response
, sizeof(response
), "SASL base64 decode "
254 "failed: %s", sasl_errstring(result
, NULL
, NULL
));
258 result
= sasl_client_step(conn
, outbuf
, outlen
, NULL
,
259 (const char **) &buf
, &buflen
);
261 if (result
!= SASL_OK
&& result
!= SASL_CONTINUE
) {
263 snprintf(response
, sizeof(response
), "SASL client negotiaton "
264 "failed: %s", sasl_errdetail(conn
));
268 status
= sasl_encode64(buf
, buflen
, outbuf
, sizeof(outbuf
), NULL
);
270 if (status
!= SASL_OK
) {
272 snprintf(response
, sizeof(response
), "SASL base64 encode "
273 "failed: %s", sasl_errstring(status
, NULL
, NULL
));
277 status
= command(outbuf
);
281 * If we didn't get a positive final response, then error out
282 * (that probably means we failed an authorization check).
289 * We _should_ be okay now. Get a few properties now that negotiation
293 result
= sasl_getprop(conn
, SASL_MAXOUTBUF
, (const void **) &moutbuf
);
295 if (result
!= SASL_OK
) {
296 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
297 "output buffer size: %s", sasl_errdetail(conn
));
301 maxoutbuf
= *moutbuf
;
303 result
= sasl_getprop(conn
, SASL_SSF
, (const void **) &ssf
);
307 if (result
!= SASL_OK
) {
308 snprintf(response
, sizeof(response
), "Cannot retrieve SASL negotiated "
309 "security strength factor: %s", sasl_errdetail(conn
));
314 * Limit this to what we can deal with. Shouldn't matter much because
315 * this is only outgoing data (which should be small)
318 if (maxoutbuf
== 0 || maxoutbuf
> BUFSIZ
)
327 * Callback to return the userid sent down via the user parameter
331 sasl_get_user(void *context
, int id
, const char **result
, unsigned *len
)
333 char *user
= (char *) context
;
335 if (! result
|| id
!= SASL_CB_USER
)
336 return SASL_BADPARAM
;
346 * Callback to return the password (we call ruserpass, which can get it
351 sasl_get_pass(sasl_conn_t
*conn
, void *context
, int id
, sasl_secret_t
**psecret
)
353 struct pass_context
*p_context
= (struct pass_context
*) context
;
354 char *pass
= p_context
->password
;
359 if (! psecret
|| id
!= SASL_CB_PASS
)
360 return SASL_BADPARAM
;
364 *psecret
= (sasl_secret_t
*) mh_xmalloc(sizeof(sasl_secret_t
) + len
);
366 (*psecret
)->len
= len
;
367 strcpy((char *) (*psecret
)->data
, pass
);
371 #endif /* CYRUS_SASL */
375 * Split string containing proxy command into an array of arguments
376 * suitable for passing to exec. Returned array must be freed. Shouldn't
377 * be possible to call this with host set to NULL.
380 parse_proxy(char *proxy
, char *host
)
384 int hlen
= strlen(host
);
386 unsigned char *cur
, *pro
;
389 /* skip any initial space */
390 for (pro
= (unsigned char *) proxy
; isspace(*pro
); pro
++)
393 /* calculate required size for argument array */
394 for (cur
= pro
; *cur
; cur
++) {
395 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1]))
397 else if (*cur
== '%' && cur
[1] == 'h') {
400 } else if (!isspace(*cur
))
404 /* put together list of arguments */
405 p
= pargv
= mh_xmalloc(pargc
* sizeof(char *));
406 c
= *pargv
= mh_xmalloc(plen
* sizeof(char));
407 for (cur
= pro
; *cur
; cur
++) {
408 if (isspace(*cur
) && cur
[1] && !isspace(cur
[1])) {
411 } else if (*cur
== '%' && cur
[1] == 'h') {
415 } else if (!isspace(*cur
))
423 pop_init (char *host
, char *port
, char *user
, char *pass
, char *proxy
,
424 int snoop
, int sasl
, char *mech
)
431 #endif /* ! CYRUS_SASL */
433 if (proxy
&& *proxy
) {
435 int inpipe
[2]; /* for reading from the server */
436 int outpipe
[2]; /* for sending to the server */
438 /* first give up any root priviledges we may have for rpop */
451 dup2(outpipe
[0],0); /* connect read end of connection */
452 dup2(inpipe
[1], 1); /* connect write end of connection */
453 if(inpipe
[0]>1) close(inpipe
[0]);
454 if(inpipe
[1]>1) close(inpipe
[1]);
455 if(outpipe
[0]>1) close(outpipe
[0]);
456 if(outpipe
[1]>1) close(outpipe
[1]);
458 /* run the proxy command */
459 argv
=parse_proxy(proxy
, host
);
460 execvp(argv
[0],argv
);
470 /* okay in the parent we do some stuff */
471 close(inpipe
[1]); /* child uses this */
472 close(outpipe
[0]); /* child uses this */
476 /* and write on fd2 */
481 if ((fd1
= client (host
, port
? port
: "pop3", response
,
482 sizeof(response
), snoop
)) == NOTOK
) {
486 if ((fd2
= dup (fd1
)) == NOTOK
) {
489 if ((s
= strerror(errno
)))
490 snprintf (response
, sizeof(response
),
491 "unable to dup connection descriptor: %s", s
);
493 snprintf (response
, sizeof(response
),
494 "unable to dup connection descriptor: unknown error");
499 if (pop_set (fd1
, fd2
, snoop
) == NOTOK
)
502 SIGNAL (SIGPIPE
, SIG_IGN
);
504 switch (sasl_getline (response
, sizeof response
, input
)) {
507 fprintf (stderr
, "<--- %s\n", response
);
508 if (*response
== '+') {
511 if (pop_auth_sasl(user
, pass
, host
, mech
) != NOTOK
)
514 # endif /* CYRUS_SASL */
515 if (command ("USER %s", user
) != NOTOK
516 && command ("%s %s", (pophack
++, "PASS"),
520 strncpy (buffer
, response
, sizeof(buffer
));
522 strncpy (response
, buffer
, sizeof(response
));
528 fprintf (stderr
, "%s\n", response
);
534 return NOTOK
; /* NOTREACHED */
538 pop_set (int in
, int out
, int snoop
)
541 if ((input
= fdopen (in
, "r")) == NULL
542 || (output
= fdopen (out
, "w")) == NULL
) {
543 strncpy (response
, "fdopen failed on connection descriptor", sizeof(response
));
559 pop_fd (char *in
, int inlen
, char *out
, int outlen
)
561 snprintf (in
, inlen
, "%d", fileno (input
));
562 snprintf (out
, outlen
, "%d", fileno (output
));
568 * Find out number of messages available
569 * and their total size.
573 pop_stat (int *nmsgs
, int *nbytes
)
576 if (command ("STAT") == NOTOK
)
579 *nmsgs
= *nbytes
= 0;
580 sscanf (response
, "+OK %d %d", nmsgs
, nbytes
);
587 pop_list (int msgno
, int *nmsgs
, int *msgs
, int *bytes
)
593 if (command ("LIST %d", msgno
) == NOTOK
)
598 sscanf (response
, "+OK %d %d %d", msgs
, bytes
, ids
);
601 sscanf (response
, "+OK %d %d", msgs
, bytes
);
605 if (command ("LIST") == NOTOK
)
608 for (i
= 0; i
< *nmsgs
; i
++)
609 switch (multiline ()) {
619 sscanf (response
, "%d %d %d",
620 msgs
++, bytes
++, ids
++);
623 sscanf (response
, "%d %d", msgs
++, bytes
++);
627 switch (multiline ()) {
639 pop_retr (int msgno
, int (*action
)(char *))
641 return traverse (action
, "RETR %d", msgno
);
646 traverse (int (*action
)(char *), const char *fmt
, ...)
650 char buffer
[sizeof(response
)];
653 result
= vcommand (fmt
, ap
);
658 strncpy (buffer
, response
, sizeof(buffer
));
661 switch (multiline ()) {
666 strncpy (response
, buffer
, sizeof(response
));
670 (*action
) (response
);
679 return command ("DELE %d", msgno
);
686 return command ("NOOP");
693 return command ("RSET");
698 pop_top (int msgno
, int lines
, int (*action
)(char *))
700 return traverse (action
, "TOP %d %d", msgno
, lines
);
709 i
= command ("QUIT");
722 #endif /* CYRUS_SASL */
731 command(const char *fmt
, ...)
737 result
= vcommand(fmt
, ap
);
745 vcommand (const char *fmt
, va_list ap
)
747 char *cp
, buffer
[BUFSIZ
];
749 vsnprintf (buffer
, sizeof(buffer
), fmt
, ap
);
753 fprintf(stderr
, "(encrypted) ");
754 #endif /* CYRUS_SASL */
756 if ((cp
= strchr (buffer
, ' ')))
758 fprintf (stderr
, "---> %s ********\n", buffer
);
764 fprintf (stderr
, "---> %s\n", buffer
);
767 if (putline (buffer
, output
) == NOTOK
)
771 if (poprint
&& sasl_ssf
)
772 fprintf(stderr
, "(decrypted) ");
773 #endif /* CYRUS_SASL */
775 switch (sasl_getline (response
, sizeof response
, input
)) {
778 fprintf (stderr
, "<--- %s\n", response
);
779 return (*response
== '+' ? OK
: NOTOK
);
784 fprintf (stderr
, "%s\n", response
);
788 return NOTOK
; /* NOTREACHED */
795 char buffer
[BUFSIZ
+ TRMLEN
];
797 if (sasl_getline (buffer
, sizeof buffer
, input
) != OK
)
803 fprintf(stderr
, "(decrypted) ");
804 #endif /* CYRUS_SASL */
805 fprintf (stderr
, "<--- %s\n", response
);
808 if (strncmp (buffer
, TRM
, TRMLEN
) == 0) {
809 if (buffer
[TRMLEN
] == 0)
812 strncpy (response
, buffer
+ TRMLEN
, sizeof(response
));
815 strncpy (response
, buffer
, sizeof(response
));
821 * Note that these functions have been modified to deal with layer encryption
826 sasl_getline (char *s
, int n
, FILE *iop
)
832 while (--n
> 0 && (c
= sasl_fgetc (iop
)) != EOF
&& c
!= -2)
833 if ((*p
++ = c
) == '\n')
837 if (ferror (iop
) && c
!= EOF
) {
838 strncpy (response
, "error on connection", sizeof(response
));
841 if (c
== EOF
&& p
== s
) {
842 strncpy (response
, "connection closed by foreign host", sizeof(response
));
856 putline (char *s
, FILE *iop
)
859 char outbuf
[BUFSIZ
], *buf
;
863 if (!sasl_complete
) {
864 #endif /* CYRUS_SASL */
865 fprintf (iop
, "%s\r\n", s
);
869 * Build an output buffer, encrypt it using sasl_encode, and
870 * squirt out the results.
872 strncpy(outbuf
, s
, sizeof(outbuf
) - 3);
873 outbuf
[sizeof(outbuf
) - 3] = '\0'; /* Just in case */
874 strcat(outbuf
, "\r\n");
876 result
= sasl_encode(conn
, outbuf
, strlen(outbuf
),
877 (const char **) &buf
, &buflen
);
879 if (result
!= SASL_OK
) {
880 snprintf(response
, sizeof(response
), "SASL encoding error: %s",
881 sasl_errdetail(conn
));
885 fwrite(buf
, buflen
, 1, iop
);
887 #endif /* CYRUS_SASL */
891 strncpy (response
, "lost connection", sizeof(response
));
900 * Okay, our little fgetc replacement. Hopefully this is a little more
901 * efficient than the last one.
906 static unsigned char *buffer
= NULL
, *ptr
;
907 static unsigned int size
= 0;
909 unsigned int retbufsize
= 0;
911 char *retbuf
, tmpbuf
[SASL_BUFFER_SIZE
];
914 * If we have some leftover data, return that
923 * Otherwise, fill our buffer until we have some data to return.
926 while (retbufsize
== 0) {
928 cc
= read(fileno(f
), tmpbuf
, sizeof(tmpbuf
));
934 snprintf(response
, sizeof(response
), "Error during read from "
935 "network: %s", strerror(errno
));
940 * We're not allowed to call sasl_decode until sasl_complete is
941 * true, so we do these gyrations ...
944 if (!sasl_complete
) {
951 result
= sasl_decode(conn
, tmpbuf
, cc
,
952 (const char **) &retbuf
, &retbufsize
);
954 if (result
!= SASL_OK
) {
955 snprintf(response
, sizeof(response
), "Error during SASL "
956 "decoding: %s", sasl_errdetail(conn
));
962 if (retbufsize
> size
) {
963 buffer
= mh_xrealloc(buffer
, retbufsize
);
967 memcpy(buffer
, retbuf
, retbufsize
);
969 cnt
= retbufsize
- 1;
971 return (int) buffer
[0];
973 #endif /* CYRUS_SASL */