Work on user based control
authorSam Moore <[email protected]>
Mon, 14 Oct 2013 14:32:02 +0000 (22:32 +0800)
committerSam Moore <[email protected]>
Mon, 14 Oct 2013 14:32:02 +0000 (22:32 +0800)
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
server/actuators/filetest.c
server/control.c
server/fastcgi.c
server/fastcgi.h
server/login.c
server/main.c
server/options.h
server/parameters
server/sensor.c
server/shadow [new file with mode: 0644]

index e650cf8..9b9a1a8 100644 (file)
@@ -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;
index 697753a..8dcc9df 100644 (file)
@@ -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)
index d9a7dc6..c6b5823 100644 (file)
@@ -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 <dirent.h>
 
 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;
index cdaef79..a77a3a4 100644 (file)
@@ -9,6 +9,8 @@
 #include <fcgi_stdio.h>
 #include <openssl/sha.h>
 #include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 #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))
                                {
index dd89594..85dab57 100644 (file)
@@ -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);
index a2a11e9..78e31f9 100644 (file)
 
 /**
  * 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)");
                }
        }
 }
index a48c223..7933bab 100644 (file)
@@ -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();
 
index 839bc4d..bf668b3 100644 (file)
@@ -31,6 +31,9 @@ typedef struct
        /** Authentication method **/
        enum {AUTH_NONE, AUTH_LDAP, AUTH_SHADOW} auth_method;
 
+       /** Starting directory **/
+       char root_dir[BUFSIZ];
+
        
 
 } Options;
index 2fa6890..da48239 100644 (file)
@@ -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
index 9c3d2b4..e136988 100644 (file)
@@ -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 (file)
index 0000000..49c685d
--- /dev/null
@@ -0,0 +1,3 @@
+admin:$1$Pga9JAbx$FvYbRAxzx6lLl6IGIxMsF./:[email protected]:0
+user1:$1$DY5atz7F$XsLeojV/lL1uiLAkvR8Xm0.:[email protected]:1
+user2:$1$ahKf1yrT$Dksl6fXzVfzJjdc55gkSZ.:[email protected]:1

UCC git Repository :: git.ucc.asn.au