/* administration.c -- Functions needed for administration tasks * * GPLv2 only - Copyright (C) 2008 - 2012 * David Sommerseth * * 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; version 2 * of the License. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /** * @file authentication.c * @author David Sommerseth * @date 2008-12-03 * * @brief Functions used for authentication of administration sessions. * */ #include #include #include #include /** * @{ */ #ifndef DRIVERAPIVERSION # define DRIVERAPIVERSION 2 #endif /** * @} */ #include #include #include #include #include #include #include #include #include #ifndef DRIVER_MODE #define DRIVER_MODE #endif #include #include "../sqlite.h" #if (DRIVERAPIVERSION > 1) || defined(DOXYGEN) /* * API Version 2 functions * */ /** * Authenticate a user for the administration interface. This interface do not * require any certificate validation and is intended for administration utilities * for eurephia. The eurephia context type must be either ECTX_ADMIN_CONSOLE or * ECTX_ADMIN_WEB. * * @param ctx eurephiaCTX - context used for administration task * @param req_access String containing the requested administration access level * @param uname username of the user being authenticated * @param pwd password from the user * * @return Returns an eurephia ResultMsg XML document with the result. On fatal errors, NULL is returned */ static xmlDoc *auth_user(eurephiaCTX *ctx, const char *req_access, const char *uname, const char *pwd) { xmlDoc *res_d = NULL; xmlNode *info_n = NULL; dbresult *res = NULL; char *crpwd = NULL, *dbpwd = NULL; char *activated = NULL, *deactivated = NULL, *blid = NULL, *uid = NULL; int access = 0; char interface; DEBUG(ctx, 21, "Function call: auth_user(ctx, '%s, '%s', 'xxxxxxxx')", req_access, uname); assert(ctx != NULL); switch( ctx->context_type ) { case ECTX_ADMIN_CONSOLE: interface = 'C'; break; case ECTX_ADMIN_WEB: interface = 'W'; break; default: eurephia_log(ctx, LOG_ERROR, 0, "Wrong eurephia context type (0x%04x)", ctx->context_type); return NULL; } if( (strlen_nullsafe(uname) < 4) || (strlen_nullsafe(pwd) < 4) ) { eurephia_log(ctx, LOG_WARNING, 0, "Username and/or password is either null or less than 4 bytes"); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Username or password is too short"); } // // Authenticate user and password // res = sqlite_query(ctx, "SELECT activated, deactivated, bl.blid, " " password, uid " " FROM openvpn_users ou" " LEFT JOIN openvpn_blacklist bl USING (username)" " WHERE ou.username = '%q'", uname); if( sqlite_query_status(res) != dbSUCCESS ) { xmlNode *err_n = NULL; eurephia_log(ctx, LOG_FATAL, 0, "Could not authenticate user against the database"); err_n = sqlite_log_error_xml(ctx, res); res_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Authentication failed"); xmlFreeNode(err_n); sqlite_free_results(res); return res_d; } if( sqlite_get_numtuples(res) == 1 ) { activated = sqlite_get_value(res, 0, 0); deactivated = sqlite_get_value(res, 0, 1); blid = sqlite_get_value(res, 0, 2); dbpwd = sqlite_get_value(res, 0, 3); uid = strdup_nullsafe(sqlite_get_value(res, 0, 4)); if( blid != NULL ) { eurephia_log(ctx, LOG_WARNING, 0, "User account '%s' is BLACKLISTED. You have no access.", uname); sqlite_free_results(res); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } if( activated == NULL ) { eurephia_log(ctx, LOG_WARNING, 0, "User account '%s' is not yet activated.", uname); sqlite_free_results(res); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } if( deactivated != NULL ) { eurephia_log(ctx, LOG_WARNING, 0, "User account '%s' is deactivated.", uname); sqlite_free_results(res); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } if( dbpwd == NULL ) { eurephia_log(ctx, LOG_WARNING, 0, "Authentication failed. DB error."); sqlite_free_results(res); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } else { int pwdok = 0; // Verify the password crpwd = eurephia_pwd_crypt(ctx, pwd, dbpwd); pwdok = ((crpwd != NULL) && (strcmp(crpwd, dbpwd) == 0) ? 1 : 0); memset(crpwd, 0, strlen_nullsafe(crpwd)); memset(dbpwd, 0, strlen_nullsafe(dbpwd)); free_nullsafe(ctx, crpwd); if( pwdok == 0 ) { eurephia_log(ctx, LOG_WARNING, 0, "Authentication failed."); sleep(2); sqlite_free_results(res); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } } sqlite_free_results(res); // Check if access level is granted // (SQLite do not handle advanced joins so well, so we need to // do this check with an extra query) res = sqlite_query(ctx, "SELECT (count(*) = 1) AS access " " FROM eurephia_adminaccess" " WHERE uid = '%q' AND interface = '%c' AND access = '%q'", uid, interface, req_access); if( sqlite_query_status(res) != dbSUCCESS ) { xmlNode *err_n = NULL; eurephia_log(ctx, LOG_FATAL, 0, "Could not check access level"); err_n = sqlite_log_error_xml(ctx, res); res_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n,"Failed to validate access level"); xmlFreeNode(err_n); sqlite_free_results(res); return res_d; } access = atoi_nullsafe(sqlite_get_value(res, 0, 0)); sqlite_free_results(res); if( access == 0 ) { eurephia_log(ctx, LOG_WARNING, 0, "User account '%s' is lacking privileges for this operation", uname); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } } else { eurephia_log(ctx, LOG_WARNING, 0, "Authentication failed. No unique records found."); sqlite_free_results(res); sleep(2); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } // If we reach this place, authentication was successful. Return users uid info_n = xmlNewNode(NULL, (xmlChar *) "UserAccount"); assert( info_n != NULL ); xmlNewProp(info_n, (xmlChar *) "uid", (xmlChar *) uid); res_d = eurephiaXML_ResultMsg(ctx, exmlRESULT, info_n, "Successful authentication"); xmlFreeNode(info_n); free_nullsafe(ctx, uid); return res_d; } /** * Validates a session key, to see if it still is valid (not auto-logged out or invalid session key) * and to check if they have access to a different access level. The eurephia context type must be * either ECTX_ADMIN_CONSOLE or ECTX_ADMIN_WEB. * * @param ctx eurephiaCTX * @param sesskey String containing the session key to validate * @param req_access String containing the required administration access level * * @return Returns an eurephia ResultMsg XML document with the result. On fatal errors, NULL is returned */ static xmlDoc *auth_session(eurephiaCTX *ctx, const char *sesskey, const char *req_access) { dbresult *res = NULL; int valid = 0, access = 0, expire_time = 0; char interface; xmlDoc *ret_d = NULL; xmlNode *err_n = NULL; DEBUG(ctx, 21, "Function call: auth_session(ctx, '%s, '%s')", sesskey, req_access); assert( (ctx != NULL) && (sesskey != NULL) ); switch( ctx->context_type ) { case ECTX_ADMIN_CONSOLE: interface = 'C'; break; case ECTX_ADMIN_WEB: interface = 'W'; break; default: eurephia_log(ctx, LOG_ERROR, 0, "Wrong eurephia context type (0x%04x)", ctx->context_type); return NULL; } // Check if the session is still valid (not expired) and that this session are allowed to access // the requested access level. expire_time = (60 * atoi_nullsafe(defaultValue(eGet_value(ctx->dbc->config, "eurephiadmin_autologout"), "10") ) ); res = sqlite_query(ctx, "SELECT (strftime('%%s',CURRENT_TIMESTAMP)-strftime('%%s',last_action)) > %i AS exp," " (access IS NOT NULL) AS access" " FROM eurephia_adminlog" " LEFT JOIN eurephia_adminaccess USING(uid,interface)" " WHERE status IN (1,2)" " AND sessionkey = '%q'" " AND access = '%q'" " AND interface = '%c'", expire_time, sesskey, req_access, interface); if( sqlite_query_status(res) != dbSUCCESS ) { eurephia_log(ctx, LOG_FATAL, 0, "Could not validate session"); err_n = sqlite_log_error_xml(ctx, res); ret_d = eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Session authentication failed"); sqlite_free_results(res); xmlFreeNode(err_n); return ret_d; } valid = (atoi_nullsafe(sqlite_get_value(res, 0, 0)) == 0); access = (atoi_nullsafe(sqlite_get_value(res, 0, 1)) == 1); sqlite_free_results(res); // If still valid, update last_action if( valid && access ) { res = sqlite_query(ctx, "UPDATE eurephia_adminlog" " SET last_action = CURRENT_TIMESTAMP, status = 2" " WHERE sessionkey = '%q'", sesskey); if( sqlite_query_status(res) != dbSUCCESS ) { eurephia_log(ctx, LOG_ERROR, 0, "Could not register session activity"); err_n = sqlite_log_error_xml(ctx, res); } sqlite_free_results(res); } else { // If not valid, register session as auto-logged out res = sqlite_query(ctx, "UPDATE eurephia_adminlog" " SET logout = CURRENT_TIMESTAMP, status = %i" " WHERE sessionkey = '%q'", (access ? 4 : 5), sesskey); if( sqlite_query_status(res) != dbSUCCESS ) { eurephia_log(ctx, LOG_ERROR, 0, "Could not register old session as logged out"); err_n = sqlite_log_error_xml(ctx, res); } sqlite_free_results(res); // Delete session variables res = sqlite_query(ctx, "DELETE FROM openvpn_sessions WHERE sessionkey = '%q'", sesskey); if( sqlite_query_status(res) != dbSUCCESS ) { eurephia_log(ctx, LOG_ERROR, 0, "Could not delete session variables (%s))", sesskey); sqlite_log_error(ctx, res); } else if( !access ) { eurephia_log(ctx, LOG_WARNING, 0, "User account is lacking privileges"); } sqlite_free_results(res); } if (valid && access) { ret_d = eurephiaXML_ResultMsg(ctx, exmlRESULT, err_n, "Session authenticated"); } else { ret_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Session authentication failed"); } if( err_n != NULL ) { xmlFreeNode(err_n); } return ret_d; } /** * Registers the user as logged in after a successful authentication. The user must * be registered as logged in to have a valid session. * * @param ctx eurephiaCTX * @param uid Numeric value if the user ID the session belongs to * @param sesskey String containing the session key * * @return Returns an eurephia ResultMsg XML document with the result. On fatal errors, NULL is returned */ static xmlDoc *register_login(eurephiaCTX *ctx, const int uid, const char *sesskey) { xmlDoc *ret_d = NULL; dbresult *res = NULL; char interface; DEBUG(ctx, 21, "Function call: register_login(ctx, %i, '%s')", uid, sesskey); assert( ctx != NULL ); if( (sesskey == NULL) || (uid < 1) ) { return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Invalid data for login registration"); } switch( ctx->context_type ) { case ECTX_ADMIN_CONSOLE: interface = 'C'; break; case ECTX_ADMIN_WEB: interface = 'W'; break; default: eurephia_log(ctx, LOG_ERROR, 0, "Wrong eurephia context type (0x%04x)", ctx->context_type); return NULL; } // Register login into eurephia_adminlog ... uid, login, interface, sessionkey res = sqlite_query(ctx, "INSERT INTO eurephia_adminlog " " (uid, interface, status, login, last_action, sessionkey) " "VALUES ('%i','%c',1,CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '%q')", uid, interface, sesskey); if( sqlite_query_status(res) == dbSUCCESS ) { ret_d = eurephiaXML_ResultMsg(ctx, exmlRESULT, NULL, "Session is registered as logged in"); } else { xmlNode *err_n = NULL; eurephia_log(ctx, LOG_FATAL, 0, "Failed to register the session in the database"); err_n = sqlite_log_error_xml(ctx, res); ret_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Failed to register the session in the database"); xmlFreeNode(err_n); } sqlite_free_results(res); return ret_d; } /** * Registers a session as logged out. This will require the user to do a new authentication * on next access via the administration interface * * @param ctx eurephiaCTX * @param sessionkey String containing the session key * * @return Returns an eurephia ResultMsg XML document with the result. On fatal errors, NULL is returned */ static xmlDoc *register_logout(eurephiaCTX *ctx, const char *sessionkey) { dbresult *res = NULL; xmlDoc *ret_d = NULL; xmlNode *err_n = NULL; DEBUG(ctx, 21, "Function call: register_logout(ctx, '%s')", sessionkey); assert((ctx != NULL) && (sessionkey != NULL)); if( (ctx->context_type != ECTX_ADMIN_CONSOLE) && (ctx->context_type != ECTX_ADMIN_WEB) ) { eurephia_log(ctx, LOG_CRITICAL, 0, "eurephia admin function call attempted with wrong context type"); return NULL; } // Update session as logged out res = sqlite_query(ctx, "UPDATE eurephia_adminlog " " SET logout = CURRENT_TIMESTAMP, status = 3" " WHERE sessionkey = '%q'", sessionkey); if( (sqlite_query_status(res) != dbSUCCESS) || (sqlite_get_affected_rows(res) == 0) ) { eurephia_log(ctx, LOG_FATAL, 0, "Failed to register the session as logged out (updated %i rows)", sqlite_get_affected_rows(res)); if( sqlite_query_status(res) == dbERROR ) { err_n = sqlite_log_error_xml(ctx, res); } ret_d = eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Failed to register the session as logged out"); xmlFreeNode(err_n); goto exit; } sqlite_free_results(res); // Delete session variables res = sqlite_query(ctx, "DELETE FROM openvpn_sessions WHERE sessionkey = '%q'", sessionkey); if( (sqlite_query_status(res) == dbSUCCESS) && (sqlite_get_affected_rows(res) > 0) ) { ret_d = eurephiaXML_ResultMsg(ctx, exmlRESULT, NULL, "Session is logged out"); } else { eurephia_log(ctx, LOG_ERROR, 0, "Could not delete session variables (%s))", sessionkey); if( sqlite_query_status(res) == dbERROR ) { err_n = sqlite_log_error_xml(ctx, res); } ret_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Could not delete session variables (%s)", sessionkey); xmlFreeNode(err_n); } exit: sqlite_free_results(res); return ret_d; } /** * @copydoc eDBadminAuthenticate() */ xmlDoc *eDBadminAuthenticate(eurephiaCTX *ctx, xmlDoc *qryxml) { xmlDoc *res_d = NULL; xmlNode *qry_n = NULL; char *mode = NULL; int type = 0; DEBUG(ctx, 20, "Function call: eDBadminAuthenticate(ctx, xmlDoc)"); assert( (ctx != NULL) && (qryxml != NULL) ); if( (ctx->context_type != ECTX_ADMIN_CONSOLE) && (ctx->context_type != ECTX_ADMIN_WEB) ) { eurephia_log(ctx, LOG_CRITICAL, 0, "eurephia admin function call attempted with wrong context type"); return NULL; } qry_n = eurephiaXML_getRoot(ctx, qryxml, "Authenticate", 1); if( qry_n != NULL ) { type = 1; goto accept; } qry_n = eurephiaXML_getRoot(ctx, qryxml, "Register", 1); if( qry_n != NULL ) { type = 2; goto accept; } eurephia_log(ctx, LOG_ERROR, 0, "Could not find a valid XML request for eDBadminAuthenticate()"); return NULL; accept: mode = xmlGetAttrValue(qry_n->properties, "mode"); if( mode == NULL ) { eurephia_log(ctx, LOG_ERROR, 0, "Invalid authentication request"); return NULL; } switch( type ) { case 1: // Authenticate tag if( strcmp(mode, "user") == 0 ) { // const char *req_access, const char *uname, const char *pwd const char *reqacc = NULL, *uname = NULL, *pwd = NULL; uname = xmlGetNodeContent(qry_n, "username"); pwd = xmlGetNodeContent(qry_n, "password"); reqacc = xmlGetNodeContent(qry_n, "accesslevel"); res_d = auth_user(ctx, reqacc, uname, pwd); } else if ( strcmp(mode, "session") == 0 ) { const char *sesskey = NULL, *reqacc = NULL; sesskey = xmlGetNodeContent(qry_n, "sessionkey"); reqacc = xmlGetNodeContent(qry_n, "accesslevel"); res_d = auth_session(ctx, sesskey, reqacc); } break; case 2: // Register tag if( strcmp(mode, "login") == 0 ) { const char *sesskey = NULL; unsigned int uid = 0; uid = atoi_nullsafe(xmlGetAttrValue(qry_n->properties, "uid")); sesskey = xmlExtractContent(qry_n); res_d = register_login(ctx, uid, sesskey); } else if( strcmp(mode, "logout") == 0 ) { const char *sesskey = NULL; sesskey = xmlExtractContent(qry_n); res_d = register_logout(ctx, sesskey); } break; default: eurephia_log(ctx, LOG_FATAL, 0, "The unthinkable has just happened (type %i)", type); res_d = NULL; break; } return res_d; } #endif