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 */
448 dup2(outpipe
[0],0); /* connect read end of connection */
449 dup2(inpipe
[1], 1); /* connect write end of connection */
450 if(inpipe
[0]>1) close(inpipe
[0]);
451 if(inpipe
[1]>1) close(inpipe
[1]);
452 if(outpipe
[0]>1) close(outpipe
[0]);
453 if(outpipe
[1]>1) close(outpipe
[1]);
455 /* run the proxy command */
456 argv
=parse_proxy(proxy
, host
);
457 execvp(argv
[0],argv
);
467 /* okay in the parent we do some stuff */
468 close(inpipe
[1]); /* child uses this */
469 close(outpipe
[0]); /* child uses this */
473 /* and write on fd2 */
478 if ((fd1
= client (host
, port
? port
: "pop3", response
,
479 sizeof(response
), snoop
)) == NOTOK
) {
483 if ((fd2
= dup (fd1
)) == NOTOK
) {
486 if ((s
= strerror(errno
)))
487 snprintf (response
, sizeof(response
),
488 "unable to dup connection descriptor: %s", s
);
490 snprintf (response
, sizeof(response
),
491 "unable to dup connection descriptor: unknown error");
496 if (pop_set (fd1
, fd2
, snoop
) == NOTOK
)
499 SIGNAL (SIGPIPE
, SIG_IGN
);
501 switch (sasl_getline (response
, sizeof response
, input
)) {
504 fprintf (stderr
, "<--- %s\n", response
);
505 if (*response
== '+') {
508 if (pop_auth_sasl(user
, pass
, host
, mech
) != NOTOK
)
511 # endif /* CYRUS_SASL */
512 if (command ("USER %s", user
) != NOTOK
513 && command ("%s %s", (pophack
++, "PASS"),
517 strncpy (buffer
, response
, sizeof(buffer
));
519 strncpy (response
, buffer
, sizeof(response
));
525 fprintf (stderr
, "%s\n", response
);
531 return NOTOK
; /* NOTREACHED */
535 pop_set (int in
, int out
, int snoop
)
538 if ((input
= fdopen (in
, "r")) == NULL
539 || (output
= fdopen (out
, "w")) == NULL
) {
540 strncpy (response
, "fdopen failed on connection descriptor", sizeof(response
));
556 pop_fd (char *in
, int inlen
, char *out
, int outlen
)
558 snprintf (in
, inlen
, "%d", fileno (input
));
559 snprintf (out
, outlen
, "%d", fileno (output
));
565 * Find out number of messages available
566 * and their total size.
570 pop_stat (int *nmsgs
, int *nbytes
)
573 if (command ("STAT") == NOTOK
)
576 *nmsgs
= *nbytes
= 0;
577 sscanf (response
, "+OK %d %d", nmsgs
, nbytes
);
584 pop_list (int msgno
, int *nmsgs
, int *msgs
, int *bytes
)
590 if (command ("LIST %d", msgno
) == NOTOK
)
595 sscanf (response
, "+OK %d %d %d", msgs
, bytes
, ids
);
598 sscanf (response
, "+OK %d %d", msgs
, bytes
);
602 if (command ("LIST") == NOTOK
)
605 for (i
= 0; i
< *nmsgs
; i
++)
606 switch (multiline ()) {
616 sscanf (response
, "%d %d %d",
617 msgs
++, bytes
++, ids
++);
620 sscanf (response
, "%d %d", msgs
++, bytes
++);
624 switch (multiline ()) {
636 pop_retr (int msgno
, int (*action
)(char *))
638 return traverse (action
, "RETR %d", msgno
);
643 traverse (int (*action
)(char *), const char *fmt
, ...)
647 char buffer
[sizeof(response
)];
650 result
= vcommand (fmt
, ap
);
655 strncpy (buffer
, response
, sizeof(buffer
));
658 switch (multiline ()) {
663 strncpy (response
, buffer
, sizeof(response
));
667 (*action
) (response
);
676 return command ("DELE %d", msgno
);
683 return command ("NOOP");
690 return command ("RSET");
695 pop_top (int msgno
, int lines
, int (*action
)(char *))
697 return traverse (action
, "TOP %d %d", msgno
, lines
);
706 i
= command ("QUIT");
719 #endif /* CYRUS_SASL */
728 command(const char *fmt
, ...)
734 result
= vcommand(fmt
, ap
);
742 vcommand (const char *fmt
, va_list ap
)
744 char *cp
, buffer
[BUFSIZ
];
746 vsnprintf (buffer
, sizeof(buffer
), fmt
, ap
);
750 fprintf(stderr
, "(encrypted) ");
751 #endif /* CYRUS_SASL */
753 if ((cp
= strchr (buffer
, ' ')))
755 fprintf (stderr
, "---> %s ********\n", buffer
);
761 fprintf (stderr
, "---> %s\n", buffer
);
764 if (putline (buffer
, output
) == NOTOK
)
768 if (poprint
&& sasl_ssf
)
769 fprintf(stderr
, "(decrypted) ");
770 #endif /* CYRUS_SASL */
772 switch (sasl_getline (response
, sizeof response
, input
)) {
775 fprintf (stderr
, "<--- %s\n", response
);
776 return (*response
== '+' ? OK
: NOTOK
);
781 fprintf (stderr
, "%s\n", response
);
785 return NOTOK
; /* NOTREACHED */
792 char buffer
[BUFSIZ
+ TRMLEN
];
794 if (sasl_getline (buffer
, sizeof buffer
, input
) != OK
)
800 fprintf(stderr
, "(decrypted) ");
801 #endif /* CYRUS_SASL */
802 fprintf (stderr
, "<--- %s\n", response
);
805 if (strncmp (buffer
, TRM
, TRMLEN
) == 0) {
806 if (buffer
[TRMLEN
] == 0)
809 strncpy (response
, buffer
+ TRMLEN
, sizeof(response
));
812 strncpy (response
, buffer
, sizeof(response
));
818 * Note that these functions have been modified to deal with layer encryption
823 sasl_getline (char *s
, int n
, FILE *iop
)
829 while (--n
> 0 && (c
= sasl_fgetc (iop
)) != EOF
&& c
!= -2)
830 if ((*p
++ = c
) == '\n')
834 if (ferror (iop
) && c
!= EOF
) {
835 strncpy (response
, "error on connection", sizeof(response
));
838 if (c
== EOF
&& p
== s
) {
839 strncpy (response
, "connection closed by foreign host", sizeof(response
));
853 putline (char *s
, FILE *iop
)
856 char outbuf
[BUFSIZ
], *buf
;
860 if (!sasl_complete
) {
861 #endif /* CYRUS_SASL */
862 fprintf (iop
, "%s\r\n", s
);
866 * Build an output buffer, encrypt it using sasl_encode, and
867 * squirt out the results.
869 strncpy(outbuf
, s
, sizeof(outbuf
) - 3);
870 outbuf
[sizeof(outbuf
) - 3] = '\0'; /* Just in case */
871 strcat(outbuf
, "\r\n");
873 result
= sasl_encode(conn
, outbuf
, strlen(outbuf
),
874 (const char **) &buf
, &buflen
);
876 if (result
!= SASL_OK
) {
877 snprintf(response
, sizeof(response
), "SASL encoding error: %s",
878 sasl_errdetail(conn
));
882 fwrite(buf
, buflen
, 1, iop
);
884 #endif /* CYRUS_SASL */
888 strncpy (response
, "lost connection", sizeof(response
));
897 * Okay, our little fgetc replacement. Hopefully this is a little more
898 * efficient than the last one.
903 static unsigned char *buffer
= NULL
, *ptr
;
904 static unsigned int size
= 0;
906 unsigned int retbufsize
= 0;
908 char *retbuf
, tmpbuf
[SASL_BUFFER_SIZE
];
911 * If we have some leftover data, return that
920 * Otherwise, fill our buffer until we have some data to return.
923 while (retbufsize
== 0) {
925 cc
= read(fileno(f
), tmpbuf
, sizeof(tmpbuf
));
931 snprintf(response
, sizeof(response
), "Error during read from "
932 "network: %s", strerror(errno
));
937 * We're not allowed to call sasl_decode until sasl_complete is
938 * true, so we do these gyrations ...
941 if (!sasl_complete
) {
948 result
= sasl_decode(conn
, tmpbuf
, cc
,
949 (const char **) &retbuf
, &retbufsize
);
951 if (result
!= SASL_OK
) {
952 snprintf(response
, sizeof(response
), "Error during SASL "
953 "decoding: %s", sasl_errdetail(conn
));
959 if (retbufsize
> size
) {
960 buffer
= mh_xrealloc(buffer
, retbufsize
);
964 memcpy(buffer
, retbuf
, retbufsize
);
966 cnt
= retbufsize
- 1;
968 return (int) buffer
[0];
970 #endif /* CYRUS_SASL */