X-Git-Url: https://git.ucc.asn.au/?a=blobdiff_plain;f=server%2Flogin.c;h=f3209facc74f15c2de5e692941893569ef456920;hb=0ff0cf164af4c65064fb6a3ca4749396453ad631;hp=615be9edc40f1d17696277b5786c8c35f94fa8c8;hpb=218c2d77e10b28315864990acbcf55ffe26a76e9;p=matches%2FMCTX3420.git diff --git a/server/login.c b/server/login.c index 615be9e..f3209fa 100644 --- a/server/login.c +++ b/server/login.c @@ -3,47 +3,239 @@ * @brief Implementation of Login related functionality */ -#define _BSD_SOURCE -#include "login.h" +#include "common.h" +#include "options.h" #include +#include +// LDAP stuff #define LDAP_DEPRECATED 1 // Required to use ldap_simple_bind_s #include -#define LDAP_URI "ldaps://ldap.pheme.uwa.edu.au" -#define LDAP_DN_BASE "ou=Users,ou=UWA,dc=uwads,dc=uwa,dc=edu,dc=au" +// MySQL stuff +#undef _GNU_SOURCE // HACK to silence compiler warning on redefinition in my_global.h +#include +#include + + + + + +/** + * Attempt to login by searching a MySQL database + * @param user - Username + * @param pass - Password + * @param db_host - Host running the DataBase + * @param db_user - User to search the database as + * @param db_pass - Password for the database user + * @param db_name - Name of the database to use + * @param db_table - Table to search in + * @returns Privelage level of the user or USER_UNAUTH for failure to authenticate + */ +UserType Login_MySQL(const char * user, const char * pass, + const char * db_host, const char * db_user, const char * db_pass, const char * db_name, const char * db_table) +{ + MYSQL * con = mysql_init(NULL); + if (con == NULL) + { + Log(LOGERR, "mysql_init failed - %s", mysql_error(con)); + return USER_UNAUTH; + } + + if (mysql_real_connect(con, db_host, db_user, db_pass, NULL, 0, NULL, 0) == NULL) + { + Log(LOGERR, "mysql_real_connect failed - %s", mysql_error(con)); + mysql_close(con); + return USER_UNAUTH; + } + + char buffer[BUFSIZ]; + + // Select the database + sprintf(buffer, "USE %s;", db_name); + if (mysql_query(con, buffer)) + { + Log(LOGERR, "mysql_query failed - %s", mysql_error(con)); + mysql_close(con); + return USER_UNAUTH; + } + + // Search for the user + sprintf(buffer, "SELECT password FROM %s WHERE user_name = \"%s\";", db_table, user); + if (mysql_query(con, buffer)) + { + Log(LOGERR, "mysql_query failed - %s", mysql_error(con)); + mysql_close(con); + return USER_UNAUTH; + } + + // Process the result + MYSQL_RES * result = mysql_store_result(con); + if (result == NULL) + { + Log(LOGERR, "mysql_store_result failed - %s", mysql_error(con)); + mysql_close(con); + return USER_UNAUTH; + } + + int num_fields = mysql_num_fields(result); + if (num_fields != 1) + { + Log(LOGERR, "The database may be corrupt; %d fields found, expected %d", num_fields, 1); + mysql_close(con); + return USER_UNAUTH; + } + + UserType user_type = USER_UNAUTH; + MYSQL_ROW row; + + // Get first row + if ((row = mysql_fetch_row(result))) + { + if (strcmp(crypt(pass, row[0]), row[0]) == 0) + { + user_type = USER_NORMAL; + } + + // There should only be one row. Through a hissy fit if we see any more. + if ((row = mysql_fetch_row(result))) + { + Log(LOGERR, "Too many rows found."); + user_type = USER_UNAUTH; + } + } + else + { + Log(LOGERR, "No user matching %s", user); + } + + //TODO: Handle administrator users somehow better than this + // UserCake stores the permission level in a seperate table to the username/password, which is annoying + if (user_type != USER_UNAUTH && strcmp(user, "admin") == 0) + { + user_type = USER_ADMIN; + } + mysql_free_result(result); + mysql_close(con); + return user_type; +} /** - * Attempt to bind to the LDAP_URI + * Attempt to login using a file formatted like /etc/shadow + * This is here... because all better options have been exhausted * @param user - The username * @param pass - The password - * @returns An error code according to libldap; LDAP_SUCCESS if everything worked + * @param shadow - The file to use + * @returns Privelage level of the user or USER_UNAUTH for failure to authenticate */ -int Login_LDAP_Bind(const char * user, const char * pass) +UserType Login_Shadow(const char * user, const char * pass, const char * shadow) { + if (strlen(user) + strlen(pass) >= BUFSIZ-1) + { + Log(LOGERR, "User/Password too long!\n"); + return USER_UNAUTH; + } + + FILE * f = fopen(shadow, "r"); + if (f == NULL) + { + Log(LOGERR,"Can't open %s - %s\n", shadow, strerror(errno)); + return USER_UNAUTH; + } + char buffer[BUFSIZ]; + int passwd_index = -1; - Log(LOGINFO, "Username: \"%s\"", user); + while (fgets(buffer, BUFSIZ, f) != NULL) // NOTE: Restrict username+password strings to BUFSIZ... what could possibly go wrong? + { + + //Log(LOGDEBUG,"Scanning %d: %s", strlen(buffer), buffer); + + for (int i = 0; i < BUFSIZ-1 && buffer[i] != '\0'; ++i) + { + if (buffer[i] == ':') + { + buffer[i] = '\0'; + passwd_index = i+1; + break; + } + } - char dn[BUFSIZ]; // Fill with the DN + if (strcmp(user,buffer) == 0) + { + //Log(LOGDEBUG,"User matches! %s\n", buffer); + break; + } + passwd_index = -1; + } - const char * user_type = "Students"; + fclose(f); - // Staff members have numbers starting in zero - if (user[0] == '0') + if (passwd_index <= 0) + { + //Log(LOGDEBUG,"No user found matching %s\n", user); + return USER_UNAUTH; + } + + int gid_index = -1; + for (int i = passwd_index; i < BUFSIZ-1 && buffer[i] != '\0'; ++i) + { + if (buffer[i] == ':') + { + gid_index = i+1; + buffer[i] = '\0'; + } + if (buffer[i] == '\n') + buffer[i] = '\0'; + } + char * end = buffer+gid_index; + UserType user_type = USER_NORMAL; + if (gid_index > passwd_index && gid_index < BUFSIZ-1) + { + int gid = strtol(buffer+gid_index, &end,10); + Log(LOGDEBUG, "Usertype %d %s", gid, buffer+gid_index); + if (*end == '\0' && gid == 0) + { + Log(LOGDEBUG, "Admin"); + user_type = USER_ADMIN; + } + } + + // Determine the salt + char salt[BUFSIZ]; + int s = 0; int count = 0; + for (int i = passwd_index; i < BUFSIZ-1; ++i) { - user_type = "Staff"; + salt[s++] = buffer[i]; + if (salt[s] == '$' && ++count >= 3) + break; } + +// Log(LOGDEBUG,"Salted Entry: %s\n", buffer+passwd_index); +// Log(LOGDEBUG,"Salted Attempt: %s\n", crypt(pass, salt)); - if (sprintf(dn, "cn=%s,ou=%s,%s", user, user_type, LDAP_DN_BASE) >= BUFSIZ) + if (strcmp(crypt(pass, salt), buffer+passwd_index) == 0) { - Log(LOGERR,"DN too long; recompile with increased BUFSIZ"); + return user_type; } + return USER_UNAUTH; +} + +/** + * Attempt to bind to a LDAP uri + * @param uri - The uri + * @param dn - The DN + * @param pass - The password + * @returns An error code according to libldap; LDAP_SUCCESS if everything worked + */ +int Login_LDAP_Bind(const char * uri, const char * dn, const char * pass) +{ + Log(LOGDEBUG, "Bind to %s with dn %s and pass %s", uri, dn, pass); // Initialise LDAP; prepares to connect to the server LDAP * ld = NULL; - int err = ldap_initialize(&ld, LDAP_URI); + int err = ldap_initialize(&ld, uri); if (err != LDAP_SUCCESS || ld == NULL) { Log(LOGERR,"ldap_initialize failed - %s (ld = %p)", ldap_err2string(err), ld); @@ -68,13 +260,14 @@ int Login_LDAP_Bind(const char * user, const char * pass) } else { - Log(LOGDEBUG, "Successfully bound to %s with username %s", LDAP_URI, user); + Log(LOGDEBUG, "Successfully bound to %s with dn %s", uri, dn); } - err = ldap_unbind_s(ld); - if (err != LDAP_SUCCESS) + int err2 = ldap_unbind_s(ld); + if (err2 != LDAP_SUCCESS) { - Log(LOGERR, "ldap_unbind_s failed - %s", ldap_err2string(err)); + Log(LOGERR, "ldap_unbind_s failed - %s", ldap_err2string(err2)); + err = err2; } return err; } @@ -85,28 +278,25 @@ int Login_LDAP_Bind(const char * user, const char * pass) * @param params - Parameter string, UNUSED */ void Logout_Handler(FCGIContext * context, char * params) -{ +{ FCGI_ReleaseControl(context); + FCGI_SendControlCookie(context, false); //Unset the cookie + FCGI_AcceptJSON(context, "Logged out"); } /** * Handle a Login Request * @param context - The context - * @param params - Parameter string, should contain username and password + * @param params - Parameter string, should contain username and password. + * NOTE: Care should be taken when using params, as it is + * completely unescaped. Do not log or use it without + * suitable escaping. */ void Login_Handler(FCGIContext * context, char * params) { - - if (context->control_key[0] != '\0') - { - FCGI_RejectJSON(context, "Already logged in"); - return; - } - - char * user = ""; // The username supplied through CGI - char * pass = ""; // The password supplied through CGI - //TODO: Make sure these are passed through HTTPS, *not* HTTP .... otherwise people can eavesdrop on the passwords + char * user; // The username supplied through CGI + char * pass; // The password supplied through CGI FCGIValue values[] = { {"user", &user, FCGI_REQUIRED(FCGI_STRING_T)}, @@ -127,35 +317,122 @@ void Login_Handler(FCGIContext * context, char * params) return; } - - // Trim leading whitespace (the BUFSIZ check is to make sure incorrectly terminated strings don't cause an infinite loop) + // Trim leading whitespace int i = 0; - for (i = 0; i < BUFSIZ && isspace(user[0]) && user[0] != '\0'; ++i,++user); + for (i = 0; isspace(user[0]) && user[0] != '\0'; ++i, ++user); // Truncate string at first non alphanumeric character - for (i = 0; i < BUFSIZ && isalnum(user[i]) && user[i] != '\0'; ++i); + for (i = 0; isalnum(user[i]) && user[i] != '\0'; ++i); user[i] = '\0'; - if (strlen(pass) <= 0) + + UserType user_type = USER_UNAUTH; + + switch (g_options.auth_method) { - FCGI_RejectJSON(context, "No password supplied."); - return; - } - // Try to authenticate - int err = Login_LDAP_Bind(user, pass); + case AUTH_LDAP: + { + if (*pass == '\0') + { + FCGI_RejectJSON(context, "No password supplied."); + return; + } + + //TODO: Generate the DN in some sane way + char dn[BUFSIZ]; + + + // On a simple LDAP server: + //int len = sprintf(dn, "uid=%s,%s", user, g_options.auth_options); + + // At UWA (hooray) + char * user_group = "Students"; + if (user[0] == '0') + user_group = "Staff"; + int len = sprintf(dn, "cn=%s,ou=%s,%s", user, user_group, g_options.auth_options); + + + if (len >= BUFSIZ) + { + FCGI_RejectJSON(context, "DN too long! Recompile with increased BUFSIZ"); + return; + } + + if (Login_LDAP_Bind(g_options.auth_uri, dn, pass) == LDAP_SUCCESS) + { + if (user[0] == '0') + user_type = USER_ADMIN; + else + user_type = USER_NORMAL; + } + break; + } + case AUTH_SHADOW: + { + user_type = Login_Shadow(user, pass, g_options.auth_uri); + break; + } + case AUTH_MYSQL: + { + //WARNING: C string manipulation code approaching! + // Non reentrent; uses strsep and modifies g_options.auth_options + // If problems happen, try strdup first ... + static char * db_opts[] = {"root", "", "users", "uc_users"}; + static bool db_init_opts = false; + if (!db_init_opts) + { + db_init_opts = true; + db_opts[0] = (char*)g_options.auth_options; + for (int i = 1; i < sizeof(db_opts)/sizeof(char*); ++i) + { + char * def = db_opts[i]; + db_opts[i] = db_opts[i-1]; + + strsep(db_opts+i, ","); + if (db_opts[i] == NULL) + { + db_opts[i] = def; + break; + } + } + //Log(LOGDEBUG, "MySQL: user %s pass %s name %s table %s", db_opts[0], db_opts[1], db_opts[2], db_opts[3]); + } + + user_type = Login_MySQL(user, pass, g_options.auth_uri, db_opts[0],db_opts[1], db_opts[2], db_opts[3]); + break; + } + default: + { + Log(LOGWARN, "No authentication!"); + user_type = USER_ADMIN; + break; + } + } + // error check - if (err == LDAP_SUCCESS) + if (user_type == USER_UNAUTH) { - FCGI_LockControl(context, false); + Log(LOGDEBUG, "Authentication failure for %s", user); + FCGI_RejectJSONEx(context, STATUS_UNAUTHORIZED, "Authentication failure."); + } + else + { + // Try and gain control over the system + if (FCGI_LockControl(context, user, user_type)) + { + FCGI_EscapeText(context->user_name); //Don't break javascript pls + // Give the user a cookie + FCGI_SendControlCookie(context, true); //Send the control key + FCGI_AcceptJSON(context, "Logged in"); + Log(LOGDEBUG, "Successful authentication for %s", user); + } + else + { + Log(LOGDEBUG, "%s successfully authenticated but system was in use by %s", user, context->user_name); + FCGI_RejectJSON(context, "Someone else is already logged in (and you are not an admin)"); + } } - - - FCGI_BeginJSON(context, STATUS_OK); - FCGI_JSONPair("user", user); - FCGI_JSONPair("login", ldap_err2string(err)); - FCGI_JSONPair("key", context->control_key); - FCGI_EndJSON(); }