/* Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * \file authsql.c * \brief implements SQL authentication. Prototypes of these functions * can be found in auth.h . * \author IC&S (http://www.ic-s.nl) */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "auth.h" #include "db.h" #include "list.h" #include "debug.h" #include "dbmd5.h" #include "dbmail.h" #include "misc.h" #include #include #include #include #include #include #ifdef HAVE_CRYPT_H #include #endif /** * used for query strings */ #define AUTH_QUERY_SIZE 1024 static char __auth_query_data[AUTH_QUERY_SIZE]; /* string to be returned by auth_getencryption() */ #define _DESCSTRLEN 50 static char __auth_encryption_desc_string[_DESCSTRLEN]; /** * \brief perform a authentication query * \param thequery the query * \return * - -1 on error * - 0 otherwise */ static int __auth_query(const char *thequery); static u64_t __auth_insert_result(const char *sequence_identifier); int auth_connect() { /* this function is only called after a connection has been made * if, in the future this is not the case, db.h should export a * function that enables checking for the database connection */ return 0; } int auth_disconnect() { return 0; } int auth_user_exists(const char *username, u64_t * user_idnr) { const char *query_result; char *escaped_username; assert(user_idnr != NULL); *user_idnr = 0; if (!username) { trace(TRACE_ERROR, "%s,%s: got NULL as username", __FILE__, __func__); return 0; } if (db_escape_string(&escaped_username, username)) { trace(TRACE_ERROR, "%s,%s: error escaping username.", __FILE__, __func__); return -1; } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT user_idnr FROM dbmail_users WHERE userid='%s'", escaped_username); free(escaped_username); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not execute query", __FILE__, __func__); return -1; } if (db_num_rows() == 0) { db_free_result(); return 0; } query_result = db_get_result(0, 0); *user_idnr = (query_result) ? strtoull(query_result, 0, 10) : 0; db_free_result(); return 1; } int auth_get_known_users(struct list *users) { u64_t i; const char *query_result; if (!users) { trace(TRACE_ERROR, "%s,%s: got a NULL pointer as argument", __FILE__, __func__); return -2; } list_init(users); /* do a inverted (DESC) query because adding the names to the * final list inverts again */ snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT userid FROM dbmail_users ORDER BY userid DESC"); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not retrieve user list", __FILE__, __func__); return -1; } if (db_num_rows() > 0) { for (i = 0; i < (unsigned) db_num_rows(); i++) { query_result = db_get_result(i, 0); if (!list_nodeadd (users, query_result, strlen(query_result) + 1)) { list_freelist(&users->start); return -2; } } } db_free_result(); return 0; } int auth_getclientid(u64_t user_idnr, u64_t * client_idnr) { const char *query_result; assert(client_idnr != NULL); *client_idnr = 0; snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT client_idnr FROM dbmail_users WHERE user_idnr = '%llu'", user_idnr); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not retrieve client id for user [%llu]\n", __FILE__, __func__, user_idnr); return -1; } if (db_num_rows() == 0) { db_free_result(); return 1; } query_result = db_get_result(0, 0); *client_idnr = (query_result) ? strtoull(query_result, 0, 10) : 0; db_free_result(); return 1; } int auth_getmaxmailsize(u64_t user_idnr, u64_t * maxmail_size) { const char *query_result; assert(maxmail_size != NULL); *maxmail_size = 0; snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT maxmail_size FROM dbmail_users WHERE user_idnr = '%llu'", user_idnr); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not retrieve client id for user [%llu]", __FILE__, __func__, user_idnr); return -1; } if (db_num_rows() == 0) { db_free_result(); return 0; } query_result = db_get_result(0, 0); if (query_result) *maxmail_size = strtoull(query_result, NULL, 10); else return -1; db_free_result(); return 1; } char *auth_getencryption(u64_t user_idnr) { const char *query_result; __auth_encryption_desc_string[0] = '\0'; if (user_idnr == 0) { /* assume another function returned an error code (-1) * or this user does not exist (0) */ trace(TRACE_ERROR, "%s,%s: got (%lld) as userid", __FILE__, __func__, user_idnr); return __auth_encryption_desc_string; /* return empty */ } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT encryption_type FROM dbmail_users WHERE user_idnr = '%llu'", user_idnr); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not retrieve encryption type for user [%llu]", __FILE__, __func__, user_idnr); return __auth_encryption_desc_string; /* return empty */ } if (db_num_rows() == 0) { db_free_result(); return __auth_encryption_desc_string; /* return empty */ } query_result = db_get_result(0, 0); strncpy(__auth_encryption_desc_string, query_result, _DESCSTRLEN); db_free_result(); return __auth_encryption_desc_string; } int auth_check_user(const char *username, struct list *userids, int checks) { int occurences = 0; int r; void *saveres; u64_t counter; unsigned num_rows; char *escaped_username; const char *query_result; trace(TRACE_DEBUG, "%s,%s: checking user [%s] in alias table", __FILE__, __func__, username); saveres = db_get_result_set(); db_set_result_set(NULL); if (checks > MAX_CHECKS_DEPTH) { trace(TRACE_ERROR, "%s,%s: maximum checking depth reached, " "there probably is a loop in your alias table", __FILE__, __func__); return -1; } if (db_escape_string(&escaped_username, username)) { trace(TRACE_ERROR, "%s,%s: error escaping username.", __FILE__, __func__); return -1; } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT deliver_to FROM dbmail_aliases WHERE " "lower(alias) = lower('%s')", escaped_username); free(escaped_username); trace(TRACE_DEBUG, "%s,%s: checks [%d]", __FILE__, __func__, checks); if (__auth_query(__auth_query_data) == -1) { /* copy the old result set */ db_set_result_set(saveres); return 0; } num_rows = db_num_rows(); if (num_rows < 1) { if (checks > 0) { /* found the last one, this is the deliver to * but checks needs to be bigger then 0 because * else it could be the first query failure */ list_nodeadd(userids, username, strlen(username) + 1); trace(TRACE_DEBUG, "%s,%s: adding [%s] to deliver_to address", __FILE__, __func__, username); db_free_result(); db_set_result_set(saveres); return 1; } else { trace(TRACE_DEBUG, "%s,%s: user %s not in aliases table", __FILE__, __func__, username); db_free_result(); db_set_result_set(saveres); return 0; } } trace(TRACE_DEBUG, "%s,%s: into checking loop", __FILE__, __func__); if (num_rows > 0) { for (counter = 0; counter < num_rows; counter++) { /* do a recursive search for deliver_to */ query_result = db_get_result(counter, 0); trace(TRACE_DEBUG, "%s,%s: checking user %s to %s", __FILE__, __func__, username, query_result); r = auth_check_user(query_result, userids, (checks < 0) ? 1 : checks + 1); if (r < 0) { /* loop detected */ db_free_result(); db_set_result_set(saveres); if (checks > 0) return -1; /* still in recursive call */ if (userids->start) { list_freelist(&userids->start); userids->total_nodes = 0; } return 0; /* report to calling routine: no results */ } occurences += r; } } db_free_result(); db_set_result_set(saveres); return occurences; } int auth_check_user_ext(const char *username, struct list *userids, struct list *fwds, int checks) { int occurences = 0; void *saveres; u64_t counter; char *escaped_username; const char *query_result; char *endptr; u64_t id; unsigned num_rows; if (checks > 20) { trace(TRACE_ERROR,"%s,%s: too many checks. Possible loop in recursion.", __FILE__, __func__); return 0; } if (strlen(username) < 1) return 0; saveres = db_get_result_set(); db_set_result_set(NULL); trace(TRACE_DEBUG, "%s,%s: checking user [%s] in alias table", __FILE__, __func__, username); if (db_escape_string(&escaped_username, username)) { trace(TRACE_ERROR, "%s,%s: error escaping username.", __FILE__, __func__); return -1; } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT deliver_to FROM dbmail_aliases " "WHERE lower(alias) = lower('%s') " "AND lower(alias) <> lower(deliver_to)", escaped_username); free(escaped_username); if (__auth_query(__auth_query_data) == -1) { db_set_result_set(saveres); return 0; } num_rows = db_num_rows(); if (num_rows == 0) { if (checks > 0) { /* found the last one, this is the deliver to * but checks needs to be bigger then 0 because * else it could be the first query failure */ id = strtoull(username, &endptr, 10); if (*endptr == 0) list_nodeadd(userids, &id, sizeof(id)); /* numeric deliver-to --> this is a userid */ else list_nodeadd(fwds, username, strlen(username) + 1); trace(TRACE_DEBUG, "%s,%s: adding [%s] to deliver_to address", __FILE__, __func__, username); db_free_result(); db_set_result_set(saveres); return 1; } else { trace(TRACE_DEBUG, "%s,%s: user %s not in aliases table", __FILE__, __func__, username); db_free_result(); db_set_result_set(saveres); return 0; } } trace(TRACE_DEBUG, "%s,%s: into checking loop", __FILE__, __func__); if (num_rows > 0) { for (counter = 0; counter < num_rows; counter++) { /* do a recursive search for deliver_to */ query_result = db_get_result(counter, 0); trace(TRACE_DEBUG, "%s,%s: checking user %s to %s", __FILE__, __func__, username, query_result); occurences += auth_check_user_ext(query_result, userids, fwds, checks+1); } } db_free_result(); db_set_result_set(saveres); return occurences; } int __auth_query(const char *thequery) { /* start using authentication result */ if (db_query(thequery) < 0) { trace(TRACE_ERROR, "%s,%s: error executing query", __FILE__, __func__); return -1; } return 0; } int auth_adduser(const char *username, const char *password, const char *enctype, u64_t clientid, u64_t maxmail, u64_t * user_idnr) { char *escaped_password; char *escaped_username; assert(user_idnr != NULL); *user_idnr = 0; if (db_escape_string(&escaped_username, username)) { trace(TRACE_ERROR, "%s,%s: error escaping username.", __FILE__, __func__); return -1; } if (db_escape_string(&escaped_password, password)) { trace(TRACE_ERROR, "%s,%s: error escaping password.", __FILE__, __func__); free(escaped_username); return -1; } #ifdef _DBAUTH_STRICT_USER_CHECK /* first check to see if this user already exists */ snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT * FROM dbmail_users WHERE userid = '%s'", escaped_username); if (__auth_query(__auth_query_data) == -1) { /* query failed */ trace(TRACE_ERROR, "%s,%s: query failed", __FILE__, __func__); free(escaped_username); free(escaped_password); return -1; } if (db_num_rows() > 0) { /* this username already exists */ trace(TRACE_ERROR, "%s,%s: user already exists", __FILE__, __func__); free(escaped_username); free(escaped_password); db_free_result(); return -1; } db_free_result(); #endif if (strlen(password) >= AUTH_QUERY_SIZE) { trace(TRACE_ERROR, "%s,%s: password length is insane", __FILE__, __func__); free(escaped_username); free(escaped_password); return -1; } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "INSERT INTO dbmail_users " "(userid,passwd,client_idnr,maxmail_size,encryption_type, last_login) VALUES " "('%s','%s',%llu,'%llu','%s', CURRENT_TIMESTAMP)", escaped_username, escaped_password, clientid, maxmail, enctype ? enctype : ""); free(escaped_username); free(escaped_password); if (__auth_query(__auth_query_data) == -1) { /* query failed */ trace(TRACE_ERROR, "%s,%s: query for adding user failed", __FILE__, __func__); return -1; } *user_idnr = __auth_insert_result("user_idnr"); return 1; } int auth_delete_user(const char *username) { char *escaped_username; if (db_escape_string(&escaped_username, username)) { trace(TRACE_ERROR, "%s,%s: error escaping username.", __FILE__, __func__); return -1; } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "DELETE FROM dbmail_users WHERE userid = '%s'", escaped_username); free(escaped_username); if (__auth_query(__auth_query_data) == -1) { /* query failed */ trace(TRACE_ERROR, "%s,%s: query for removing user failed", __FILE__, __func__); return -1; } return 0; } int auth_change_username(u64_t user_idnr, const char *new_name) { char *escaped_new_name; if (db_escape_string(&escaped_new_name, new_name)) { trace(TRACE_ERROR, "%s,%s: error escaping new_name.", __FILE__, __func__); return -1; } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "UPDATE dbmail_users SET userid = '%s' WHERE user_idnr='%llu'", escaped_new_name, user_idnr); free(escaped_new_name); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not change name for user [%llu]", __FILE__, __func__, user_idnr); return -1; } return 0; } int auth_change_password(u64_t user_idnr, const char *new_pass, const char *enctype) { char *escaped_new_pass; if (db_escape_string(&escaped_new_pass, new_pass)) { trace(TRACE_ERROR, "%s,%s: error escaping new_pass.", __FILE__, __func__); return -1; } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "UPDATE dbmail_users SET passwd = '%s', encryption_type = '%s' " " WHERE user_idnr='%llu'", escaped_new_pass, enctype ? enctype : "", user_idnr); free(escaped_new_pass); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not change passwd for user [%llu]", __FILE__, __func__, user_idnr); return -1; } return 0; } int auth_change_clientid(u64_t user_idnr, u64_t new_cid) { snprintf(__auth_query_data, AUTH_QUERY_SIZE, "UPDATE dbmail_users SET client_idnr = '%llu' " "WHERE user_idnr='%llu'", new_cid, user_idnr); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not change client id for user [%llu]", __FILE__, __func__, user_idnr); return -1; } return 0; } int auth_change_mailboxsize(u64_t user_idnr, u64_t new_size) { snprintf(__auth_query_data, AUTH_QUERY_SIZE, "UPDATE dbmail_users SET maxmail_size = '%llu' " "WHERE user_idnr = '%llu'", new_size, user_idnr); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not change maxmailsize for user [%llu]", __FILE__, __func__, user_idnr); return -1; } return 0; } int auth_validate(char *username, char *password, u64_t * user_idnr) { const char *query_result; int is_validated = 0; timestring_t timestring; char salt[13]; char cryptres[35]; char *escaped_username; assert(user_idnr != NULL); *user_idnr = 0; if (username == NULL || password == NULL) { trace(TRACE_DEBUG, "%s,%s: username or password is NULL", __FILE__, __func__); return 0; } create_current_timestring(×tring); /* the shared mailbox user should not log in! */ if (strcmp(username, SHARED_MAILBOX_USERNAME) == 0) return 0; if (db_escape_string(&escaped_username, username)) { trace(TRACE_ERROR, "%s,%s: error escaping username.", __FILE__, __func__); return -1; } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT user_idnr, passwd, encryption_type FROM dbmail_users " "WHERE userid = '%s'", escaped_username); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: could not select user information", __FILE__, __func__); dm_free(escaped_username); return -1; } dm_free(escaped_username); if (db_num_rows() == 0) { db_free_result(); return 0; } /* get encryption type */ query_result = db_get_result(0, 2); if (!query_result || strcasecmp(query_result, "") == 0) { trace(TRACE_DEBUG, "%s,%s: validating using plaintext passwords", __FILE__, __func__); /* get password from database */ query_result = db_get_result(0, 1); is_validated = (strcmp(query_result, password) == 0) ? 1 : 0; } else if (strcasecmp(query_result, "crypt") == 0) { trace(TRACE_DEBUG, "%s,%s: validating using crypt() encryption", __FILE__, __func__); query_result = db_get_result(0, 1); is_validated = (strcmp((const char *) crypt(password, query_result), /* Flawfinder: ignore */ query_result) == 0) ? 1 : 0; } else if (strcasecmp(query_result, "md5") == 0) { /* get password */ query_result = db_get_result(0, 1); if (strncmp(query_result, "$1$", 3)) { trace(TRACE_DEBUG, "%s,%s: validating using MD5 digest comparison", __FILE__, __func__); /* redundant statement: query_result = db_get_result(0, 1); */ is_validated = (strncmp(makemd5(password), query_result, 32) == 0) ? 1 : 0; } else { trace(TRACE_DEBUG, "%s, %s: validating using MD5 hash comparison", __FILE__, __func__); strncpy(salt, query_result, 12); strncpy(cryptres, (char *) crypt(password, query_result), 34); /* Flawfinder: ignore */ trace(TRACE_DEBUG, "%s,%s: salt : %s", __FILE__, __func__, salt); trace(TRACE_DEBUG, "%s,%s: hash : %s", __FILE__, __func__, query_result); trace(TRACE_DEBUG, "%s,%s: crypt(): %s", __FILE__, __func__, cryptres); is_validated = (strncmp(query_result, cryptres, 34) == 0) ? 1 : 0; } } else if (strcasecmp(query_result, "md5sum") == 0) { trace(TRACE_DEBUG, "%s,%s: validating using MD5 digest comparison", __FILE__, __func__); query_result = db_get_result(0, 1); is_validated = (strncmp(makemd5(password), query_result, 32) == 0) ? 1 : 0; } if (is_validated) { query_result = db_get_result(0, 0); *user_idnr = (query_result) ? strtoull(query_result, NULL, 10) : 0; db_free_result(); /* log login in the dbase */ snprintf(__auth_query_data, AUTH_QUERY_SIZE, "UPDATE dbmail_users SET last_login = '%s' " "WHERE user_idnr = '%llu'", timestring, *user_idnr); if (__auth_query(__auth_query_data) == -1) trace(TRACE_ERROR, "%s,%s: could not update user login time", __FILE__, __func__); } else { db_free_result(); } return (is_validated ? 1 : 0); } u64_t auth_md5_validate(char *username, unsigned char *md5_apop_he, char *apop_stamp) { /* returns useridnr on OK, 0 on validation failed, -1 on error */ char *checkstring; unsigned char *md5_apop_we; u64_t user_idnr; const char *query_result; timestring_t timestring; char *escaped_username; create_current_timestring(×tring); if (db_escape_string(&escaped_username, username)) { trace(TRACE_ERROR, "%s,%s: error escaping username.", __FILE__, __func__); return -1; } snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT passwd,user_idnr FROM dbmail_users WHERE " "userid = '%s'", escaped_username); free(escaped_username); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: error calling __auth_query()", __FILE__, __func__); return -1; } if (db_num_rows() < 1) { /* no such user found */ db_free_result(); return 0; } /* now authenticate using MD5 hash comparisation */ query_result = db_get_result(0, 0); /* value holds the password */ trace(TRACE_DEBUG, "%s,%s: apop_stamp=[%s], userpw=[%s]", __FILE__, __func__, apop_stamp, query_result); memtst((checkstring = (char *) dm_malloc(strlen(apop_stamp) + strlen(query_result) + 2)) == NULL); snprintf(checkstring, strlen(apop_stamp) + strlen(query_result) + 2, "%s%s", apop_stamp, query_result); md5_apop_we = makemd5(checkstring); trace(TRACE_DEBUG, "%s,%s: checkstring for md5 [%s] -> result [%s]", __FILE__, __func__, checkstring, md5_apop_we); trace(TRACE_DEBUG, "%s,%s: validating md5_apop_we=[%s] md5_apop_he=[%s]", __FILE__, __func__, md5_apop_we, md5_apop_he); if (strcmp(md5_apop_he, makemd5(checkstring)) == 0) { trace(TRACE_MESSAGE, "%s,%s: user [%s] is validated using APOP", __FILE__, __func__, username); /* get user idnr */ query_result = db_get_result(0, 1); user_idnr = (query_result) ? strtoull(query_result, NULL, 10) : 0; db_free_result(); dm_free(checkstring); /* log login in the dbase */ snprintf(__auth_query_data, AUTH_QUERY_SIZE, "UPDATE dbmail_users SET last_login = '%s' " "WHERE user_idnr = '%llu'", timestring, user_idnr); if (__auth_query(__auth_query_data) == -1) trace(TRACE_ERROR, "%s,%s: could not update user login time", __FILE__, __func__); return user_idnr; } trace(TRACE_MESSAGE, "%s,%s: user [%s] could not be validated", __FILE__, __func__, username); db_free_result(); dm_free(checkstring); return 0; } char *auth_get_userid(u64_t user_idnr) { const char *query_result; char *returnid = NULL; snprintf(__auth_query_data, AUTH_QUERY_SIZE, "SELECT userid FROM dbmail_users WHERE user_idnr = '%llu'", user_idnr); if (__auth_query(__auth_query_data) == -1) { trace(TRACE_ERROR, "%s,%s: query failed", __FILE__, __func__); return 0; } if (db_num_rows() < 1) { trace(TRACE_DEBUG, "%s,%s: user has no username?", __FILE__, __func__); db_free_result(); return 0; } query_result = db_get_result(0, 0); if (query_result) { trace(TRACE_DEBUG, "%s,%s: query_result = %s", __FILE__, __func__, query_result); if (! (returnid = (char *) dm_malloc(strlen(query_result) + 1))) { trace(TRACE_ERROR, "%s,%s: out of memory", __FILE__, __func__); db_free_result(); return NULL; } strncpy(returnid, query_result, strlen(query_result) + 1); } db_free_result(); trace(TRACE_DEBUG, "%s,%s: returning %s as returnid", __FILE__, __func__, returnid); return returnid; } u64_t __auth_insert_result(const char *sequence_identifier) { u64_t insert_result; insert_result = db_insert_result(sequence_identifier); return insert_result; }