diff options
-rw-r--r-- | database/postgresql/administration/authentication.c | 530 | ||||
-rw-r--r-- | database/postgresql/edb-pgsql.c | 2 |
2 files changed, 531 insertions, 1 deletions
diff --git a/database/postgresql/administration/authentication.c b/database/postgresql/administration/authentication.c new file mode 100644 index 0000000..24b90a3 --- /dev/null +++ b/database/postgresql/administration/authentication.c @@ -0,0 +1,530 @@ +/* administration.c -- Functions needed for administration tasks + * + * GPLv2 only - Copyright (C) 2008 - 2010 + * David Sommerseth <dazo@users.sourceforge.net> + * + * 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 <dazo@users.sourceforge.net> + * @date 2008-12-03 + * + * @brief Functions used for authentication of administration sessions. + * + */ + +#include <string.h> +#include <unistd.h> +#include <assert.h> +#include <libxml/tree.h> +#include <libpq-fe.h> + +/** + * @{ + */ +#ifndef DRIVERAPIVERSION +# define DRIVERAPIVERSION 2 +#endif +/** + * @} + */ + +#include <eurephia_nullsafe.h> +#include <eurephia_context.h> +#include <eurephia_log.h> +#include <eurephia_xml.h> +#include <eurephia_values.h> +#include <eurephiadb_session_struct.h> +#include <eurephiadb_mapping.h> +#include <passwd.h> + +#ifndef DRIVER_MODE +#define DRIVER_MODE +#endif +#include <eurephiadb_driver.h> + +#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 diff --git a/database/postgresql/edb-pgsql.c b/database/postgresql/edb-pgsql.c index 2fe1463..79389ac 100644 --- a/database/postgresql/edb-pgsql.c +++ b/database/postgresql/edb-pgsql.c @@ -37,7 +37,7 @@ #define DRIVERVERSION "1.0" #ifndef DRIVERAPIVERSION -# define DRIVERAPIVERSION 1 +# define DRIVERAPIVERSION 2 #endif #include <eurephiadb_driver.h> |