From e3e7914fe2f59765e5f92371329652a02518928c Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Mon, 14 Oct 2013 22:32:02 +0800 Subject: [PATCH] Work on user based control Test file shadow (password matches username *cough*) for login purposes. When a user gains FCGI_LockControl, a directory is created (if it doesn't exist). Control_Handler will operate from the directory of the currently logged in user. The user who owns the currently running experiment must match a user who wants to control it. Alternately an administrator can pass "force" Consider generalising this to also apply to actuators (since any user can change the actuator control :S). Also it's horribly messy and terrible and all in the Control_Handler at the moment. FCGI_Context should probably store a lot of the information that the g_controls structure is storing at the moment? Merge/replace? --- server/actuator.c | 9 +++- server/actuators/filetest.c | 4 +- server/control.c | 96 +++++++++++++++++++++++++++++++++++-- server/fastcgi.c | 80 +++++++++++++++++++++---------- server/fastcgi.h | 14 +++--- server/login.c | 85 +++++++++++++++++++++----------- server/main.c | 13 +++-- server/options.h | 3 ++ server/parameters | 3 +- server/sensor.c | 10 +++- server/shadow | 3 ++ 11 files changed, 245 insertions(+), 75 deletions(-) create mode 100644 server/shadow diff --git a/server/actuator.c b/server/actuator.c index e650cf8..9b9a1a8 100644 --- a/server/actuator.c +++ b/server/actuator.c @@ -36,12 +36,17 @@ int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn a->name = name; a->set = set; // Set read function a->init = init; // Set init function - if (init != NULL) - init(name, user_id); // Call it + a->sanity = sanity; pthread_mutex_init(&(a->mutex), NULL); + if (init != NULL) + { + if (!init(name, user_id)) + Fatal("Couldn't initialise actuator %s", name); + } + Actuator_SetValue(a, initial_value, false); return g_num_actuators; diff --git a/server/actuators/filetest.c b/server/actuators/filetest.c index 697753a..8dcc9df 100644 --- a/server/actuators/filetest.c +++ b/server/actuators/filetest.c @@ -4,8 +4,10 @@ static FILE * f = NULL; bool Filetest_Init(const char * name, int id) { f = fopen(name, "w"); + if (f == NULL) + return false; setbuf(f, NULL); // Unbuffer - return (f != NULL); + return true; } bool Filetest_Set(int id, double value) diff --git a/server/control.c b/server/control.c index d9a7dc6..c6b5823 100644 --- a/server/control.c +++ b/server/control.c @@ -3,14 +3,17 @@ * @brief Handles all client control requests (admin related) */ #include "common.h" +#include "options.h" #include "control.h" #include "sensor.h" #include "actuator.h" +#include typedef struct ControlData { ControlModes current_mode; pthread_mutex_t mutex; struct timeval start_time; + char user_name[31]; // The user who owns the currently running experiment } ControlData; ControlData g_controls = {CONTROL_STOP, PTHREAD_MUTEX_INITIALIZER, {0}}; @@ -36,6 +39,9 @@ void Control_Handler(FCGIContext *context, char *params) { const char *name = ""; bool force = false; ControlModes desired_mode; + + + // Login/auth now handled entirely in fastcgi.c and login.c @@ -48,8 +54,47 @@ void Control_Handler(FCGIContext *context, char *params) { {"name", &name, FCGI_STRING_T} }; + if (!FCGI_ParseRequest(context, params, values, 3)) return; + + //HACKETY HACK HACK (should really be a seperate function) + if (strcmp(action, "list") == 0) + { + DIR * dir = opendir(context->user_name); + if (dir == NULL) + { + FCGI_RejectJSON(context, "Failed to open user directory"); + return; + } + struct dirent * ent; + FCGI_BeginJSON(context, STATUS_OK); + FCGI_JSONKey("experiments"); + FCGI_PrintRaw("["); + + bool first = true; + while ((ent = readdir(dir)) != NULL) + { + char * c; + for (c = ent->d_name; *c != '\0' && *c != '.'; ++c); + + if (*c != '\0' && strcmp(c, ".exp") == 0) + { + if (!first) + FCGI_PrintRaw(","); + *c = '\0'; // Ummm... probably not a great idea + FCGI_PrintRaw(ent->d_name); + first = false; + } + } + FCGI_PrintRaw("]"); + FCGI_EndJSON(); + + return; // Dear god this is terrible + } + //TODO: Need a "load" action to set data files (but not run) from a past experiment + + //TODO: Need a "delete" action so that people can overwrite experiments (without all this "force" shenanigans) if (!strcmp(action, "emergency")) { desired_mode = CONTROL_EMERGENCY; @@ -65,25 +110,68 @@ void Control_Handler(FCGIContext *context, char *params) { FCGI_RejectJSON(context, "Unknown action specified."); return; } + + if (*g_controls.user_name != '\0' && strcmp(g_controls.user_name,context->user_name) != 0) + { + if (context->user_type != USER_ADMIN) + { + FCGI_RejectJSON(context, "Another user has an experiment in progress."); + return; + } + + if (!force) + { + Log(LOGERR, "User %s is currently running an experiment!", g_controls.user_name); + FCGI_RejectJSON(context, "Pass \"force\" to take control over another user's experiment"); + return; + } + } + + + //HACK + chdir(context->user_name); + void *arg = NULL; if (desired_mode == CONTROL_START) { if (PathExists(name) && !force) { FCGI_RejectJSON(context, "An experiment with that name already exists."); + chdir(g_options.root_dir); // REVERSE HACK + return; + } + char * c = (char*)name; + for (c = (char*)name; *c != '\0' && *c != '.'; ++c); + if (*c == '.') + { + FCGI_RejectJSON(context, "Can't include '.' characters in experiment names (at this point we don't care anymore, go find someone who does)."); + chdir(g_options.root_dir); // REVERSE HACK return; } - arg = (void*)name; } + + const char *ret; - if ((ret = Control_SetMode(desired_mode, arg)) != NULL) { + if ((ret = Control_SetMode(desired_mode, arg)) != NULL) + { FCGI_RejectJSON(context, ret); - } else { + } + else + { + if (desired_mode == CONTROL_STOP) + g_controls.user_name[0] = '\0'; + else + { + snprintf(g_controls.user_name, sizeof(g_controls.user_name), context->user_name); + } FCGI_BeginJSON(context, STATUS_OK); FCGI_JSONPair("description", "ok"); FCGI_EndJSON(); } + + //REVERSE HACK + chdir(g_options.root_dir); } /** @@ -130,7 +218,7 @@ const char* Control_SetMode(ControlModes desired_mode, void * arg) break; case CONTROL_EMERGENCY: if (g_controls.current_mode != CONTROL_START) //pfft - ret = "Not running so how can there be an emergency."; + ret = "Not running so how can there be an emergency?"; break; default: break; diff --git a/server/fastcgi.c b/server/fastcgi.c index cdaef79..a77a3a4 100644 --- a/server/fastcgi.c +++ b/server/fastcgi.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "common.h" #include "sensor.h" @@ -46,7 +48,7 @@ static void IdentifyHandler(FCGIContext *context, char *params) { FCGI_JSONPair("build_date", __DATE__ " " __TIME__); FCGI_JSONLong("api_version", API_VERSION); FCGI_JSONBool("logged_in", has_control); - FCGI_JSONPair("friendly_name", has_control ? context->friendly_name : ""); + FCGI_JSONPair("user_name", has_control ? context->user_name : ""); //Sensor and actuator information if (ident_sensors) { @@ -75,36 +77,61 @@ static void IdentifyHandler(FCGIContext *context, char *params) { } /** - * Gives the user a key that determines who has control over - * the system at any one time. The key can be forcibly generated, revoking - * any previous control keys. To be used in conjunction with HTTP - * basic authentication. + * Given an authorised user, attempt to set the control over the system. + * Modifies members in the context structure appropriately if successful. * @param context The context to work in - * @param force Whether to force key generation or not. - * @return true on success, false otherwise (eg someone else already in control) + * @param user_name - Name of the user + * @param user_type - Type of the user, passed after successful authentication + * @return true on success, false otherwise (eg someone else already in control) */ -bool FCGI_LockControl(FCGIContext *context, bool force) { +bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType user_type) +{ + // Get current time time_t now = time(NULL); bool expired = now - context->control_timestamp > CONTROL_TIMEOUT; - if (force || !*(context->control_key) || expired) + // Can't lock control if: User not actually logged in (sanity), or key is still valid and the user is not an admin + if (user_type == USER_UNAUTH || + (user_type != USER_ADMIN && !expired && *(context->control_key) != '\0')) + return false; + + // Release any existing control (if any) + FCGI_ReleaseControl(context); + + // Set timestamp + context->control_timestamp = now; + + // Generate a SHA1 hash for the user + SHA_CTX sha1ctx; + unsigned char sha1[20]; + int i = rand(); + SHA1_Init(&sha1ctx); + SHA1_Update(&sha1ctx, &now, sizeof(now)); + SHA1_Update(&sha1ctx, &i, sizeof(i)); + SHA1_Final(sha1, &sha1ctx); + for (i = 0; i < sizeof(sha1); i++) + sprintf(context->control_key + i * 2, "%02x", sha1[i]); + + // Set the IP address + snprintf(context->control_ip, 16, "%s", getenv("REMOTE_ADDR")); + // Set the user name + int uname_len = strlen(user_name); + if (snprintf(context->user_name, sizeof(context->user_name), "%s", user_name) < uname_len) + { + Log(LOGERR, "Username at %d characters too long (limit %d)", uname_len, sizeof(context->user_name)); + return false; // :-( + } + // Set the user type + context->user_type = user_type; + // Create directory + if (mkdir(user_name, 0777) != 0 && errno != EEXIST) { - SHA_CTX sha1ctx; - unsigned char sha1[20]; - int i = rand(); - - SHA1_Init(&sha1ctx); - SHA1_Update(&sha1ctx, &now, sizeof(now)); - SHA1_Update(&sha1ctx, &i, sizeof(i)); - SHA1_Final(sha1, &sha1ctx); - - context->control_timestamp = now; - for (i = 0; i < 20; i++) - sprintf(context->control_key + i * 2, "%02x", sha1[i]); - snprintf(context->control_ip, 16, "%s", getenv("REMOTE_ADDR")); - return true; + Log(LOGERR, "Couldn't create user directory %s/%s - %s", g_options.root_dir, user_name, strerror(errno)); + return false; // :-( } - return false; + + + return true; // :-) } /** @@ -133,6 +160,7 @@ bool FCGI_HasControl(FCGIContext *context, const char *key) { */ void FCGI_ReleaseControl(FCGIContext *context) { *(context->control_key) = 0; + // Note: context->user_name should *not* be cleared return; } @@ -507,8 +535,8 @@ void * FCGI_RequestLoop (void *data) if (module_handler) { - //if (module_handler != Login_Handler && module_handler != IdentifyHandler) - if (false) // Testing + if (module_handler != Login_Handler && module_handler != IdentifyHandler && module_handler) + //if (false) // Testing { if (!FCGI_HasControl(&context, cookie)) { diff --git a/server/fastcgi.h b/server/fastcgi.h index dd89594..85dab57 100644 --- a/server/fastcgi.h +++ b/server/fastcgi.h @@ -39,19 +39,21 @@ typedef struct FCGIValue { unsigned flags; } FCGIValue; +typedef enum {USER_UNAUTH, USER_NORMAL, USER_ADMIN} UserType; + /**Contextual information related to FCGI requests*/ typedef struct { /**The time of last valid user access possessing the control key**/ time_t control_timestamp; /**A SHA-1 hash that is the control key, determining who is logged in**/ - char control_key[41]; - /**Determines if the user is an admin or not**/ - bool is_admin; + char control_key[41]; /**The IPv4 address of the logged-in user**/ char control_ip[16]; - /**A friendly name for the logged-in user. Max length 30**/ - char friendly_name[31]; + /**Determines if the user is an admin or not**/ + UserType user_type; + /**Name of the logged in user**/ + char user_name[31]; /**The name of the current module**/ const char *current_module; /**For debugging purposes?**/ @@ -60,7 +62,7 @@ typedef struct typedef void (*ModuleHandler) (FCGIContext *context, char *params); -extern bool FCGI_LockControl(FCGIContext *context, bool force); +extern bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType user_type); extern void FCGI_ReleaseControl(FCGIContext *context); extern bool FCGI_HasControl(FCGIContext *context, const char *key); extern char *FCGI_KeyPair(char *in, const char **key, const char **value); diff --git a/server/login.c b/server/login.c index a2a11e9..78e31f9 100644 --- a/server/login.c +++ b/server/login.c @@ -16,24 +16,24 @@ /** * Attempt to login using a file formatted like /etc/shadow - * This is here for horrible hack purposes + * This is here... because all better options have been exhausted * @param user - The username * @param pass - The password - * @returns True if the login was successful, false otherwise + * @returns Privelage level of the user or USER_UNAUTH for failure to authenticate */ -bool Login_Shadow(const char * user, const char * pass, const char * shadow) +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 false; + return USER_UNAUTH; } FILE * f = fopen(shadow, "r"); if (f == NULL) { Log(LOGERR,"Can't open %s - %s\n", shadow, strerror(errno)); - return false; + return USER_UNAUTH; } char buffer[BUFSIZ]; @@ -42,9 +42,9 @@ bool Login_Shadow(const char * user, const char * pass, const char * shadow) 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); + //Log(LOGDEBUG,"Scanning %d: %s", strlen(buffer), buffer); - for (int i = 0; i < BUFSIZ-1; ++i) + for (int i = 0; i < BUFSIZ-1 && buffer[i] != '\0'; ++i) { if (buffer[i] == ':') { @@ -56,7 +56,7 @@ bool Login_Shadow(const char * user, const char * pass, const char * shadow) if (strcmp(user,buffer) == 0) { - Log(LOGDEBUG,"User matches! %s\n", buffer); + //Log(LOGDEBUG,"User matches! %s\n", buffer); break; } passwd_index = -1; @@ -64,16 +64,31 @@ bool Login_Shadow(const char * user, const char * pass, const char * shadow) if (passwd_index <= 0) { - Log(LOGDEBUG,"No user found matching %s\n", user); - return false; + //Log(LOGDEBUG,"No user found matching %s\n", user); + return USER_UNAUTH; } - for (int i = passwd_index; i < BUFSIZ-1; ++i) + int gid_index = -1; + for (int i = passwd_index; i < BUFSIZ-1 && buffer[i] != '\0'; ++i) { - if (buffer[i] == ':' || buffer[i] == '\n') + 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; } } @@ -87,10 +102,14 @@ bool Login_Shadow(const char * user, const char * pass, const char * shadow) break; } - Log(LOGDEBUG,"Salted Entry: %s\n", buffer+passwd_index); - Log(LOGDEBUG,"Salted Attempt: %s\n", crypt(pass, salt)); +// Log(LOGDEBUG,"Salted Entry: %s\n", buffer+passwd_index); +// Log(LOGDEBUG,"Salted Attempt: %s\n", crypt(pass, salt)); - return (strcmp(crypt(pass, salt), buffer+passwd_index) == 0); + if (strcmp(crypt(pass, salt), buffer+passwd_index) == 0) + { + return user_type; + } + return USER_UNAUTH; } /** @@ -193,7 +212,7 @@ void Login_Handler(FCGIContext * context, char * params) user[i] = '\0'; - bool authenticated = true; + UserType user_type = USER_UNAUTH; switch (g_options.auth_method) { @@ -213,10 +232,10 @@ void Login_Handler(FCGIContext * context, char * params) //int len = sprintf(dn, "uid=%s,%s", user, g_options.ldap_base_dn); // At UWA (hooray) - char * user_type = "Students"; + char * user_group = "Students"; if (user[0] == '0') - user_type = "Staff"; - int len = sprintf(dn, "cn=%s,ou=%s,%s", user, user_type, g_options.ldap_base_dn); + user_group = "Staff"; + int len = sprintf(dn, "cn=%s,ou=%s,%s", user, user_group, g_options.ldap_base_dn); if (len >= BUFSIZ) @@ -225,41 +244,49 @@ void Login_Handler(FCGIContext * context, char * params) return; } - authenticated = (Login_LDAP_Bind(g_options.auth_uri, dn, pass) == LDAP_SUCCESS); + 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: { - authenticated = Login_Shadow(user, pass, g_options.auth_uri); + user_type = Login_Shadow(user, pass, g_options.auth_uri); break; } default: { Log(LOGWARN, "No authentication!"); + user_type = USER_ADMIN; break; } } // error check - if (!authenticated) + if (user_type == USER_UNAUTH) { + Log(LOGDEBUG, "Authentication failure for %s", user); FCGI_RejectJSONEx(context, STATUS_UNAUTHORIZED, "Authentication failure."); } else { - if (FCGI_LockControl(context, false)) + // Try and gain control over the system + if (FCGI_LockControl(context, user, user_type)) { - //Todo: change this to something better than the username if using LDAP. - snprintf(context->friendly_name, 31, "%s", user); - FCGI_EscapeText(context->friendly_name); //Don't break javascript pls - + FCGI_EscapeText(context->user_name); //Don't break javascript pls // Give the user a cookie FCGI_AcceptJSON(context, "Logged in", context->control_key); + Log(LOGDEBUG, "Successful authentication for %s", user); } else { - FCGI_RejectJSON(context, "Someone else is already logged in"); + 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)"); } } } diff --git a/server/main.c b/server/main.c index a48c223..7933bab 100644 --- a/server/main.c +++ b/server/main.c @@ -33,6 +33,10 @@ void ParseArguments(int argc, char ** argv) g_options.program = argv[0]; // program name g_options.verbosity = LOGDEBUG; // default log level + // Set the main directory + if (getcwd(g_options.root_dir, sizeof(g_options.root_dir)) == NULL) + Fatal("Couldn't get current working directory - %s", strerror(errno)); + gettimeofday(&(g_options.start_time), NULL); // Start time @@ -85,6 +89,7 @@ void ParseArguments(int argc, char ** argv) 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); if (g_options.auth_uri[0] != '\0') { @@ -128,19 +133,19 @@ int main(int argc, char ** argv) Pin_Init(); // Try and start things - + /* const char *ret; if ((ret = Control_SetMode(CONTROL_START, "test")) != NULL) Fatal("Control_SetMode failed with '%s'", ret); - + */ // run request thread in the main thread FCGI_RequestLoop(NULL); - + /* if ((ret = Control_SetMode(CONTROL_STOP, "test")) != NULL) Fatal("Control_SetMode failed with '%s'", ret); - + */ //Sensor_StopAll(); //Actuator_StopAll(); diff --git a/server/options.h b/server/options.h index 839bc4d..bf668b3 100644 --- a/server/options.h +++ b/server/options.h @@ -31,6 +31,9 @@ typedef struct /** Authentication method **/ enum {AUTH_NONE, AUTH_LDAP, AUTH_SHADOW} auth_method; + /** Starting directory **/ + char root_dir[BUFSIZ]; + } Options; diff --git a/server/parameters b/server/parameters index 2fa6890..da48239 100644 --- a/server/parameters +++ b/server/parameters @@ -22,7 +22,8 @@ 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="/etc/shadow" +auth_uri="shadow" # Set to the dn of the LDAP server ldap_base_dn="ou=People,dc=daedalus" # Testing diff --git a/server/sensor.c b/server/sensor.c index 9c3d2b4..e136988 100644 --- a/server/sensor.c +++ b/server/sensor.c @@ -45,8 +45,6 @@ int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn s->name = name; s->read = read; // Set read function s->init = init; // Set init function - if (init != NULL) - init(name, user_id); // Call it // Start by averaging values taken over a second s->sample_us = 1e6; @@ -54,6 +52,14 @@ int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn // Set sanity function s->sanity = sanity; + + if (init != NULL) + { + if (!init(name, user_id)) + Fatal("Couldn't init sensor %s", name); + } + + return g_num_sensors; } diff --git a/server/shadow b/server/shadow new file mode 100644 index 0000000..49c685d --- /dev/null +++ b/server/shadow @@ -0,0 +1,3 @@ +admin:$1$Pga9JAbx$FvYbRAxzx6lLl6IGIxMsF./:admin@snoopy.com:0 +user1:$1$DY5atz7F$XsLeojV/lL1uiLAkvR8Xm0.:user1@hatemail.com:1 +user2:$1$ahKf1yrT$Dksl6fXzVfzJjdc55gkSZ.:user2@gratemail.com:1 -- 2.20.1