/* administration.c -- Functions needed for administration tasks * * GPLv2 only - Copyright (C) 2008 - 2010 * 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 #include /** * @{ */ #ifndef DRIVERAPIVERSION # define DRIVERAPIVERSION 2 #endif /** * @} */ #include #include #include #include #include #include #include #include #ifndef DRIVER_MODE #define DRIVER_MODE #endif #include #include "../pgsql-common.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; PGresult *dbr = NULL; ePGprepParams *qry_args = 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 // qry_args = ePGprepParamsAlloc(ctx, PREPSQL_ADMIN_AUTHUSER); ePGprepParamsAddArgument(ctx, qry_args, uname); dbr = ePGprepExec(ctx, qry_args); if( !dbr || (PQresultStatus(dbr) != PGRES_TUPLES_OK) ) { xmlNode *err_n = NULL; eurephia_log(ctx, LOG_FATAL, 0, "Could not authenticate user against the database"); err_n = ePGerrorMessageXML(ctx, dbr, LOG_FATAL, PREPSQL_ADMIN_AUTHUSER); res_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Authentication failed"); xmlFreeNode(err_n); return res_d; } if( PQntuples(dbr) == 1 ) { activated = ePGgetValue(dbr, 0, 0); deactivated = ePGgetValue(dbr, 0, 1); blid = ePGgetValue(dbr, 0, 2); dbpwd = ePGgetValue(dbr, 0, 3); uid = strdup_nullsafe(ePGgetValue(dbr, 0, 4)); if( blid != NULL ) { eurephia_log(ctx, LOG_WARNING, 0, "User account '%s' is BLACKLISTED. You have no access.", uname); PQclear(dbr); 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); PQclear(dbr); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } if( deactivated != NULL ) { eurephia_log(ctx, LOG_WARNING, 0, "User account '%s' is deactivated.", uname); PQclear(dbr); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } if( dbpwd == NULL ) { eurephia_log(ctx, LOG_WARNING, 0, "Authentication failed. DB error."); PQclear(dbr); 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); PQclear(dbr); return eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Authentication failed"); } } PQclear(dbr); // 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) qry_args = ePGprepParamsAlloc(ctx, PREPSQL_ADMIN_CHECKACL); ePGprepParamsAddArgument(ctx, qry_args, uid); ePGprepParamsAddArgumentChar(ctx, qry_args, interface); ePGprepParamsAddArgument(ctx, qry_args, req_access); dbr = ePGprepExec(ctx, qry_args); if( !dbr || (PQresultStatus(dbr) != PGRES_TUPLES_OK) ) { xmlNode *err_n = NULL; eurephia_log(ctx, LOG_FATAL, 0, "Could not check access level"); err_n = ePGerrorMessageXML(ctx, dbr, LOG_FATAL, PREPSQL_ADMIN_AUTHUSER); res_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n,"Failed to validate access level"); xmlFreeNode(err_n); return res_d; } access = ePGgetValue_bool(dbr, 0, 0); PQclear(dbr); 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."); PQclear(dbr); 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) { PGresult *dbr = NULL; ePGprepParams *qry_args = 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 = atoi_nullsafe(defaultValue(eGet_value(ctx->dbc->config, "eurephiadmin_autologout"), "10") ); qry_args = ePGprepParamsAlloc(ctx, PREPSQL_ADMIN_AUTHSESS); ePGprepParamsAddArgumentInt(ctx, qry_args, expire_time); ePGprepParamsAddArgument(ctx, qry_args, sesskey); ePGprepParamsAddArgument(ctx, qry_args, req_access); ePGprepParamsAddArgumentChar(ctx, qry_args, interface); dbr = ePGprepExec(ctx, qry_args); if( !dbr || (PQresultStatus(dbr) != PGRES_TUPLES_OK) ) { eurephia_log(ctx, LOG_FATAL, 0, "Could not validate session"); err_n = ePGerrorMessageXML(ctx, dbr, LOG_FATAL, PREPSQL_ADMIN_AUTHSESS); ret_d = eurephiaXML_ResultMsg(ctx, exmlERROR, NULL, "Session authentication failed"); xmlFreeNode(err_n); return ret_d; } valid = (ePGgetValue_bool(dbr, 0, 0) == 0); access = (ePGgetValue_bool(dbr, 0, 1) == 1); PQclear(dbr); // If still valid, update last_action if( valid && access ) { qry_args = ePGprepParamsAlloc(ctx, PREPSQL_ADMIN_ADMLOG_LASTACT); ePGprepParamsAddArgument(ctx, qry_args, sesskey); dbr = ePGprepExec(ctx, qry_args); if( !dbr || (PQresultStatus(dbr) != PGRES_COMMAND_OK) ) { eurephia_log(ctx, LOG_ERROR, 0, "Could not register session activity"); err_n = ePGerrorMessageXML(ctx, dbr, LOG_CRITICAL, PREPSQL_ADMIN_ADMLOG_LASTACT); } else { PQclear(dbr); } } else { // If not valid, register session as auto-logged out qry_args = ePGprepParamsAlloc(ctx, PREPSQL_ADMIN_ADMLOG_LOGOUT); ePGprepParamsAddArgumentInt(ctx, qry_args, (access ? 4 : 5)); ePGprepParamsAddArgument(ctx, qry_args, sesskey); if( !dbr || (PQresultStatus(dbr) != PGRES_COMMAND_OK) ) { eurephia_log(ctx, LOG_ERROR, 0, "Could not register old session as logged out"); err_n = ePGerrorMessageXML(ctx, dbr, LOG_CRITICAL, PREPSQL_ADMIN_ADMLOG_LOGOUT); } else { PQclear(dbr); } // Delete session variables qry_args = ePGprepParamsAlloc(ctx, PREPSQL_SESSIONS_DESTROY_SESS); ePGprepParamsAddArgument(ctx, qry_args, sesskey); dbr = ePGprepExec(ctx, qry_args); if( !dbr || (PQresultStatus(dbr) != PGRES_COMMAND_OK) ) { eurephia_log(ctx, LOG_ERROR, 0, "Could not delete session variables (%s))", sesskey); err_n = ePGerrorMessageXML(ctx, dbr, LOG_CRITICAL, PREPSQL_SESSIONS_DESTROY_SESS); dbr = NULL; } else if( !access ) { eurephia_log(ctx, LOG_WARNING, 0, "User account is lacking privileges"); } if( dbr != NULL ) { PQclear(dbr); } } 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) { PGresult *dbr = NULL; ePGprepParams *qry_args = NULL; xmlDoc *ret_d = 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 qry_args = ePGprepParamsAlloc(ctx, PREPSQL_ADMIN_REGLOGIN); ePGprepParamsAddArgumentInt(ctx, qry_args, uid); ePGprepParamsAddArgumentChar(ctx, qry_args, interface); ePGprepParamsAddArgument(ctx, qry_args, sesskey); dbr = ePGprepExec(ctx, qry_args); if( dbr && (PQresultStatus(dbr) == PGRES_COMMAND_OK) ) { ret_d = eurephiaXML_ResultMsg(ctx, exmlRESULT, NULL, "Session is registered as logged in"); PQclear(dbr); } else { xmlNode *err_n = NULL; eurephia_log(ctx, LOG_FATAL, 0, "Failed to register the session in the database"); err_n = ePGerrorMessageXML(ctx, dbr, LOG_CRITICAL, PREPSQL_ADMIN_REGLOGIN); ret_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Failed to register the session in the database"); xmlFreeNode(err_n); } 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) { PGresult *dbr = NULL; ePGprepParams *qry_args = 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 qry_args = ePGprepParamsAlloc(ctx, PREPSQL_ADMIN_ADMLOG_LOGOUT); ePGprepParamsAddArgument(ctx, qry_args, "3"); ePGprepParamsAddArgument(ctx, qry_args, sessionkey); dbr = ePGprepExec(ctx, qry_args); if( !dbr || (PQresultStatus(dbr) != PGRES_COMMAND_OK) ) { eurephia_log(ctx, LOG_FATAL, 0, "Failed to register the session as logged out"); err_n = ePGerrorMessageXML(ctx, dbr, LOG_CRITICAL, PREPSQL_ADMIN_ADMLOG_LOGOUT); ret_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Failed to register the session as logged out"); xmlSaveFormatFileEnc("-", ret_d, "UTF-8", 1); xmlFreeNode(err_n); return ret_d; } PQclear(dbr); // Delete session variables qry_args = ePGprepParamsAlloc(ctx, PREPSQL_SESSIONS_DESTROY_SESS); ePGprepParamsAddArgument(ctx, qry_args, sessionkey); dbr = ePGprepExec(ctx, qry_args); if( dbr && (PQresultStatus(dbr) == PGRES_COMMAND_OK) ) { ret_d = eurephiaXML_ResultMsg(ctx, exmlRESULT, NULL, "Session is logged out"); PQclear(dbr); } else { eurephia_log(ctx, LOG_ERROR, 0, "Could not delete session variables (%s))", sessionkey); err_n = ePGerrorMessageXML(ctx, dbr, LOG_CRITICAL, PREPSQL_SESSIONS_DESTROY_SESS); ret_d = eurephiaXML_ResultMsg(ctx, exmlERROR, err_n, "Could not delete session variables (%s)", sessionkey); xmlFreeNode(err_n); } 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