/* * Copyright (c) 1999 Ian Freislich * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: authenticate.c,v 1.34 2003/01/07 12:25:55 ianf Exp $ */ #include #include #include #ifdef USE_PAM #include #endif #ifdef SOLARIS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "popd.h" #include "authenticate.h" /* global variables */ extern struct config config; /* function prototypes */ static void dosetuid(struct connection *user); static int db_file_pass(struct connection *cxn), #ifndef USE_PAM unix_pass(struct connection *cxn), #endif radius_pass(struct connection *cxn), checkpassword(struct connection *cxn), checkchallenge(struct connection *cxn, char *ch); static char *getsecret(struct connection *cxn); #ifdef USE_PAM static void freeresponse(int num, struct pam_response *response); static int conversation(int num, const struct pam_message **msg, struct pam_response **result, void *appdata), pam_pass(struct connection *cxn); static void freeresponse(int num, struct pam_response *response) { int i; for (i = 0; i < num; i++) if (response[i].resp != NULL) free(response[i].resp); free(response); } static int conversation(int num, const struct pam_message **msg, struct pam_response **result, void *appdata) { struct pam_response *response; int i; response = xmalloc(num * sizeof(struct pam_response)); for (i = 0; i < num; i++) switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_ON: response[i].resp = strdup(((char **)appdata)[0]); if (response[i].resp == NULL) { freeresponse(i, response); return PAM_CONV_ERR; } response[i].resp_retcode = PAM_SUCCESS; break; case PAM_PROMPT_ECHO_OFF: response[i].resp = strdup(((char **)appdata)[1]); if (response[i].resp == NULL) { freeresponse(i, response); return PAM_CONV_ERR; } response[i].resp_retcode = PAM_SUCCESS; break; case PAM_TEXT_INFO: case PAM_ERROR_MSG: response[i].resp_retcode = PAM_SUCCESS; response[i].resp = NULL; break; default: freeresponse(i, response); return PAM_CONV_ERR; } *result = response; return PAM_SUCCESS; } #endif /* * Set the effective userid of this proccess to * the given username. Root privilage is too great for * access to mailboxes. * ( assumption here, we are runing as root ) * * Return on success, exits on failures - no permission and * no such user */ static void dosetuid(struct connection *cxn) { char *username; struct passwd *pw; int facility; if (config.localuser) { username = cxn->username; } else { username = config.user; } facility = LOG_CRIT; if ((pw = getpwnam(username)) != (struct passwd *)NULL) { if (!seteuid(pw->pw_uid)) { cxn->uid = getuid(); cxn->euid = pw->pw_uid; return; } else { syslog(facility, "Unable to setuid to %s", username); exit_error(EX_NOPERM, "Unable to setuid to %s", username); } } else { syslog(facility, "No such user %s", username); exit_error(EX_NOUSER, "No such user %s", username); } } #ifndef USE_PAM static int unix_pass(struct connection *cxn) { struct passwd *pw; if ((pw = getpwnam(cxn->username)) != (struct passwd *)NULL) { if (strcmp(pw->pw_passwd, crypt(cxn->password, pw->pw_passwd))) return(FALSE); return(TRUE); } return(FALSE); } #endif static int radius_pass(struct connection *cxn) { struct rad_handle *authreq; const void *data; char *p; int i; size_t len; authreq = rad_auth_open(); for (i = 0; config.radius && config.radius[i].host; i++) { rad_add_server(authreq, config.radius[i].host, config.radius[i].port, config.radius[i].secret, config.radius[i].timeout, config.radius[i].tries); } rad_create_request(authreq, RAD_ACCESS_REQUEST); rad_put_int(authreq, RAD_NAS_PORT_TYPE, RAD_VIRTUAL); rad_put_string(authreq, RAD_USER_NAME, cxn->auth_string); rad_put_string(authreq, RAD_USER_PASSWORD, cxn->password); switch(rad_send_request(authreq)) { case RAD_ACCESS_ACCEPT: while ((i = rad_get_attr(authreq, &data, &len)) > 0) { switch(i) { case RAD_LOGIN_IP_HOST: config.proxy_addr = rad_cvt_addr(data); break; case RAD_LOGIN_TCP_PORT: config.proxy_port = htons(rad_cvt_int(data)); break; case RAD_REPLY_MESSAGE: config.proxy_name = rad_cvt_string(data, len); if ((p = strchr(config.proxy_name, ':'))) { *p++ = '\0'; config.proxy_passwd = p; } break; } } rad_close(authreq); return(TRUE); case RAD_ACCESS_REJECT: case RAD_ACCESS_CHALLENGE: /* Fall through */ break; } rad_close(authreq); return(FALSE); } /* * Check the username and password against an entry * in a db file * * Return true on success, false on failure, */ static int db_file_pass(struct connection *cxn) { DB *dp; DBT key, data; char *encpw; char *encrypted_pass; char *path; path = xmalloc(strlen(config.etcdir) + strlen(config.dbfile) + 5); sprintf(path, "%s/%s", config.etcdir, config.dbfile); if (!(dp = dbopen(path, O_RDONLY, S_IRUSR|S_IWUSR, DB_HASH, NULL))) { free(path); return(FALSE); } key.data = (u_char *)cxn->auth_string; key.size = strlen(key.data); if ((dp->get)(dp, &key, &data, 0)) { (dp->close)(dp); free(path); return(FALSE); } encrypted_pass = (char*)data.data; encrypted_pass[data.size] = '\0'; encpw = crypt(cxn->password, encrypted_pass); if (strcmp(encpw, encrypted_pass)) { (dp->close)(dp); free(path); return(FALSE); } (dp->close)(dp); free(path); return(TRUE); } #ifdef USE_PAM static int pam_pass(struct connection *cxn) { struct pam_conv pamconv; pam_handle_t *pamhandle; char *data[2]; int result; data[0] = strdup(cxn->auth_string); data[1] = strdup(cxn->password); pamconv.conv = &conversation; pamconv.appdata_ptr = data; if ((result = pam_start ("popd", NULL, &pamconv, &pamhandle)) != PAM_SUCCESS) return(FALSE); if ((result = pam_authenticate(pamhandle, 0)) != PAM_SUCCESS) return(FALSE); if ((result = pam_acct_mgmt(pamhandle, 0)) != PAM_SUCCESS) return(FALSE); /* if ((result = pam_setcred(pamhandle, PAM_ESTABLISH_CRED)) != * PAM_SUCCESS) * return(FALSE); * if ((result = pam_open_session(pamhandle, 0)) != PAM_SUCCESS) * return(FALSE); * pam_end(pamhandle, pam_close_session(pamhandle, 0)); */ pam_end(pamhandle, result); return TRUE; } #endif /* * Decide which method to use to authenticate the user, * perform the authentication and on success setuid to the user. * * This routine is the hook for authentication methods. * * Return true on success and false on failure */ static int checkpassword(struct connection *cxn) { if (config.radius && radius_pass(cxn)) return(TRUE); else if (config.virtual && db_file_pass(cxn)) return(TRUE); else if (!config.radius && !config.virtual && #ifndef USE_PAM unix_pass(cxn)) #else pam_pass(cxn)) #endif return(TRUE); return(FALSE); } /* * Retrieve the secret for a given user. * * Returns the secret on success and false on failure to * open the db file, * false if the key is not found * in the * db file. * * Exits if memory can't be allocated for the path. */ static char *getsecret(struct connection *cxn) { DB *dp; DBT key, data; char *secret; char *path; path = xmalloc(strlen(config.etcdir) + strlen(config.secrets) + 5); sprintf(path, "%s/%s", config.etcdir, config.secrets); if (!(dp = dbopen(path, O_RDONLY, S_IRUSR|S_IWUSR, DB_HASH, NULL))) { free(path); return(NULL); } key.data = (u_char *)cxn->auth_string; key.size = strlen(key.data); if ((dp->get)(dp, &key, &data, 0)) { (dp->close)(dp); free(path); return(NULL); } secret = xcalloc(data.size + 1, 1); strncpy(secret, (char*)data.data, data.size); (dp->close)(dp); free(path); return(secret); } /* * Check the md5 challenge. * * Return true if the challenge is valid, * False on falure. * * Exit if memory can't be allocated for challenge string */ static int checkchallenge(struct connection *cxn, char *ch) { char *secret, *string; MD5_CTX context; unsigned char digest[16]; if (!(secret = getsecret(cxn))) return(FALSE); if (!*secret) { free(secret); return(FALSE); } string = xmalloc(strlen(config.timestamp) + strlen(secret) + 1); strcpy(string, config.timestamp); strcat(string, secret); MD5Init(&context); MD5Update(&context, (unsigned char *)string, strlen(string)); MD5Final(digest, &context); if (!strcmp(binhex(digest, sizeof(digest)), ch)) { free(string); free(secret); dosetuid(cxn); return(TRUE); } free(string); free(secret); return(FALSE); } /* * Do the authentication * * Commands that we deal with here are: * auth * apop * user * pass * quit * * Loop continually unitil we have enough information. * Exit on quit and time out. */ int authenticate(struct connection *cxn) { char *arg1, *arg2; int counter = 0; for (;;) { switch (recvcmd(&arg1, &arg2)) { case AUTH: message(CMDINVAL); break; case APOP: if (!config.secrets) { message(CMDISABLE); break; } if (arg1 && arg2) { cxndetails(cxn, arg1, config.defaultrealm, config.maildir, config.bulletindir, config.virtual, config.hashdepth); if (checkchallenge(cxn, arg2)) return(TRUE); else freeconnection(cxn); sleep(++counter); if (counter == 4) return FALSE; message(CHALLENGEINVAL); break; } message(TOOFEWARGUMENTS); break; case USER: if (cxn->auth_string) { sendline(SEND_FLUSH, "-ERR Already selected %s", cxn->auth_string); break; } if (arg1) { if (cxndetails(cxn, arg1, config.defaultrealm, config.maildir, config.bulletindir, config.virtual, config.hashdepth)) sendline(SEND_FLUSH, "+OK enter " "password for %s", cxn->auth_string); break; } message(NEEDUSERNAME); break; case PASS: if (arg1 && cxn->auth_string) { cxn->password = xmalloc(strlen(arg1) + 1); strcpy(cxn->password, arg1); if (!config.pwcheck) { dosetuid(cxn); return(TRUE); } if (checkpassword(cxn)) { dosetuid(cxn); return(TRUE); } else freeconnection(cxn); sleep(++counter); if (counter == 4) { freeconnection(cxn); return FALSE; } message(BADPASSWORD); break; } else if (cxn->auth_string == NULL) message(NEEDUSERNAME); else if (arg1 == NULL) message(NEEDPASSWORD); break; case QUIT: return(QUIT); break; case TIMEDOUT: exit_error(EX_NOINPUT, "timed out waiting for input"); case INVALCMD: default: message(CMDINVAL); } } }