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