#ifdef __GNUC__
#ident "$Id: auth_rimap.c,v 1.1 2004/03/31 18:08:42 dasenbro Exp $"
#endif
#include "mechanisms.h"
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#ifdef _AIX
# include <strings.h>
#endif
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <netdb.h>
#include "auth_rimap.h"
#include "utils.h"
#include "globals.h"
static const char *r_host = NULL;
static struct addrinfo *ai = NULL;
#define DEFAULT_REMOTE_SERVICE "imap"
#define TAG "saslauthd"
#define LOGIN_CMD (TAG " LOGIN ")
#define NETWORK_IO_TIMEOUT 30
#define RESP_LEN 1000
#define RESP_IERROR "NO [ALERT] saslauthd internal error"
#define RESP_UNAVAILABLE "NO [ALERT] The remote authentication server is currently unavailable"
#define RESP_UNEXPECTED "NO [ALERT] Unexpected response from remote authentication server"
static RETSIGTYPE
sig_null (
int sig
)
{
switch (sig) {
case SIGALRM:
signal(SIGALRM, sig_null);
break;
case SIGPIPE:
signal(SIGPIPE, sig_null);
break;
default:
syslog(LOG_WARNING, "auth_rimap: unexpected signal %d", sig);
break;
}
#if RETSIGTYPE == void
return;
#else
return 0;
#endif
}
static char *
qstring (
const char *s
)
{
char *c;
register const char *p1;
register char *p2;
int len;
int num_quotes;
num_quotes = 0;
p1 = s;
while ((p1 = strchr(p1, '"')) != NULL) {
num_quotes++;
}
if (!num_quotes) {
len = strlen(s) + 2 + 1;
c = malloc(len);
if (c == NULL) {
return NULL;
}
*c = '"';
*(c+1) = '\0';
strcat(c, s);
strcat(c, "\"");
return c;
}
len = strlen(s) + 2 + (2*num_quotes) + 1;
c = malloc(len);
if (c == NULL) {
return NULL;
}
p1 = s;
p2 = c;
*p2++ = '"';
while (*p1) {
if (*p1 == '"') {
*p2++ = '\\';
}
*p2++ = *p1++;
}
strcat(p2, "\"");
return c;
}
int
auth_rimap_init (
void
)
{
struct addrinfo hints;
int err;
char *c;
if (mech_option == NULL) {
syslog(LOG_ERR, "rimap_init: no hostname specified");
return -1;
} else {
r_host = mech_option;
}
c = strchr(r_host, '/');
if (c != NULL) {
*c++ = '\0';
} else {
c = DEFAULT_REMOTE_SERVICE;
}
if (ai)
freeaddrinfo(ai);
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_CANONNAME;
if ((err = getaddrinfo(r_host, c, &hints, &ai)) != 0) {
syslog(LOG_ERR, "auth_rimap_init: getaddrinfo %s/%s: %s",
r_host, c, gai_strerror(err));
return -1;
}
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
syslog(LOG_ERR, "auth_rimap_init: no IP address info for %s",
ai->ai_canonname ? ai->ai_canonname : r_host);
freeaddrinfo(ai);
ai = NULL;
return -1;
}
return 0;
}
char *
auth_rimap (
const char *login,
const char *password,
const char *service __attribute__((unused)),
const char *realm __attribute__((unused))
)
{
int s=-1;
struct addrinfo *r;
struct iovec iov[5];
char *qlogin;
char *qpass;
char *c;
int rc;
char rbuf[RESP_LEN];
char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
int saved_errno;
assert(login != NULL);
assert(password != NULL);
for (r = ai; r; r = r->ai_next) {
s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
if (s < 0)
continue;
if (connect(s, r->ai_addr, r->ai_addrlen) >= 0)
break;
close(s);
s = -1;
saved_errno = errno;
getnameinfo(r->ai_addr, r->ai_addrlen,
hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
NI_NUMERICHOST | NI_WITHSCOPEID | NI_NUMERICSERV);
errno = saved_errno;
syslog(LOG_WARNING, "auth_rimap: connect %s[%s]/%s: %m",
ai->ai_canonname ? ai->ai_canonname : r_host, hbuf, pbuf);
}
if (s < 0) {
getnameinfo(ai->ai_addr, ai->ai_addrlen, NULL, 0, pbuf, sizeof(pbuf),
NI_NUMERICSERV);
syslog(LOG_WARNING, "auth_rimap: couldn't connect to %s/%s",
ai->ai_canonname ? ai->ai_canonname : r_host, pbuf);
return strdup("NO [ALERT] Couldn't contact remote authentication server");
}
(void) signal(SIGALRM, sig_null);
(void) signal(SIGPIPE, sig_null);
alarm(NETWORK_IO_TIMEOUT);
rc = read(s, rbuf, sizeof(rbuf));
alarm(0);
if (rc == -1) {
syslog(LOG_WARNING, "auth_rimap: read (banner): %m");
(void) close(s);
return strdup("NO [ALERT] error synchronizing with remote authentication server");
}
rbuf[rc] = '\0';
c = strpbrk(rbuf, "\r\n");
if (c != NULL) {
*c = '\0';
}
if (!strncmp(rbuf, "* NO", sizeof("* NO")-1)) {
(void) close(s);
return strdup(RESP_UNAVAILABLE);
}
if (!strncmp(rbuf, "* BYE", sizeof("* BYE")-1)) {
(void) close(s);
return strdup(RESP_UNAVAILABLE);
}
if (strncmp(rbuf, "* OK", sizeof("* OK")-1)) {
syslog(LOG_WARNING,
"auth_rimap: unexpected response during initial handshake: %s",
rbuf);
(void) close(s);
return strdup(RESP_UNEXPECTED);
}
qlogin = qstring(login);
qpass = qstring(password);
if (qlogin == NULL) {
if (qpass != NULL) {
memset(qpass, 0, strlen(qpass));
free(qpass);
}
(void) close(s);
syslog(LOG_WARNING, "auth_rimap: qstring(login) == NULL");
return strdup(RESP_IERROR);
}
if (qpass == NULL) {
if (qlogin != NULL) {
memset(qlogin, 0, strlen(qlogin));
free(qlogin);
}
(void) close(s);
syslog(LOG_WARNING, "auth_rimap: qstring(password) == NULL");
return strdup(RESP_IERROR);
}
iov[0].iov_base = LOGIN_CMD;
iov[0].iov_len = sizeof(LOGIN_CMD) - 1;
iov[1].iov_base = qlogin;
iov[1].iov_len = strlen(qlogin);
iov[2].iov_base = " ";
iov[2].iov_len = sizeof(" ") - 1;
iov[3].iov_base = qpass;
iov[3].iov_len = strlen(qpass);
iov[4].iov_base = "\r\n";
iov[4].iov_len = sizeof("\r\n") - 1;
if (flags & VERBOSE) {
syslog(LOG_DEBUG, "auth_rimap: sending %s%s %s",
LOGIN_CMD, qlogin, qpass);
}
alarm(NETWORK_IO_TIMEOUT);
rc = retry_writev(s, iov, 5);
alarm(0);
if (rc == -1) {
syslog(LOG_WARNING, "auth_rimap: writev: %m");
memset(qlogin, 0, strlen(qlogin));
free(qlogin);
memset(qpass, 0, strlen(qlogin));
free(qpass);
(void)close(s);
return strdup(RESP_IERROR);
}
memset(qlogin, 0, strlen(qlogin));
free(qlogin);
memset(qpass, 0, strlen(qlogin));
free(qpass);
alarm(NETWORK_IO_TIMEOUT);
rc = read(s, rbuf, sizeof(rbuf));
alarm(0);
(void) close(s);
if (rc == -1) {
syslog(LOG_WARNING, "auth_rimap: read (response): %m");
return strdup(RESP_IERROR);
}
rbuf[rc] = '\0';
c = strpbrk(rbuf, "\r\n");
if (c != NULL) {
*c = '\0';
}
if (!strncmp(rbuf, TAG " OK", sizeof(TAG " OK")-1)) {
if (flags & VERBOSE) {
syslog(LOG_DEBUG, "auth_rimap: [%s] %s", login, rbuf);
}
return strdup("OK remote authentication successful");
}
if (!strncmp(rbuf, TAG " NO", sizeof(TAG " NO")-1)) {
if (flags & VERBOSE) {
syslog(LOG_DEBUG, "auth_rimap: [%s] %s", login, rbuf);
}
return strdup("NO remote server rejected your credentials");
}
syslog(LOG_WARNING, "auth_rimap: unexpected response to auth request: %s",
rbuf);
return RESP_UNEXPECTED;
}