GSS_S_PROMPTING_NEEDED is a bit
[cyrus-sasl.git] / saslauthd / auth_rimap.c
1 /* MODULE: auth_rimap */
2
3 /* COPYRIGHT
4  * Copyright (c) 1998 Messaging Direct Ltd.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
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.
15  *
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
27  * DAMAGE.
28  * 
29  * Copyright 1998, 1999 Carnegie Mellon University
30  * 
31  *                       All Rights Reserved
32  * 
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
40  * permission.
41  * 
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.
49  * END COPYRIGHT */
50
51 /* SYNOPSIS
52  * Proxy authentication to a remote IMAP (or IMSP) server.
53  * END SYNOPSIS */
54
55 #ifdef __GNUC__
56 #ident "$Id: auth_rimap.c,v 1.12 2006/04/06 20:19:54 jeaton Exp $"
57 #endif
58
59 /* PUBLIC DEPENDENCIES */
60 #include "mechanisms.h"
61
62 #include <unistd.h>
63 #include <stdlib.h>
64 #include <assert.h>
65 #include <errno.h>
66 #include <string.h>
67 #ifdef _AIX
68 # include <strings.h>
69 #endif /* _AIX */
70 #include <syslog.h>
71 #include <sys/types.h>
72 #include <sys/socket.h>
73 #include <netinet/in.h>
74 #include <arpa/inet.h>
75 #include <signal.h>
76 #include <netdb.h>
77
78 #include "auth_rimap.h"
79 #include "utils.h"
80 #include "globals.h"
81 /* END PUBLIC DEPENDENCIES */
82
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 */
87
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  */
94
95 /* Common failure response strings for auth_rimap() */
96
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"
100 \f
101 /* FUNCTION: sig_null */
102
103 /* SYNOPSIS
104  * Catch and ignore a signal.
105  * END SYNOPSIS */
106
107 static RETSIGTYPE                               /* R: OS dependent */
108 sig_null (
109   /* PARAMETERS */
110   int sig                                       /* I: signal being caught */
111   /* END PARAMETERS */
112   )
113 {
114
115     switch (sig) {
116         
117       case SIGALRM:
118         signal(SIGALRM, sig_null);
119         break;
120
121       case SIGPIPE:
122         signal(SIGPIPE, sig_null);
123         break;
124
125       default:
126         syslog(LOG_WARNING, "auth_rimap: unexpected signal %d", sig);
127         break;
128     }
129 #ifdef __APPLE__
130     return;
131 #else /* __APPLE__ */
132 # if RETSIGTYPE == void
133     return;
134 # else /* RETSIGTYPE */
135     return 0;
136 # endif /* RETSIGTYPE */
137 #endif /* __APPLE__ */
138 }
139
140 /* END FUNCTION: sig_null */
141 \f
142 /* FUNCTION: qstring */
143
144 /* SYNOPSIS
145  * Quote a string for transmission over the IMAP protocol.
146  * END SYNOPSIS */
147
148 static char *                           /* R: the quoted string         */
149 qstring (
150   /* PARAMETERS */
151   const char *s                         /* I: string to quote           */
152   /* END PARAMETERS */
153   )
154 {
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*/
160
161     /* see of we have to deal with any '"' characters */
162     num_quotes = 0;
163     p1 = s;
164     while ((p1 = strchr(p1, '"')) != NULL) {
165         num_quotes++;
166     }
167     
168     if (!num_quotes) {
169         /*
170          * no double-quotes to escape, so just wrap the input string
171          * in double-quotes and return it.
172          */
173         len = strlen(s) + 2 + 1;
174         c = malloc(len);
175         if (c == NULL) {
176             return NULL;
177         }
178         *c = '"';
179         *(c+1) = '\0';
180         strcat(c, s);
181         strcat(c, "\"");
182         return c;
183     }
184     /*
185      * Ugh, we have to escape double quotes ...
186      */
187     len = strlen(s) + 2 + (2*num_quotes) + 1;
188     c = malloc(len);
189     if (c == NULL) {
190         return NULL;
191     }
192     p1 = s;
193     p2 = c;
194     *p2++ = '"';
195     while (*p1) {
196         if (*p1 == '"') {
197             *p2++ = '\\';               /* escape the '"' */
198         }
199         *p2++ = *p1++;
200     }
201     strcat(p2, "\"");
202     return c;
203 }
204
205 /* END FUNCTION: qstring */
206 \f
207 /* FUNCTION: auth_rimap_init */
208
209 /* SYNOPSIS
210  * Validate the host and service names for the remote server.
211  * END SYNOPSIS */
212
213 int
214 auth_rimap_init (
215   /* PARAMETERS */
216   void                                  /* no parameters */
217   /* END PARAMETERS */
218   )
219 {
220
221     /* VARIABLES */
222     struct addrinfo hints;
223     int err;
224     char *c;                            /* scratch pointer               */
225     /* END VARIABLES */
226
227     if (mech_option == NULL) {
228         syslog(LOG_ERR, "rimap_init: no hostname specified");
229         return -1;
230     } else {
231         r_host = mech_option;
232     }
233
234     /* Determine the port number to connect to.
235      *
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:
238      *
239      *          hostname
240      * or
241      *          hostname/port
242      */
243
244     c = strchr(r_host, '/');            /* look for optional service  */
245     
246     if (c != NULL) {
247         *c++ = '\0';                    /* tie off hostname and point */
248                                         /* to service string          */
249     } else {
250         c = DEFAULT_REMOTE_SERVICE;
251     }
252
253     if (ai)
254         freeaddrinfo(ai);
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));
262         return -1;
263      }
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);
268         freeaddrinfo(ai);
269         ai = NULL;
270         return -1;
271     }
272
273     return 0;
274 }
275
276 /* END FUNCTION: auth_rimap_init */
277 \f
278 /* FUNCTION: auth_rimap */
279
280 /* SYNOPSIS
281  * Proxy authenticate to a remote IMAP server.
282  *
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.
287  *
288  * NOTE: since IMSP uses the same form of LOGIN command as IMAP does,
289  * this driver will also work with IMSP servers.
290  */
291
292 /* XXX This should be extended to support SASL PLAIN authentication */
293
294 char *                                  /* R: Allocated response string */
295 auth_rimap (
296   /* PARAMETERS */
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))
301   /* END PARAMETERS */
302   )
303 {
304     /* VARIABLES */
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];
314     int saved_errno;
315     int niflags;
316     /* END VARIABLES */
317
318     /* sanity checks */
319     assert(login != NULL);
320     assert(password != NULL);
321
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);
325         if (s < 0)
326             continue;
327         if (connect(s, r->ai_addr, r->ai_addrlen) >= 0)
328             break;
329         close(s);
330         s = -1;
331         saved_errno = errno;
332         niflags = (NI_NUMERICHOST | NI_NUMERICSERV);
333 #ifdef NI_WITHSCOPEID
334         if (r->ai_family == AF_INET6)
335             niflags |= NI_WITHSCOPEID;
336 #endif
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));
341         }
342         errno = saved_errno;
343         syslog(LOG_WARNING, "auth_rimap: connect %s[%s]/%s: %m",
344                ai->ai_canonname ? ai->ai_canonname : r_host, hbuf, pbuf);
345     }
346     if (s < 0) {
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");
353     }
354
355     /* CLAIM: we now have a TCP connection to the remote IMAP server */
356
357     /*
358      * Install noop signal handlers. These just reinstall the handler
359      * and return so that we take an EINTR during network I/O.
360      */
361     (void) signal(SIGALRM, sig_null);
362     (void) signal(SIGPIPE, sig_null);
363     
364     /* read and parse the IMAP banner */
365
366     alarm(NETWORK_IO_TIMEOUT);
367     rc = read(s, rbuf, sizeof(rbuf));
368     alarm(0);
369     if (rc == -1) {
370         syslog(LOG_WARNING, "auth_rimap: read (banner): %m");
371         (void) close(s);
372         return strdup("NO [ALERT] error synchronizing with remote authentication server");
373     }
374     rbuf[rc] = '\0';                    /* tie off response */
375     c = strpbrk(rbuf, "\r\n");
376     if (c != NULL) {
377         *c = '\0';                      /* tie off line termination */
378     }
379
380     if (!strncmp(rbuf, "* NO", sizeof("* NO")-1)) {
381         (void) close(s);
382         return strdup(RESP_UNAVAILABLE);
383     }
384     if (!strncmp(rbuf, "* BYE", sizeof("* BYE")-1)) {
385         (void) close(s);
386         return strdup(RESP_UNAVAILABLE);
387     }
388     if (strncmp(rbuf, "* OK", sizeof("* OK")-1)) {
389         syslog(LOG_WARNING,
390                "auth_rimap: unexpected response during initial handshake: %s",
391                rbuf);
392         (void) close(s);
393         return strdup(RESP_UNEXPECTED);
394     }
395     
396     /* build the LOGIN command */
397
398     qlogin = qstring(login);            /* quote login */
399     qpass = qstring(password);          /* quote password */
400     if (qlogin == NULL) {
401         if (qpass != NULL) {
402             memset(qpass, 0, strlen(qpass));
403             free(qpass);
404         }
405         (void) close(s);
406         syslog(LOG_WARNING, "auth_rimap: qstring(login) == NULL");
407         return strdup(RESP_IERROR);
408     }
409     if (qpass == NULL) {
410         if (qlogin != NULL) {
411             memset(qlogin, 0, strlen(qlogin));
412             free(qlogin);
413         }
414         (void) close(s);
415         syslog(LOG_WARNING, "auth_rimap: qstring(password) == NULL");
416         return strdup(RESP_IERROR);
417     }
418
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;
429
430     if (flags & VERBOSE) {
431         syslog(LOG_DEBUG, "auth_rimap: sending %s%s %s",
432                LOGIN_CMD, qlogin, qpass);
433     }
434     alarm(NETWORK_IO_TIMEOUT);
435     rc = retry_writev(s, iov, 5);
436     alarm(0);
437     if (rc == -1) {
438         syslog(LOG_WARNING, "auth_rimap: writev: %m");
439         memset(qlogin, 0, strlen(qlogin));
440         free(qlogin);
441         memset(qpass, 0, strlen(qlogin));
442         free(qpass);
443         (void)close(s);
444         return strdup(RESP_IERROR);
445     }
446
447     /* don't need these any longer */
448     memset(qlogin, 0, strlen(qlogin));
449     free(qlogin);
450     memset(qpass, 0, strlen(qlogin));
451     free(qpass);
452
453     /* read and parse the LOGIN response */
454
455     alarm(NETWORK_IO_TIMEOUT);
456     rc = read(s, rbuf, sizeof(rbuf));
457     alarm(0);
458     (void) close(s);                    /* we're done with the remote */
459     if (rc == -1) {
460         syslog(LOG_WARNING, "auth_rimap: read (response): %m");
461         return strdup(RESP_IERROR);
462     }
463
464     rbuf[rc] = '\0';                    /* tie off response */
465     c = strpbrk(rbuf, "\r\n");
466     if (c != NULL) {
467         *c = '\0';                      /* tie off line termination */
468     }
469
470      if (!strncmp(rbuf, TAG " OK", sizeof(TAG " OK")-1)) {
471         if (flags & VERBOSE) {
472             syslog(LOG_DEBUG, "auth_rimap: [%s] %s", login, rbuf);
473         }
474         return strdup("OK remote authentication successful");
475     }
476     if (!strncmp(rbuf, TAG " NO", sizeof(TAG " NO")-1)) {
477         if (flags & VERBOSE) {
478             syslog(LOG_DEBUG, "auth_rimap: [%s] %s", login, rbuf);
479         }
480         return strdup("NO remote server rejected your credentials");
481     }
482     syslog(LOG_WARNING, "auth_rimap: unexpected response to auth request: %s",
483            rbuf);
484     return strdup(RESP_UNEXPECTED);
485     
486 }
487
488 /* END FUNCTION: auth_rimap */
489
490 /* END MODULE: auth_rimap */