1 /* MODULE: auth_rimap */
4 * Copyright (c) 1998 Messaging Direct Ltd.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY MESSAGING DIRECT LTD. ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MESSAGING DIRECT LTD. OR
20 * ITS EMPLOYEES OR AGENTS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
23 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
25 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
26 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
29 * Copyright 1998, 1999 Carnegie Mellon University
33 * Permission to use, copy, modify, and distribute this software and its
34 * documentation for any purpose and without fee is hereby granted,
35 * provided that the above copyright notice appear in all copies and that
36 * both that copyright notice and this permission notice appear in
37 * supporting documentation, and that the name of Carnegie Mellon
38 * University not be used in advertising or publicity pertaining to
39 * distribution of the software without specific, written prior
42 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
43 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
44 * FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE FOR
45 * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
46 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
47 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
48 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
52 * Proxy authentication to a remote IMAP (or IMSP) server.
56 #ident "$Id: auth_rimap.c,v 1.12 2006/04/06 20:19:54 jeaton Exp $"
59 /* PUBLIC DEPENDENCIES */
60 #include "mechanisms.h"
71 #include <sys/types.h>
72 #include <sys/socket.h>
73 #include <netinet/in.h>
74 #include <arpa/inet.h>
78 #include "auth_rimap.h"
81 /* END PUBLIC DEPENDENCIES */
83 /* PRIVATE DEPENDENCIES */
84 static const char *r_host = NULL; /* remote hostname (mech_option) */
85 static struct addrinfo *ai = NULL; /* remote authentication host */
86 /* END PRIVATE DEPENDENCIES */
88 #define DEFAULT_REMOTE_SERVICE "imap" /* getservbyname() name for remote
89 service we connect to. */
90 #define TAG "saslauthd" /* IMAP command tag */
91 #define LOGIN_CMD (TAG " LOGIN ") /* IMAP login command (with tag) */
92 #define NETWORK_IO_TIMEOUT 30 /* network I/O timeout (seconds) */
93 #define RESP_LEN 1000 /* size of read response buffer */
95 /* Common failure response strings for auth_rimap() */
97 #define RESP_IERROR "NO [ALERT] saslauthd internal error"
98 #define RESP_UNAVAILABLE "NO [ALERT] The remote authentication server is currently unavailable"
99 #define RESP_UNEXPECTED "NO [ALERT] Unexpected response from remote authentication server"
101 /* FUNCTION: sig_null */
104 * Catch and ignore a signal.
107 static RETSIGTYPE /* R: OS dependent */
110 int sig /* I: signal being caught */
118 signal(SIGALRM, sig_null);
122 signal(SIGPIPE, sig_null);
126 syslog(LOG_WARNING, "auth_rimap: unexpected signal %d", sig);
131 #else /* __APPLE__ */
132 # if RETSIGTYPE == void
134 # else /* RETSIGTYPE */
136 # endif /* RETSIGTYPE */
137 #endif /* __APPLE__ */
140 /* END FUNCTION: sig_null */
142 /* FUNCTION: qstring */
145 * Quote a string for transmission over the IMAP protocol.
148 static char * /* R: the quoted string */
151 const char *s /* I: string to quote */
155 char *c; /* pointer to returned string */
156 register const char *p1; /* scratch pointers */
157 register char *p2; /* scratch pointers */
158 int len; /* length of array to malloc */
159 int num_quotes; /* number of '"' chars in string*/
161 /* see of we have to deal with any '"' characters */
164 while ((p1 = strchr(p1, '"')) != NULL) {
170 * no double-quotes to escape, so just wrap the input string
171 * in double-quotes and return it.
173 len = strlen(s) + 2 + 1;
185 * Ugh, we have to escape double quotes ...
187 len = strlen(s) + 2 + (2*num_quotes) + 1;
197 *p2++ = '\\'; /* escape the '"' */
205 /* END FUNCTION: qstring */
207 /* FUNCTION: auth_rimap_init */
210 * Validate the host and service names for the remote server.
216 void /* no parameters */
222 struct addrinfo hints;
224 char *c; /* scratch pointer */
227 if (mech_option == NULL) {
228 syslog(LOG_ERR, "rimap_init: no hostname specified");
231 r_host = mech_option;
234 /* Determine the port number to connect to.
236 * r_host has already been initialized to the hostname and optional port
237 * port name to connect to. The format of the string is:
244 c = strchr(r_host, '/'); /* look for optional service */
247 *c++ = '\0'; /* tie off hostname and point */
248 /* to service string */
250 c = DEFAULT_REMOTE_SERVICE;
255 memset(&hints, 0, sizeof(hints));
256 hints.ai_family = PF_UNSPEC;
257 hints.ai_socktype = SOCK_STREAM;
258 hints.ai_flags = AI_CANONNAME;
259 if ((err = getaddrinfo(r_host, c, &hints, &ai)) != 0) {
260 syslog(LOG_ERR, "auth_rimap_init: getaddrinfo %s/%s: %s",
261 r_host, c, gai_strerror(err));
264 /* Make sure we have AF_INET or AF_INET6 addresses. */
265 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
266 syslog(LOG_ERR, "auth_rimap_init: no IP address info for %s",
267 ai->ai_canonname ? ai->ai_canonname : r_host);
276 /* END FUNCTION: auth_rimap_init */
278 /* FUNCTION: auth_rimap */
281 * Proxy authenticate to a remote IMAP server.
283 * This mechanism takes the plaintext authenticator and password, forms
284 * them into an IMAP LOGIN command, then attempts to authenticate to
285 * a remote IMAP server using those values. If the remote authentication
286 * succeeds the credentials are considered valid.
288 * NOTE: since IMSP uses the same form of LOGIN command as IMAP does,
289 * this driver will also work with IMSP servers.
292 /* XXX This should be extended to support SASL PLAIN authentication */
294 char * /* R: Allocated response string */
297 const char *login, /* I: plaintext authenticator */
298 const char *password, /* I: plaintext password */
299 const char *service __attribute__((unused)),
300 const char *realm __attribute__((unused))
305 int s=-1; /* socket to remote auth host */
306 struct addrinfo *r; /* remote socket address info */
307 struct iovec iov[5]; /* for sending LOGIN command */
308 char *qlogin; /* pointer to "quoted" login */
309 char *qpass; /* pointer to "quoted" password */
310 char *c; /* scratch pointer */
311 int rc; /* return code scratch area */
312 char rbuf[RESP_LEN]; /* response read buffer */
313 char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
319 assert(login != NULL);
320 assert(password != NULL);
322 /*establish connection to remote */
323 for (r = ai; r; r = r->ai_next) {
324 s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
327 if (connect(s, r->ai_addr, r->ai_addrlen) >= 0)
332 niflags = (NI_NUMERICHOST | NI_NUMERICSERV);
333 #ifdef NI_WITHSCOPEID
334 if (r->ai_family == AF_INET6)
335 niflags |= NI_WITHSCOPEID;
337 if (getnameinfo(r->ai_addr, r->ai_addrlen, hbuf, sizeof(hbuf),
338 pbuf, sizeof(pbuf), niflags) != 0) {
339 strlcpy(hbuf, "unknown", sizeof(hbuf));
340 strlcpy(pbuf, "unknown", sizeof(pbuf));
343 syslog(LOG_WARNING, "auth_rimap: connect %s[%s]/%s: %m",
344 ai->ai_canonname ? ai->ai_canonname : r_host, hbuf, pbuf);
347 if (getnameinfo(ai->ai_addr, ai->ai_addrlen, NULL, 0,
348 pbuf, sizeof(pbuf), NI_NUMERICSERV) != 0)
349 strlcpy(pbuf, "unknown", sizeof(pbuf));
350 syslog(LOG_WARNING, "auth_rimap: couldn't connect to %s/%s",
351 ai->ai_canonname ? ai->ai_canonname : r_host, pbuf);
352 return strdup("NO [ALERT] Couldn't contact remote authentication server");
355 /* CLAIM: we now have a TCP connection to the remote IMAP server */
358 * Install noop signal handlers. These just reinstall the handler
359 * and return so that we take an EINTR during network I/O.
361 (void) signal(SIGALRM, sig_null);
362 (void) signal(SIGPIPE, sig_null);
364 /* read and parse the IMAP banner */
366 alarm(NETWORK_IO_TIMEOUT);
367 rc = read(s, rbuf, sizeof(rbuf));
370 syslog(LOG_WARNING, "auth_rimap: read (banner): %m");
372 return strdup("NO [ALERT] error synchronizing with remote authentication server");
374 rbuf[rc] = '\0'; /* tie off response */
375 c = strpbrk(rbuf, "\r\n");
377 *c = '\0'; /* tie off line termination */
380 if (!strncmp(rbuf, "* NO", sizeof("* NO")-1)) {
382 return strdup(RESP_UNAVAILABLE);
384 if (!strncmp(rbuf, "* BYE", sizeof("* BYE")-1)) {
386 return strdup(RESP_UNAVAILABLE);
388 if (strncmp(rbuf, "* OK", sizeof("* OK")-1)) {
390 "auth_rimap: unexpected response during initial handshake: %s",
393 return strdup(RESP_UNEXPECTED);
396 /* build the LOGIN command */
398 qlogin = qstring(login); /* quote login */
399 qpass = qstring(password); /* quote password */
400 if (qlogin == NULL) {
402 memset(qpass, 0, strlen(qpass));
406 syslog(LOG_WARNING, "auth_rimap: qstring(login) == NULL");
407 return strdup(RESP_IERROR);
410 if (qlogin != NULL) {
411 memset(qlogin, 0, strlen(qlogin));
415 syslog(LOG_WARNING, "auth_rimap: qstring(password) == NULL");
416 return strdup(RESP_IERROR);
419 iov[0].iov_base = LOGIN_CMD;
420 iov[0].iov_len = sizeof(LOGIN_CMD) - 1;
421 iov[1].iov_base = qlogin;
422 iov[1].iov_len = strlen(qlogin);
423 iov[2].iov_base = " ";
424 iov[2].iov_len = sizeof(" ") - 1;
425 iov[3].iov_base = qpass;
426 iov[3].iov_len = strlen(qpass);
427 iov[4].iov_base = "\r\n";
428 iov[4].iov_len = sizeof("\r\n") - 1;
430 if (flags & VERBOSE) {
431 syslog(LOG_DEBUG, "auth_rimap: sending %s%s %s",
432 LOGIN_CMD, qlogin, qpass);
434 alarm(NETWORK_IO_TIMEOUT);
435 rc = retry_writev(s, iov, 5);
438 syslog(LOG_WARNING, "auth_rimap: writev: %m");
439 memset(qlogin, 0, strlen(qlogin));
441 memset(qpass, 0, strlen(qlogin));
444 return strdup(RESP_IERROR);
447 /* don't need these any longer */
448 memset(qlogin, 0, strlen(qlogin));
450 memset(qpass, 0, strlen(qlogin));
453 /* read and parse the LOGIN response */
455 alarm(NETWORK_IO_TIMEOUT);
456 rc = read(s, rbuf, sizeof(rbuf));
458 (void) close(s); /* we're done with the remote */
460 syslog(LOG_WARNING, "auth_rimap: read (response): %m");
461 return strdup(RESP_IERROR);
464 rbuf[rc] = '\0'; /* tie off response */
465 c = strpbrk(rbuf, "\r\n");
467 *c = '\0'; /* tie off line termination */
470 if (!strncmp(rbuf, TAG " OK", sizeof(TAG " OK")-1)) {
471 if (flags & VERBOSE) {
472 syslog(LOG_DEBUG, "auth_rimap: [%s] %s", login, rbuf);
474 return strdup("OK remote authentication successful");
476 if (!strncmp(rbuf, TAG " NO", sizeof(TAG " NO")-1)) {
477 if (flags & VERBOSE) {
478 syslog(LOG_DEBUG, "auth_rimap: [%s] %s", login, rbuf);
480 return strdup("NO remote server rejected your credentials");
482 syslog(LOG_WARNING, "auth_rimap: unexpected response to auth request: %s",
484 return strdup(RESP_UNEXPECTED);
488 /* END FUNCTION: auth_rimap */
490 /* END MODULE: auth_rimap */