From: Sam Moore Date: Sun, 20 Oct 2013 08:36:01 +0000 (+0800) Subject: Modify FastCGI server to use MySQL for auth X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=dd239d26439d97188aa6cad06573a6584dc250a6;p=matches%2FMCTX3420.git Modify FastCGI server to use MySQL for auth Made some general improvements to Login_Handler and how the "auth_uri" is dealt with. Called strsep, but it's not (that) evil since it doesn't do anything I wouldn't have done with boilerplate anyway. MySQL for auth tested and appears to work. Using SHA6 instead of md5 (!) hashes. --- diff --git a/.gitignore b/.gitignore index ae7115d..770ed60 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ server/win32 **/nbproject/private/ *jquery-ui* + +*password* diff --git a/server/Makefile b/server/Makefile index 39a71fb..c314b20 100644 --- a/server/Makefile +++ b/server/Makefile @@ -1,7 +1,7 @@ # Makefile for server software CXX = gcc -FLAGS = -std=gnu99 -Wall -pedantic -g -I/usr/include/opencv -I/usr/include/opencv2/highgui -L/usr/lib -LIB = -lfcgi -lssl -lcrypto -lpthread -lm -lopencv_highgui -lopencv_core -lopencv_ml -lopencv_imgproc -lldap -lcrypt +FLAGS = -std=gnu99 -Wall -pedantic -g -I/usr/include/opencv -I/usr/include/opencv2/highgui -L/usr/lib `mysql_config --cflags` +LIB = -lfcgi -lssl -lcrypto -lpthread -lm -lopencv_highgui -lopencv_core -lopencv_ml -lopencv_imgproc -lldap -lcrypt `mysql_config --libs` OBJ = log.o control.o data.o fastcgi.o main.o sensor.o actuator.o image.o bbb_pin.o pin_test.o login.o sensors/sensors.a actuators/actuators.a RM = rm -f diff --git a/server/fastcgi.c b/server/fastcgi.c index 0a6783f..d7e057b 100644 --- a/server/fastcgi.c +++ b/server/fastcgi.c @@ -520,8 +520,23 @@ void * FCGI_RequestLoop (void *data) snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL")); snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING")); + + //char cookies[BUFSIZ]; + //snprintf(cookies, BUFSIZ, "%s", getenv("COOKIE_STRING")); + //Log(LOGDEBUG, "ALL cookies %s", cookies); //mmmm + //Hack to get the nameless cookie only + // (works as long as browsers send the nameless cookie first...) snprintf(control_key, CONTROL_KEY_BUFSIZ, "%s", getenv("COOKIE_STRING")); + // Ignore any other cookies if the nameless cookie is empty + for (int i = 0; i < CONTROL_KEY_BUFSIZ; ++i) + { + if (control_key[i] == ';') + { + control_key[i] = '\0'; + break; + } + } Log(LOGDEBUG, "Got request #%d - Module %s, params %s", context.response_number, module, params); Log(LOGDEBUG, "Control key: %s", control_key); @@ -559,8 +574,8 @@ void * FCGI_RequestLoop (void *data) if (module_handler) { - //if (module_handler != Login_Handler && module_handler != IdentifyHandler && module_handler) - if (false) // Testing + if (g_options.auth_method != AUTH_NONE && module_handler != Login_Handler && module_handler != IdentifyHandler && module_handler) + //if (false) // Testing { if (!FCGI_HasControl(&context, control_key)) { diff --git a/server/login.c b/server/login.c index 78e31f9..c1d981f 100644 --- a/server/login.c +++ b/server/login.c @@ -4,21 +4,124 @@ */ - - -#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 +// 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); + } + + + mysql_free_result(result); + mysql_close(con); + return user_type; +} + /** * 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 + * @param shadow - The file to use * @returns Privelage level of the user or USER_UNAUTH for failure to authenticate */ UserType Login_Shadow(const char * user, const char * pass, const char * shadow) @@ -227,15 +330,17 @@ void Login_Handler(FCGIContext * context, char * params) //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.ldap_base_dn); + //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.ldap_base_dn); + int len = sprintf(dn, "cn=%s,ou=%s,%s", user, user_group, g_options.auth_options); if (len >= BUFSIZ) @@ -258,6 +363,35 @@ void Login_Handler(FCGIContext * context, char * params) 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 ... + 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!"); diff --git a/server/main.c b/server/main.c index fadcd3a..38fc9f0 100644 --- a/server/main.c +++ b/server/main.c @@ -47,7 +47,7 @@ void ParseArguments(int argc, char ** argv) g_options.auth_method = AUTH_NONE; // Don't use authentication g_options.auth_uri = ""; // - g_options.ldap_base_dn = ""; + g_options.auth_options = ""; g_options.experiment_dir = "."; for (int i = 1; i < argc; ++i) @@ -72,14 +72,10 @@ void ParseArguments(int argc, char ** argv) case 'p': g_options.enable_pin = !(strtol(argv[++i], &end, 10)); break; - // LDAP URI + // Authentication URI and options case 'A': g_options.auth_uri = argv[++i]; break; - // LDAP DN - case 'd': - g_options.ldap_base_dn = argv[++i]; - break; case 'e': // Experiments directory g_options.experiment_dir = argv[++i]; @@ -93,12 +89,7 @@ void ParseArguments(int argc, char ** argv) Fatal("argv[%d] -%c requires an integer (got \"%s\" instead)", i-1, argv[i-1][0], argv[i]); } - Log(LOGDEBUG, "Verbosity: %d", g_options.verbosity); - Log(LOGDEBUG, "Pin Module Enabled: %d", g_options.enable_pin); - Log(LOGDEBUG, "Auth URI: %s", g_options.auth_uri); - Log(LOGDEBUG, "LDAP Base DN: %s", g_options.ldap_base_dn); - //Log(LOGDEBUG, "Root directory: %s", g_options.root_dir); - Log(LOGDEBUG, "Experiment directory: %s", g_options.experiment_dir); + if (!DirExists(g_options.experiment_dir)) { @@ -107,12 +98,58 @@ void ParseArguments(int argc, char ** argv) if (g_options.auth_uri[0] != '\0') { - //HACK... - if (PathExists(g_options.auth_uri)) - g_options.auth_method = AUTH_SHADOW; - else - g_options.auth_method = AUTH_LDAP; + // Get the options part of the URI if it exists + char * c = (char*)g_options.auth_uri; + while (*(++c) != '\0' && *c != '#'); + + if (*(c++) == '#') + { + *(c-1) = '\0'; + g_options.auth_options = c; + } + + // Use the first part of the URI to identify the protocol: + c = (char*)g_options.auth_uri; + while (*(++c) != '\0' && *c != ':'); + + if (*c == '\0') // No ':' means no protocol; use plaintext file + { + g_options.auth_method = AUTH_SHADOW; + } + else if (*c == ':' && *(c+1) == '/' && *(c+2) == '/') + { + + *c = '\0'; + if (strcmp(g_options.auth_uri, "ldap") == 0 || strcmp(g_options.auth_uri, "ldaps") == 0) + { + *c = ':'; // LDAP URI's require the prodocol as part of the string + g_options.auth_method = AUTH_LDAP; + } + else if (strcmp(g_options.auth_uri, "mysql") == 0) + { + g_options.auth_uri = c+3; // MySQL doesn't (just a hostname) + g_options.auth_method = AUTH_MYSQL; + } + else + { + Fatal("Unsupported authentication method %s", g_options.auth_uri); + } + } } + else + { + Log(LOGWARN, "No authentication method."); + } + + Log(LOGDEBUG, "Verbosity: %d", g_options.verbosity); + Log(LOGDEBUG, "Pin Module Enabled: %d", g_options.enable_pin); + Log(LOGDEBUG, "Auth method: %d", g_options.auth_method); + Log(LOGDEBUG, "Auth URI: %s", g_options.auth_uri); + Log(LOGDEBUG, "Auth Options: %s", g_options.auth_options); + //Log(LOGDEBUG, "Root directory: %s", g_options.root_dir); + Log(LOGDEBUG, "Experiment directory: %s", g_options.experiment_dir); + + } diff --git a/server/options.h b/server/options.h index 0e0b598..88496cc 100644 --- a/server/options.h +++ b/server/options.h @@ -25,11 +25,11 @@ typedef struct /** URI for authentication **/ const char * auth_uri; - /** Base DN for LDAP authentication **/ - const char * ldap_base_dn; + /** Additional options for authentication (to be parsed in Login_Handler) **/ + const char * auth_options; /** Authentication method **/ - enum {AUTH_NONE, AUTH_LDAP, AUTH_SHADOW} auth_method; + enum {AUTH_NONE, AUTH_LDAP, AUTH_SHADOW, AUTH_MYSQL} auth_method; /** Experiments directory **/ const char *experiment_dir; diff --git a/server/parameters b/server/parameters index da48239..99b3f7e 100644 --- a/server/parameters +++ b/server/parameters @@ -17,18 +17,16 @@ LOGDEBUG=4 verbosity="$LOGDEBUG" # Set to 1/0 to enable/disable the pin module (gives direct control over GPIO/ADC/PWM) +#TODO: This option isn't actually implemented yet... pin_test="0" # Set to the URI to use authentication -#auth_uri="ldap://192.168.1.1" -#auth_uri="ldaps://ldap.pheme.uwa.edu.au" #UWA -#auth_uri="/etc/shadow" -auth_uri="shadow" - -# Set to the dn of the LDAP server -ldap_base_dn="ou=People,dc=daedalus" # Testing -#ldap_base_dn="ou=Users,ou=UWA,dc=uwads,dc=uwa,dc=edu,dc=au" #UWA +#auth_uri="ldap://192.168.1.1#ou=People,dc=daedalus" +#auth_uri="ldaps://ldap.pheme.uwa.edu.au#ou=Users,ou=UWA,dc=uwads,dc=uwa,dc=edu,dc=au" #UWA +auth_uri="/etc/shadow" +#auth_uri="shadow" +#auth_uri="mysql://localhost#root,$(cat mysql_password)" ## OPTIONS TO BE PASSED TO SERVER; DO NOT EDIT -parameters="-v $verbosity -p $pin_test -A $auth_uri -d $ldap_base_dn" +parameters="-v $verbosity -p $pin_test -A $auth_uri" diff --git a/server/run.sh b/server/run.sh index 76a7a48..1890059 100755 --- a/server/run.sh +++ b/server/run.sh @@ -6,13 +6,16 @@ if [ ! -e "server" ]; then exit 1 fi +# Get the parameters +. parameters +echo "Parameters are: $parameters" if [[ "$(uname -m)" != *arm* ]]; then echo Not running on the BBB # Use this to quickly test run the server in valgrind #spawn-fcgi -p9005 -n ./valgrind.sh # Use this to run the server normally - spawn-fcgi -p9005 -n ./server + spawn-fcgi -p9005 -n -- ./server $parameters exit 0 fi @@ -75,10 +78,7 @@ fi; #adc_device_path=$(dirname $(find /sys -name *AIN0)) -# Get the parameters -. parameters -echo "Parameters are: $parameters" # Run the program with parameters # TODO: Can tell spawn-fcgi to run the program as an unprivelaged user? diff --git a/testing/MCTXWeb/public_html/users/admin_upload_users.php b/testing/MCTXWeb/public_html/users/admin_upload_users.php index dd4f28c..d068f71 100644 --- a/testing/MCTXWeb/public_html/users/admin_upload_users.php +++ b/testing/MCTXWeb/public_html/users/admin_upload_users.php @@ -4,6 +4,7 @@ require_once("models/config.php"); if (!securePage($_SERVER['PHP_SELF'])){die();} require_once("models/header.php"); +createPage("User Upload"); if (!empty($_POST)) diff --git a/testing/MCTXWeb/public_html/users/login.php b/testing/MCTXWeb/public_html/users/login.php index f0168da..2c45d1f 100644 --- a/testing/MCTXWeb/public_html/users/login.php +++ b/testing/MCTXWeb/public_html/users/login.php @@ -47,6 +47,8 @@ if(!empty($_POST)) { //Hash the password and use the salt from the database to compare the password. $entered_pass = generateHash($password,$userdetails["password"]); + + echo "".$userdetails["password"]; if($entered_pass != $userdetails["password"]) { diff --git a/testing/MCTXWeb/public_html/users/models/db-settings.php b/testing/MCTXWeb/public_html/users/models/db-settings.php index 6027314..0df3b94 100644 --- a/testing/MCTXWeb/public_html/users/models/db-settings.php +++ b/testing/MCTXWeb/public_html/users/models/db-settings.php @@ -8,7 +8,7 @@ http://usercake.com $db_host = "localhost"; //Host address (most likely localhost) $db_name = "users"; //Name of Database $db_user = "root"; //Name of database user -$db_pass = "NOT_THE_PASSWORD"; //Password for database user +$db_pass = file_get_contents("mysql_password"); // TODO: Create that file or replace this line. $db_table_prefix = "uc_"; GLOBAL $errors; diff --git a/testing/MCTXWeb/public_html/users/models/funcs.php b/testing/MCTXWeb/public_html/users/models/funcs.php index 021f3a0..07af426 100644 --- a/testing/MCTXWeb/public_html/users/models/funcs.php +++ b/testing/MCTXWeb/public_html/users/models/funcs.php @@ -71,14 +71,13 @@ function generateHash($plainText, $salt = null) { if ($salt === null) { - $salt = substr(md5(uniqid(rand(), true)), 0, 25); + //$salt = substr(md5(uniqid(rand(), true)), 0, 25); // Original UserCake + $random = file_get_contents("/dev/urandom", false, null, 0, 25); // Get random number + $salt = '$6$'.bin2hex($random).'$'; // Make hex salt + } - else - { - $salt = substr($salt, 0, 25); - } - - return $salt . sha1($salt . $plainText); + //return $salt . sha1($salt . $plainText); // Original UserCake + return crypt($plainText, $salt); } //Checks if an email is valid diff --git a/testing/MCTXWeb/public_html/users/models/header.php b/testing/MCTXWeb/public_html/users/models/header.php index ee78a54..239a10a 100644 --- a/testing/MCTXWeb/public_html/users/models/header.php +++ b/testing/MCTXWeb/public_html/users/models/header.php @@ -4,14 +4,35 @@ UserCake Version: 2.0.2 http://usercake.com */ echo " - - - - -".$websiteName." - - -"; + + + + + ".$websiteName." + + + + "; + +/** + * Make the remainder of the page + */ +function createPage($title) +{ + echo " +
+
+
+

User Managment

+

$title

+
"; + + include("left-nav.php"); + + echo " +
+
"; +} ?>