/* ** Copyright 2009 Marko Njezic ** Licensed under the same terms as Courier Authlib AND/OR Courier Maildrop. ** ** Partially based on authdaemonlib.c from Courier Authlib, which had the following statement: ** ** Copyright 2000-2006 Double Precision, Inc. See COPYING for ** distribution information. ** ** Code that was taken from authdaemonlib.c is as follows: ** - s_connect() function ** - opensock() function with modification to accept socket address ** - writeauth() function ** - readline() function with related support functions (with modification ** to time-out after TIMEOUT_READ seconds) */ #include "dovecotauth.h" #include #include #include #include #include #include #include #include #include #include #include #include static int TIMEOUT_SOCK=10, TIMEOUT_WRITE=10, TIMEOUT_READ=30; static int s_connect(int sockfd, const struct sockaddr *addr, size_t addr_s, time_t connect_timeout) { fd_set fdr; struct timeval tv; int rc; #ifdef SOL_KEEPALIVE setsockopt(sockfd, SOL_SOCKET, SOL_KEEPALIVE, (const char *)&dummy, sizeof(dummy)); #endif #ifdef SOL_LINGER { struct linger l; l.l_onoff=0; l.l_linger=0; setsockopt(sockfd, SOL_SOCKET, SOL_LINGER, (const char *)&l, sizeof(l)); } #endif /* ** If configuration says to use the kernel's timeout settings, ** just call connect, and be done with it. */ if (connect_timeout == 0) return ( connect(sockfd, addr, addr_s)); /* Asynchronous connect with timeout. */ if (fcntl(sockfd, F_SETFL, O_NONBLOCK) < 0) return (-1); if ( connect(sockfd, addr, addr_s) == 0) { /* That was easy, we're done. */ if (fcntl(sockfd, F_SETFL, 0) < 0) return (-1); return (0); } if (errno != EINPROGRESS) return -1; /* Wait for the connection to go through, until the timeout expires */ FD_ZERO(&fdr); FD_SET(sockfd, &fdr); tv.tv_sec=connect_timeout; tv.tv_usec=0; rc=select(sockfd+1, 0, &fdr, 0, &tv); if (rc < 0) return (-1); if (!FD_ISSET(sockfd, &fdr)) { errno=ETIMEDOUT; return (-1); } { int gserr; socklen_t gslen = sizeof(gserr); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *)&gserr, &gslen)==0) { if (gserr == 0) return 0; errno=gserr; } } return (-1); } static int opensock(const char *addr) { int s=socket(PF_UNIX, SOCK_STREAM, 0); struct sockaddr_un skun; memset(&skun, 0, sizeof(skun)); skun.sun_family=AF_UNIX; strncpy(skun.sun_path, addr, sizeof(skun.sun_path)-1); if (s < 0) { perror("CRIT: dovecotauth: socket() failed"); return (-1); } { const char *p=getenv("TIMEOUT_SOCK"); int n=atoi(p ? p:"0"); if (n > 0) TIMEOUT_SOCK=n; } { const char *p=getenv("TIMEOUT_READ"); int n=atoi(p ? p:"0"); if (n > 0) TIMEOUT_READ=n; } { const char *p=getenv("TIMEOUT_WRITE"); int n=atoi(p ? p:"0"); if (n > 0) TIMEOUT_WRITE=n; } if (s_connect(s, (const struct sockaddr *)&skun, sizeof(skun), TIMEOUT_SOCK)) { perror("ERR: dovecotauth: s_connect() failed"); if (errno == ETIMEDOUT || errno == ECONNREFUSED) fprintf(stderr, "ERR: [Hint: perhaps dovecot-auth daemon is not running?]\n"); close(s); return (-1); } return (s); } static int writeauth(int fd, const char *p, unsigned pl) { fd_set fds; struct timeval tv; while (pl) { int n; FD_ZERO(&fds); FD_SET(fd, &fds); tv.tv_sec=TIMEOUT_WRITE; tv.tv_usec=0; if (select(fd+1, 0, &fds, 0, &tv) <= 0 || !FD_ISSET(fd, &fds)) return (-1); n=write(fd, p, pl); if (n <= 0) return (-1); p += n; pl -= n; } return (0); } struct enum_getch { char buffer[BUFSIZ]; char *buf_ptr; size_t buf_left; }; #define getauthc(fd,eg) ((eg)->buf_left-- ? \ (unsigned char)*((eg)->buf_ptr)++:\ fillgetauthc((fd),(eg))) static int fillgetauthc(int fd, struct enum_getch *eg) { time_t end_time, curtime; time(&end_time); end_time += TIMEOUT_READ; for (;;) { int n; fd_set fds; struct timeval tv; time(&curtime); if (curtime >= end_time) break; FD_ZERO(&fds); FD_SET(fd, &fds); tv.tv_sec=end_time - curtime; tv.tv_usec=0; if (select(fd+1, &fds, 0, 0, &tv) <= 0 || !FD_ISSET(fd, &fds)) break; n=read(fd, eg->buffer, sizeof(eg->buffer)); if (n <= 0) break; eg->buf_ptr=eg->buffer; eg->buf_left=n; --eg->buf_left; return (unsigned char)*(eg->buf_ptr)++; } return EOF; } static int readline(int fd, struct enum_getch *eg, char *buf, size_t bufsize) { if (bufsize == 0) return EOF; while (--bufsize) { int ch=getauthc(fd, eg); if (ch == EOF) return -1; if (ch == '\n') break; *buf++=ch; } *buf=0; return 0; } /* ** The actual implementation of Dovecot authentication protocol handling follows. ** Full specification of the protocol can be found at: http://wiki.dovecot.org/Authentication%20Protocol ** We are only interested in the "master" type requests for user information. */ int parse_userinfo(const char *user, const char *linebuf, int (*func)(struct dovecotauthinfo *, void *), void *arg) { int return_value=1; struct dovecotauthinfo a; char *buf, *p; uid_t u; /* Validate input arguments */ if (!user || !linebuf) return (1); /* Try to allocate buffer */ buf = (char *)malloc(strlen(linebuf)+1); if (!buf) return (1); strcpy(buf, linebuf); memset(&a, 0, sizeof(a)); a.homedir=""; p = strtok(buf, "\t"); if (p) a.address=p; else a.address=user; /* Parse any additional parameters */ while ((p = strtok(0, "\t")) != 0) { if (strncmp(p, "uid=", 4) == 0) { u=atol(p+4); a.sysuserid = &u; if (u == 0) { fprintf(stderr, "ERR: dovecotauth: Received invalid uid from auth socket\n"); return_value=1; goto cleanup_parse_userinfo; } } else if (strncmp(p, "gid=", 4) == 0) { a.sysgroupid=atol(p+4); if (a.sysgroupid == 0) { fprintf(stderr, "ERR: dovecotauth: Received invalid gid from auth socket\n"); return_value=1; goto cleanup_parse_userinfo; } } else if (strncmp(p, "system_user=", 12) == 0) { a.sysusername=p+12; if (a.sysusername) { struct passwd *q=getpwnam(a.sysusername); if (q && q->pw_uid == 0) { fprintf(stderr, "ERR: dovecotauth: Received invalid system user from auth socket\n"); return_value=1; goto cleanup_parse_userinfo; } } } else if (strncmp(p, "home=", 5) == 0) { a.homedir=p+5; } else if (strncmp(p, "mail=", 5) == 0) { a.maildir=p+5; } } return_value = (*func)(&a, arg); cleanup_parse_userinfo: free(buf); return return_value; } #define DOVECOTAUTH_LINEBUFSIZE 8192 int _dovecotauth_getuserinfo(int wrfd, int rdfd, const char *user, int (*func)(struct dovecotauthinfo *, void *), void *arg) { static char cmdpart1[]="VERSION\t1\t0\nUSER\t1\t"; static char cmdpart2[]="\tservice=maildrop\n"; int return_value=1, handshake=0; struct enum_getch eg; char *cmdbuf, *linebuf; /* Validate input arguments */ if (!user) return (1); /* Try to allocate buffers */ cmdbuf=(char *)malloc(strlen(cmdpart1)+strlen(cmdpart2)+strlen(user)+20); if (!cmdbuf) return (1); linebuf=(char *)malloc(DOVECOTAUTH_LINEBUFSIZE); if (!linebuf) return (1); /* Initial handshake */ eg.buf_left=0; while (readline(rdfd, &eg, linebuf, DOVECOTAUTH_LINEBUFSIZE) == 0) { if (strncmp(linebuf, "VERSION\t", 8) == 0) { if (strncmp(linebuf+8, "1\t", 2) != 0) { fprintf(stderr, "ERR: dovecotauth: Authentication protocol version mismatch\n"); return_value=1; goto cleanup_dovecotauth_getuserinfo; } } else if (strncmp(linebuf, "SPID\t", 5) == 0) { /* End of server side handshake */ handshake=1; break; } } if (!handshake) { fprintf(stderr, "ERR: dovecotauth: Did not receive proper server handshake from auth socket\n"); return_value=1; goto cleanup_dovecotauth_getuserinfo; } /* ** Try to be helpful in case that user tries to connect to the wrong auth socket. ** There's a slight chance that this won't execute in case that the previously ** returned line ends exactly at the buffer end, but we won't handle that case, ** since this is just a hint to the user, and not really neccessary. ** Normally, if user tries to communicate with wrong auth socket, ** we would simply time-out, while waiting for information. */ if (eg.buf_left > 0 && readline(rdfd, &eg, linebuf, DOVECOTAUTH_LINEBUFSIZE) == 0) { if (strncmp(linebuf, "CUID\t", 5) == 0) { fprintf(stderr, "ERR: dovecotauth: Trying to connect to what appears to be a client auth socket, instead of a master auth socket\n"); return_value=1; goto cleanup_dovecotauth_getuserinfo; } } /* Generate our part of communication */ strcat(strcat(strcpy(cmdbuf, cmdpart1), user), cmdpart2); /* Send our part of communication */ if (writeauth(wrfd, cmdbuf, strlen(cmdbuf))) { return_value=1; goto cleanup_dovecotauth_getuserinfo; } /* Parse returned information */ eg.buf_left=0; if (readline(rdfd, &eg, linebuf, DOVECOTAUTH_LINEBUFSIZE) == 0) { if (strncmp(linebuf, "USER\t1\t", 7) == 0) { /* User was found in the database and we now parse returned information */ return_value=parse_userinfo(user, linebuf+7, func, arg); goto cleanup_dovecotauth_getuserinfo; } else if (strcmp(linebuf, "NOTFOUND\t1") == 0) { /* User was not found in the database */ return_value=-1; /* Negative return value means that user is not found! */ goto cleanup_dovecotauth_getuserinfo; } else if (strncmp(linebuf, "FAIL\t1", 6) == 0) { /* An internal error has occurred on Dovecot's end */ return_value=1; goto cleanup_dovecotauth_getuserinfo; } else { fprintf(stderr, "ERR: dovecotauth: Received unknown input from auth socket\n"); } } else fprintf(stderr, "ERR: dovecotauth: Did not receive proper input from auth socket\n"); cleanup_dovecotauth_getuserinfo: free(cmdbuf); free(linebuf); return return_value; } int dovecotauth_getuserinfo(const char *addr, const char *user, int (*func)(struct dovecotauthinfo *, void *), void *arg) { int s=opensock(addr); int rc; if (s < 0) { return (1); } rc = _dovecotauth_getuserinfo(s, s, user, func, arg); close(s); return rc; }