1
2/* MODULE: auth_rimap */
3
4/* COPYRIGHT
5 * Copyright (c) 1998 Messaging Direct Ltd.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY MESSAGING DIRECT LTD. ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL MESSAGING DIRECT LTD. OR
21 * ITS EMPLOYEES OR AGENTS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
24 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
26 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
27 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
28 * DAMAGE.
29 *
30 * Copyright 1998, 1999 Carnegie Mellon University
31 *
32 *                       All Rights Reserved
33 *
34 * Permission to use, copy, modify, and distribute this software and its
35 * documentation for any purpose and without fee is hereby granted,
36 * provided that the above copyright notice appear in all copies and that
37 * both that copyright notice and this permission notice appear in
38 * supporting documentation, and that the name of Carnegie Mellon
39 * University not be used in advertising or publicity pertaining to
40 * distribution of the software without specific, written prior
41 * permission.
42 *
43 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
44 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
45 * FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE FOR
46 * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
47 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
48 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
49 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
50 * END COPYRIGHT */
51
52/* SYNOPSIS
53 * Proxy authentication to a remote IMAP (or IMSP) server.
54 * END SYNOPSIS */
55
56#ifdef __GNUC__
57#ident "$Id: auth_rimap.c,v 1.14 2011/09/22 14:39:03 mel Exp $"
58#endif
59
60/* PUBLIC DEPENDENCIES */
61#include "mechanisms.h"
62
63#include <unistd.h>
64#include <stdlib.h>
65#include <assert.h>
66#include <errno.h>
67#include <string.h>
68#ifdef _AIX
69# include <strings.h>
70#endif /* _AIX */
71#include <syslog.h>
72#include <sys/types.h>
73#include <sys/socket.h>
74#include <netinet/in.h>
75#include <arpa/inet.h>
76#include <signal.h>
77#include <netdb.h>
78
79#include "auth_rimap.h"
80#include "utils.h"
81#include "globals.h"
82/* END PUBLIC DEPENDENCIES */
83
84/* PRIVATE DEPENDENCIES */
85static const char *r_host = NULL;       /* remote hostname (mech_option) */
86static struct addrinfo *ai = NULL;	/* remote authentication host    */
87/* END PRIVATE DEPENDENCIES */
88
89#define DEFAULT_REMOTE_SERVICE "imap"	/* getservbyname() name for remote
90					   service we connect to.	 */
91#define TAG "saslauthd"			/* IMAP command tag */
92#define LOGIN_CMD (TAG " LOGIN ")	/* IMAP login command (with tag) */
93#define NETWORK_IO_TIMEOUT 30		/* network I/O timeout (seconds) */
94#define RESP_LEN 1000			/* size of read response buffer  */
95
96/* Common failure response strings for auth_rimap() */
97
98#define RESP_IERROR	"NO [ALERT] saslauthd internal error"
99#define RESP_UNAVAILABLE "NO [ALERT] The remote authentication server is currently unavailable"
100#define RESP_UNEXPECTED	"NO [ALERT] Unexpected response from remote authentication server"
101
102/* FUNCTION: sig_null */
103
104/* SYNOPSIS
105 * Catch and ignore a signal.
106 * END SYNOPSIS */
107
108static RETSIGTYPE				/* R: OS dependent */
109sig_null (
110  /* PARAMETERS */
111  int sig					/* I: signal being caught */
112  /* END PARAMETERS */
113  )
114{
115
116    switch (sig) {
117
118      case SIGALRM:
119	signal(SIGALRM, sig_null);
120	break;
121
122      case SIGPIPE:
123	signal(SIGPIPE, sig_null);
124	break;
125
126      default:
127	syslog(LOG_WARNING, "auth_rimap: unexpected signal %d", sig);
128	break;
129    }
130#ifdef __APPLE__
131    return;
132#else /* __APPLE__ */
133# if RETSIGTYPE == void
134    return;
135# else /* RETSIGTYPE */
136    return 0;
137# endif /* RETSIGTYPE */
138#endif /* __APPLE__ */
139}
140
141/* END FUNCTION: sig_null */
142
143/* FUNCTION: qstring */
144
145/* SYNOPSIS
146 * Quote a string for transmission over the IMAP protocol.
147 * END SYNOPSIS */
148
149static char *				/* R: the quoted string		*/
150qstring (
151  /* PARAMETERS */
152  const char *s				/* I: string to quote		*/
153  /* END PARAMETERS */
154  )
155{
156    char *c;				/* pointer to returned string   */
157    register const char *p1;		/* scratch pointers		*/
158    register char *p2;			/* scratch pointers             */
159    int len;				/* length of array to malloc    */
160    int num_quotes;			/* number of '"' chars in string*/
161
162    /* see of we have to deal with any '"' characters */
163    num_quotes = 0;
164    p1 = s;
165    while ((p1 = strchr(p1, '"')) != NULL) {
166	p1++;
167	num_quotes++;
168    }
169
170    if (!num_quotes) {
171	/*
172	 * no double-quotes to escape, so just wrap the input string
173	 * in double-quotes and return it.
174	 */
175	len = (int)strlen(s) + 2 + 1;
176	c = malloc(len);
177	if (c == NULL) {
178	    return NULL;
179	}
180	*c = '"';
181        *(c+1) = '\0';
182	strcat(c, s);
183	strcat(c, "\"");
184	return c;
185    }
186    /*
187     * Ugh, we have to escape double quotes ...
188     */
189    len = (int)strlen(s) + 2 + (2*num_quotes) + 1;
190    c = malloc(len);
191    if (c == NULL) {
192	return NULL;
193    }
194    p1 = s;
195    p2 = c;
196    *p2++ = '"';
197    while (*p1) {
198	if (*p1 == '"') {
199	    *p2++ = '\\';		/* escape the '"' */
200	}
201	*p2++ = *p1++;
202    }
203    strcpy(p2, "\"");
204    return c;
205}
206
207/* END FUNCTION: qstring */
208
209/* FUNCTION: auth_rimap_init */
210
211/* SYNOPSIS
212 * Validate the host and service names for the remote server.
213 * END SYNOPSIS */
214
215int
216auth_rimap_init (
217  /* PARAMETERS */
218  void					/* no parameters */
219  /* END PARAMETERS */
220  )
221{
222
223    /* VARIABLES */
224    struct addrinfo hints;
225    int err;
226    char *c;				/* scratch pointer               */
227    /* END VARIABLES */
228
229    if (mech_option == NULL) {
230	syslog(LOG_ERR, "rimap_init: no hostname specified");
231	return -1;
232    } else {
233	r_host = mech_option;
234    }
235
236    /* Determine the port number to connect to.
237     *
238     * r_host has already been initialized to the hostname and optional port
239     * port name to connect to. The format of the string is:
240     *
241     *		hostname
242     * or
243     *		hostname/port
244     */
245
246    c = strchr(r_host, '/');		/* look for optional service  */
247
248    if (c != NULL) {
249	*c++ = '\0';			/* tie off hostname and point */
250					/* to service string          */
251    } else {
252	c = DEFAULT_REMOTE_SERVICE;
253    }
254
255    if (ai)
256	freeaddrinfo(ai);
257    memset(&hints, 0, sizeof(hints));
258    hints.ai_family = PF_UNSPEC;
259    hints.ai_socktype = SOCK_STREAM;
260    hints.ai_flags = AI_CANONNAME;
261    if ((err = getaddrinfo(r_host, c, &hints, &ai)) != 0) {
262	syslog(LOG_ERR, "auth_rimap_init: getaddrinfo %s/%s: %s",
263	       r_host, c, gai_strerror(err));
264	return -1;
265     }
266    /* Make sure we have AF_INET or AF_INET6 addresses. */
267    if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
268	syslog(LOG_ERR, "auth_rimap_init: no IP address info for %s",
269	       ai->ai_canonname ? ai->ai_canonname : r_host);
270	freeaddrinfo(ai);
271	ai = NULL;
272	return -1;
273    }
274
275    return 0;
276}
277
278/* END FUNCTION: auth_rimap_init */
279
280/* FUNCTION: auth_rimap */
281
282/* SYNOPSIS
283 * Proxy authenticate to a remote IMAP server.
284 *
285 * This mechanism takes the plaintext authenticator and password, forms
286 * them into an IMAP LOGIN command, then attempts to authenticate to
287 * a remote IMAP server using those values. If the remote authentication
288 * succeeds the credentials are considered valid.
289 *
290 * NOTE: since IMSP uses the same form of LOGIN command as IMAP does,
291 * this driver will also work with IMSP servers.
292 */
293
294/* XXX This should be extended to support SASL PLAIN authentication */
295
296char *					/* R: Allocated response string */
297auth_rimap (
298  /* PARAMETERS */
299  const char *login,			/* I: plaintext authenticator */
300  const char *password,			/* I: plaintext password */
301  const char *service __attribute__((unused)),
302  const char *realm __attribute__((unused))
303  /* END PARAMETERS */
304  )
305{
306    /* VARIABLES */
307    int	s=-1;				/* socket to remote auth host   */
308    struct addrinfo *r;			/* remote socket address info   */
309    struct iovec iov[5];		/* for sending LOGIN command    */
310    char *qlogin;			/* pointer to "quoted" login    */
311    char *qpass;			/* pointer to "quoted" password */
312    char *c;				/* scratch pointer              */
313    int rc;				/* return code scratch area     */
314    char rbuf[RESP_LEN];		/* response read buffer         */
315    char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
316    int saved_errno;
317    int niflags;
318    /* END VARIABLES */
319
320    /* sanity checks */
321    assert(login != NULL);
322    assert(password != NULL);
323
324    /*establish connection to remote */
325    for (r = ai; r; r = r->ai_next) {
326	s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
327	if (s < 0)
328	    continue;
329	if (connect(s, r->ai_addr, r->ai_addrlen) >= 0)
330	    break;
331	close(s);
332	s = -1;
333	saved_errno = errno;
334	niflags = (NI_NUMERICHOST | NI_NUMERICSERV);
335#ifdef NI_WITHSCOPEID
336	if (r->ai_family == AF_INET6)
337	    niflags |= NI_WITHSCOPEID;
338#endif
339	if (getnameinfo(r->ai_addr, r->ai_addrlen, hbuf, sizeof(hbuf),
340			pbuf, sizeof(pbuf), niflags) != 0) {
341	    strlcpy(hbuf, "unknown", sizeof(hbuf));
342	    strlcpy(pbuf, "unknown", sizeof(pbuf));
343	}
344	errno = saved_errno;
345	syslog(LOG_WARNING, "auth_rimap: connect %s[%s]/%s: %m",
346	       ai->ai_canonname ? ai->ai_canonname : r_host, hbuf, pbuf);
347    }
348    if (s < 0) {
349	if (getnameinfo(ai->ai_addr, ai->ai_addrlen, NULL, 0,
350			pbuf, sizeof(pbuf), NI_NUMERICSERV) != 0)
351	    strlcpy(pbuf, "unknown", sizeof(pbuf));
352	syslog(LOG_WARNING, "auth_rimap: couldn't connect to %s/%s",
353	       ai->ai_canonname ? ai->ai_canonname : r_host, pbuf);
354	return strdup("NO [ALERT] Couldn't contact remote authentication server");
355    }
356
357    /* CLAIM: we now have a TCP connection to the remote IMAP server */
358
359    /*
360     * Install noop signal handlers. These just reinstall the handler
361     * and return so that we take an EINTR during network I/O.
362     */
363    (void) signal(SIGALRM, sig_null);
364    (void) signal(SIGPIPE, sig_null);
365
366    /* read and parse the IMAP banner */
367
368    alarm(NETWORK_IO_TIMEOUT);
369    rc = (int)read(s, rbuf, sizeof(rbuf));
370    alarm(0);
371    if ( rc>0 ) {
372        /* check if there is more to read */
373        fd_set         perm;
374        int            fds, ret;
375        struct timeval timeout;
376
377        FD_ZERO(&perm);
378        FD_SET(s, &perm);
379        fds = s +1;
380
381        timeout.tv_sec  = 1;
382        timeout.tv_usec = 0;
383        while( select (fds, &perm, NULL, NULL, &timeout ) >0 ) {
384           if ( FD_ISSET(s, &perm) ) {
385              ret = (int)read(s, rbuf+rc, sizeof(rbuf)-rc);
386              if ( ret<0 ) {
387                 rc = ret;
388                 break;
389              } else {
390                 rc += ret;
391              }
392           }
393        }
394    }
395    if (rc == -1) {
396	syslog(LOG_WARNING, "auth_rimap: read (banner): %m");
397	(void) close(s);
398	return strdup("NO [ALERT] error synchronizing with remote authentication server");
399    }
400    rbuf[rc] = '\0';			/* tie off response */
401    c = strpbrk(rbuf, "\r\n");
402    if (c != NULL) {
403	*c = '\0';			/* tie off line termination */
404    }
405
406    if (!strncmp(rbuf, "* NO", sizeof("* NO")-1)) {
407	(void) close(s);
408	return strdup(RESP_UNAVAILABLE);
409    }
410    if (!strncmp(rbuf, "* BYE", sizeof("* BYE")-1)) {
411	(void) close(s);
412	return strdup(RESP_UNAVAILABLE);
413    }
414    if (strncmp(rbuf, "* OK", sizeof("* OK")-1)) {
415	syslog(LOG_WARNING,
416	       "auth_rimap: unexpected response during initial handshake: %s",
417	       rbuf);
418	(void) close(s);
419	return strdup(RESP_UNEXPECTED);
420    }
421
422    /* build the LOGIN command */
423
424    qlogin = qstring(login);		/* quote login */
425    qpass = qstring(password);		/* quote password */
426    if (qlogin == NULL) {
427	if (qpass != NULL) {
428	    memset(qpass, 0, strlen(qpass));
429	    free(qpass);
430	}
431	(void) close(s);
432	syslog(LOG_WARNING, "auth_rimap: qstring(login) == NULL");
433	return strdup(RESP_IERROR);
434    }
435    if (qpass == NULL) {
436	if (qlogin != NULL) {
437	    memset(qlogin, 0, strlen(qlogin));
438	    free(qlogin);
439	}
440	(void) close(s);
441	syslog(LOG_WARNING, "auth_rimap: qstring(password) == NULL");
442	return strdup(RESP_IERROR);
443    }
444
445    iov[0].iov_base = LOGIN_CMD;
446    iov[0].iov_len  = sizeof(LOGIN_CMD) - 1;
447    iov[1].iov_base = qlogin;
448    iov[1].iov_len  = strlen(qlogin);
449    iov[2].iov_base = " ";
450    iov[2].iov_len  = sizeof(" ") - 1;
451    iov[3].iov_base = qpass;
452    iov[3].iov_len  = strlen(qpass);
453    iov[4].iov_base = "\r\n";
454    iov[4].iov_len  = sizeof("\r\n") - 1;
455
456    if (flags & VERBOSE) {
457	syslog(LOG_DEBUG, "auth_rimap: sending %s%s %s",
458	       LOGIN_CMD, qlogin, qpass);
459    }
460    alarm(NETWORK_IO_TIMEOUT);
461    rc = retry_writev(s, iov, 5);
462    alarm(0);
463    if (rc == -1) {
464	syslog(LOG_WARNING, "auth_rimap: writev: %m");
465	memset(qlogin, 0, strlen(qlogin));
466	free(qlogin);
467	memset(qpass, 0, strlen(qpass));
468	free(qpass);
469	(void)close(s);
470	return strdup(RESP_IERROR);
471    }
472
473    /* don't need these any longer */
474    memset(qlogin, 0, strlen(qlogin));
475    free(qlogin);
476    memset(qpass, 0, strlen(qpass));
477    free(qpass);
478
479    /* read and parse the LOGIN response */
480
481    alarm(NETWORK_IO_TIMEOUT);
482    rc = (int)read(s, rbuf, sizeof(rbuf));
483    alarm(0);
484    if ( rc>0 ) {
485        /* check if there is more to read */
486        fd_set         perm;
487        int            fds, ret;
488        struct timeval timeout;
489
490        FD_ZERO(&perm);
491        FD_SET(s, &perm);
492        fds = s +1;
493
494        timeout.tv_sec  = 1;
495        timeout.tv_usec = 0;
496        while( select (fds, &perm, NULL, NULL, &timeout ) >0 ) {
497           if ( FD_ISSET(s, &perm) ) {
498              ret = (int)read(s, rbuf+rc, sizeof(rbuf)-rc);
499              if ( ret<0 ) {
500                 rc = ret;
501                 break;
502              } else {
503                 rc += ret;
504              }
505           }
506        }
507    }
508    (void) close(s);			/* we're done with the remote */
509    if (rc == -1) {
510	syslog(LOG_WARNING, "auth_rimap: read (response): %m");
511	return strdup(RESP_IERROR);
512    }
513
514    rbuf[rc] = '\0';			/* tie off response */
515    c = strpbrk(rbuf, "\r\n");
516    if (c != NULL) {
517	*c = '\0';			/* tie off line termination */
518    }
519
520     if (!strncmp(rbuf, TAG " OK", sizeof(TAG " OK")-1)) {
521	if (flags & VERBOSE) {
522	    syslog(LOG_DEBUG, "auth_rimap: [%s] %s", login, rbuf);
523	}
524	return strdup("OK remote authentication successful");
525    }
526    if (!strncmp(rbuf, TAG " NO", sizeof(TAG " NO")-1)) {
527	if (flags & VERBOSE) {
528	    syslog(LOG_DEBUG, "auth_rimap: [%s] %s", login, rbuf);
529	}
530	return strdup("NO remote server rejected your credentials");
531    }
532    syslog(LOG_WARNING, "auth_rimap: unexpected response to auth request: %s",
533	   rbuf);
534    return strdup(RESP_UNEXPECTED);
535
536}
537
538/* END FUNCTION: auth_rimap */
539
540/* END MODULE: auth_rimap */
541