use libudev instead of a udev rule and a script.
authorLoïc Collignon <loic.collignon@iot.bzh>
Mon, 31 Jul 2017 16:14:46 +0000 (18:14 +0200)
committerLoïc Collignon <loic.collignon@iot.bzh>
Mon, 31 Jul 2017 16:14:46 +0000 (18:14 +0200)
Change-Id: Id19f4cd48d525621adc9ded9fc9e08d856dc08a5
Signed-off-by: Loïc Collignon <loic.collignon@iot.bzh>
ll-auth-binding/conf.d/cmake/config.cmake
ll-auth-binding/src/CMakeLists.txt
ll-auth-binding/src/ll-auth-binding.c

index f46ce4c..d4c141b 100644 (file)
@@ -64,8 +64,13 @@ set (PKG_REQUIRED_LIST
        libsystemd>=222
        afb-daemon
        libmicrohttpd>=0.9.55
+       libudev
 )
 
+set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
+set(THREADS_PREFER_PTHREAD_FLAG TRUE)
+find_package(Threads REQUIRED)
+
 # Static constante definition
 # -----------------------------
 add_compile_options()
index 2b682bd..cdf131c 100644 (file)
@@ -14,7 +14,7 @@ set_target_properties(ll-auth-binding PROPERTIES
        LINK_FLAGS ${BINDINGS_LINK_FLAG}
        OUTPUT_NAME ${TARGET_NAME})
 
-target_link_libraries(ll-auth-binding ${link_libraries} ${PAM_LIBRARY} ${PAM_MISC_LIBRARY})
+target_link_libraries(ll-auth-binding ${link_libraries} ${PAM_LIBRARY} ${PAM_MISC_LIBRARY} Threads::Threads)
 
 install(
        TARGETS ll-auth-binding
index 78e9733..37c47e0 100644 (file)
 #include <json-c/json.h>
 #include <security/pam_appl.h>
 #include <security/pam_misc.h>
+#include <libudev.h>
+#include <pthread.h>
+#include <poll.h>
 
 #define AFB_BINDING_VERSION 2
 #include <afb/afb-binding.h>
 
-static struct pam_conv conv = { misc_conv, NULL };
+// Defines
+#define PAM_RULE                                                                                                       "agl"
+#define UDEV_MONITOR_POLLING_TIMEOUT                                                           5000
 
-static char* current_device = NULL;
-static char* current_user = NULL;
+#define UDEV_ACTION_UNSUPPORTED                                                                                0
+#define UDEV_ACTION_ADD                                                                                                1
+#define UDEV_ACTION_REMOVE                                                                                     2
 
-afb_event evt_login, evt_logout, evt_failed;
+#define LOGIN_SUCCESS                                                                                          0
+#define LOGIN_ERROR_USER_LOGGED                                                                                1
+#define LOGIN_ERROR_PAM_START                                                                          2
+#define LOGIN_ERROR_PAM_PUTENV                                                                         3
+#define LOGIN_ERROR_PAM_AUTHENTICATE                                                           4
+#define LOGIN_ERROR_PAM_ACCT_MGMT                                                                      5
+#define LOGIN_ERROR_PAM_NO_USER                                                                                6
+#define LOGIN_ERROR_PAM_END                                                                                    7
 
-/// @brief API's verb 'login'. Try to login a user using a device
-/// @param[in] req The request object. Should contains a json with a "device" key.
-static void verb_login(struct afb_req req)
+// Globals
+static const char* error_messages[] =
 {
-       struct json_object* args = NULL;
-       struct json_object* device_object = NULL;
-       pam_handle_t* pamh;
-       int r;
+       "",
+       "The current user must be logged out first!",
+       "PAM start failed!",
+       "PAM putenv failed!",
+       "PAM authenticate failed!",
+       "PAM acct_mgmt failed!",
+       "No user provided by the PAM module!",
+       "PAM end failed!"
+};
 
-       if (current_user)
+static char*                                   current_device                                          = NULL;
+static char*                                   current_user                                            = NULL;
+static struct pam_conv                 conv                                                            = { misc_conv, NULL };
+static struct udev*                            udev_context                                            = NULL;
+static struct udev_monitor*            udev_mon                                                        = NULL;
+static pthread_t                               udev_monitoring_thread_handle;
+static struct afb_event                        evt_login, evt_logout, evt_failed;
+
+/**
+ * @brief Free the memory associated to the specified string and nullify the pointer.
+ * @param[in] string A pointer to the string.
+ */
+static inline void free_string(char** string)
+{
+       if (string)
        {
-               AFB_ERROR("[login] the current user must be logged out first!");
-               afb_req_fail(req, "current user must be logged out first!", NULL);
-               afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"A user is already logged in!\"}"));
-               return;
+               if (*string) free(*string);
+               *string = NULL;
        }
+}
 
-       args = afb_req_json(req);
-       if (args == NULL || !json_object_object_get_ex(args, "device", &device_object))
+/**
+ * @brief Free the memory associated to the specified UDev's context and nullify the pointer.
+ * @param[in] ctx UDev's context.
+ */
+static inline void free_udev_context(struct udev** ctx)
+{
+       if (ctx)
        {
-               AFB_ERROR("[login] device must be provided!");
-               afb_req_fail(req, "device must be provided!", NULL);
-               afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"device must be provided!\"}"));
-               return;
+               if (*ctx) udev_unref(*ctx);
+               *ctx = NULL;
        }
+}
 
-       const char* device = json_object_get_string(device_object);
-
-       if ((r = pam_start("agl", NULL, &conv, &pamh)) != PAM_SUCCESS)
+/**
+ * @brief Free the memory associated to the specified UDev's monitor and nullify the pointer.
+ * @param[in] mon UDev's monitor.
+ */
+static inline void free_udev_monitor(struct udev_monitor** mon)
+{
+       if (mon)
        {
-               AFB_ERROR("PAM start failed!");
-               afb_req_fail(req, "PAM start failed!", NULL);
-               afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"PAM start failed!\"}"));
-               return;
+               if (*mon) udev_monitor_unref(*mon);
+               *mon = NULL;
        }
+}
+
+/**
+ * @brief Print UDev infos for the specified device.
+ * @param[in] dev The device.
+ */
+static inline void print_udev_device_info(struct udev_device* dev)
+{
+       AFB_INFO("    Action: %s", udev_device_get_action(dev));
+       AFB_INFO("    Node: %s", udev_device_get_devnode(dev));
+       AFB_INFO("    Subsystem: %s", udev_device_get_subsystem(dev));
+       AFB_INFO("    Devtype: %s", udev_device_get_devtype(dev));
+       AFB_INFO("    DevNum: %lu", udev_device_get_devnum(dev));
+       AFB_INFO("    DevPath: %s", udev_device_get_devpath(dev));
+       AFB_INFO("    Driver: %s", udev_device_get_driver(dev));
+       AFB_INFO("    SeqNum: %llu", udev_device_get_seqnum(dev));
+       AFB_INFO("    SysName: %s", udev_device_get_sysname(dev));
+       AFB_INFO("    SysNum: %s", udev_device_get_sysnum(dev));
+       AFB_INFO("    SysPath: %s", udev_device_get_syspath(dev));
+}
 
+/**
+ * @brief Get the UDev's action as an int to allow switch condition.
+ * @param[in] dev The device.
+ */
+static inline int udev_device_get_action_int(struct udev_device* dev)
+{
+       const char* action = udev_device_get_action(dev);
+       return
+               strcmp(action, "add")
+               ? (strcmp(action, "remove") ? UDEV_ACTION_UNSUPPORTED : UDEV_ACTION_REMOVE)
+               : UDEV_ACTION_ADD;
+}
+
+/**
+ * @brief PAM authentication process.
+ * @param[in] pamh The handle to the PAM context.
+ * @param[in] device The device to login.
+ */
+static int pam_process(pam_handle_t* pamh, const char* device)
+{
+       int r;
+       
+       if (!pamh) return LOGIN_ERROR_PAM_START;
+       
        char pam_variable[4096] = "DEVICE=";
        strcat(pam_variable, device);
        
        if ((r = pam_putenv(pamh, pam_variable)) != PAM_SUCCESS)
-       {
-               AFB_ERROR("PAM putenv failed!");
-               afb_req_fail(req, "PAM putenv failed!", NULL);
-               pam_end(pamh, r);
-               afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"PAM putenv failed!\"}"));
-               return;
-       }
+               return LOGIN_ERROR_PAM_PUTENV;
 
        if ((r = pam_authenticate(pamh, 0)) != PAM_SUCCESS)
-       {
-               AFB_ERROR("PAM authenticate failed!");
-               afb_req_fail(req, "PAM authenticate failed!", NULL);
-               pam_end(pamh, r);
-               afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"PAM authenticate failed!\"}"));
-               return;
-       }
+               return LOGIN_ERROR_PAM_AUTHENTICATE;
 
        if ((r = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS)
-       {
-               AFB_ERROR("PAM acct_mgmt failed!");
-               afb_req_fail(req, "PAM acct_mgmt failed!", NULL);
-               pam_end(pamh, r);
-               afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"PAM acct_mgmt failed!\"}"));
-               return;
-       }
+                       return LOGIN_ERROR_PAM_ACCT_MGMT;
 
        const char* pam_user;
        pam_get_item(pamh, PAM_USER, (const void**)&pam_user);
        if (!pam_user)
-       {
-               AFB_ERROR("[login] No user provided by the PAM module!");
-               afb_req_fail(req, "No user provided by the PAM module!", NULL);
-               afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"No user provided by the PAM module!\"}"));
-               return;
-       }
+               return LOGIN_ERROR_PAM_NO_USER;
 
        current_device = strdup(device);
        current_user = strdup(pam_user);
+       
+       return LOGIN_SUCCESS;
+}
 
-       if ((r = pam_end(pamh, r)) != PAM_SUCCESS)
+/**
+ * @brief Login using PAM.
+ * @param[in] device The device to use.
+ * @return Exit code, @c LOGIN_SUCCESS on success.
+ */
+static int login_pam(const char* device)
+{
+       int r;
+       pam_handle_t* pamh;
+       
+       if (current_user)
+               return LOGIN_ERROR_USER_LOGGED;
+
+       if ((r = pam_start("agl", NULL, &conv, &pamh)) != PAM_SUCCESS)
+               return LOGIN_ERROR_PAM_START;
+
+       r = pam_process(pamh, device);
+       if (r != LOGIN_SUCCESS)
        {
-               AFB_ERROR("PAM end failed!");
-               afb_req_fail(req, "PAM end failed!", NULL);
-               afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"PAM end failed!\"}"));
-               return;
+               pam_end(pamh, r);
+               return r;
        }
 
-       AFB_INFO("[login] device: %s, user: %s", current_device, current_user);
-       json_object* result = json_object_new_object();
-       json_object_object_add(result, "device", json_object_new_string(current_device));
-       json_object_object_add(result, "user", json_object_new_string(current_user));
-       afb_req_success(req, NULL, current_device);
-       afb_event_broadcast(evt_login, result);
+       if ((r = pam_end(pamh, r)) != PAM_SUCCESS)
+               return LOGIN_ERROR_PAM_END;
+       
+       return LOGIN_SUCCESS;
 }
 
-/// @brief API's verb 'lgout'. Try to logout a user using a device
-/// @param[in] req The request object. Should contains a json with a "device" key.
-static void verb_logout(struct afb_req req)
+/**
+ * @brief Try to login a user using a device.
+ * @param[in] device The device to use.
+ * @return Exit code, @c LOGIN_SUCCESS if success.
+ */
+static int login(const char* device)
 {
-       struct json_object* args = NULL;
-       struct json_object* device_object = NULL;
-
-       args = afb_req_json(req);
-       if (args == NULL || !json_object_object_get_ex(args, "device", &device_object))
+       int ret;
+       struct json_object* result;
+       
+       result = json_object_new_object();
+       
+       ret = login_pam(device);
+       switch(ret)
        {
-               AFB_INFO("[logout] device must be provided!");
-               afb_req_fail(req, "device must be provided!", NULL);
-               afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"Device must be provided!\"}"));
-               return;
+               case LOGIN_SUCCESS:
+                       json_object_object_add(result, "device", json_object_new_string(current_device));
+                       json_object_object_add(result, "user", json_object_new_string(current_user));
+                       afb_event_broadcast(evt_login, result);
+                       break;
+               default:
+                       json_object_object_add(result, "message", json_object_new_string(error_messages[ret]));
+                       afb_event_broadcast(evt_failed, result);
        }
+       
+       json_object_put(result);
+       return ret;
+}
 
-       const char* device = json_object_get_string(device_object);
+/// @brief Try to logout a user using a device.
+/// @param[in] device The device to logout.
+static void logout(const char* device)
+{
+       struct json_object* result;
+       
+       result = json_object_new_object();
+       
        if (current_device && !strcmp(device, current_device))
        {
-               free(current_device);
-               current_device = NULL;
-               if (current_user)
+               json_object_object_add(result, "device", json_object_new_string(current_device));
+               json_object_object_add(result, "user", json_object_new_string(current_user));
+               AFB_INFO("[logout] device: %s", device);
+               afb_event_broadcast(evt_logout, NULL);
+               
+               free_string(&current_device);
+               free_string(&current_user);
+       }
+       else
+       {
+               json_object_object_add(result, "message", json_object_new_string(current_device));
+               json_object_object_add(result, "user", json_object_new_string("The unplugged device wasn't the user key!"));
+               AFB_INFO("The unplugged device wasn't the user key!");
+               afb_event_broadcast(evt_failed, result);
+       }
+       json_object_put(result);
+}
+
+/**
+ * @brief UDev's monitoring thread.
+ */
+void* udev_monitoring_thread(void* arg)
+{
+       struct udev_device* dev;
+       struct pollfd pfd;
+       int action;
+       
+       pfd.fd = udev_monitor_get_fd(udev_mon);
+       pfd.events = POLLIN;
+       
+       while(1)
+       {
+               if (poll(&pfd, 1, UDEV_MONITOR_POLLING_TIMEOUT))
                {
-                       free(current_user);
-                       current_user = NULL;
-                       AFB_INFO("[logout] device: %s", device);
-                       afb_req_success(req, NULL, device);
-                       afb_event_broadcast(evt_logout, NULL);
-                       return;
+                       dev = udev_monitor_receive_device(udev_mon);
+                       if (dev)
+                       {
+                               if (!strcmp(udev_device_get_devtype(dev), "disk"))
+                               {
+                                       action = udev_device_get_action_int(dev);
+                                       switch(action)
+                                       {
+                                               case UDEV_ACTION_ADD:
+                                                       AFB_INFO("A device is plugged-in");
+                                                       print_udev_device_info(dev);
+                                                       login(udev_device_get_devnode(dev));
+                                                       break;
+                                               case UDEV_ACTION_REMOVE:
+                                                       AFB_INFO("A device is plugged-out");
+                                                       print_udev_device_info(dev);
+                                                       logout(udev_device_get_devnode(dev));
+                                                       break;
+                                               default:
+                                                       AFB_DEBUG("Unsupported udev action");
+                                                       break;
+                                       }
+                               }
+                               udev_device_unref(dev);
+                       }
+                       else
+                       {
+                               AFB_ERROR("No Device from udev_monitor_receive_device().");
+                       }
                }
                else
                {
-                       AFB_INFO("No user was linked to this device!");
-                       afb_req_fail(req, "No user was linked to this device!", NULL);
-                       afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"No user was linked to this device!\"}"));
-                       return;
+                       AFB_DEBUG("Udev polling timeout");
                }
        }
-
-       AFB_INFO("The unplugged device wasn't the user key!");
-       afb_req_fail(req, "The unplugged device wasn't the user key!", NULL);
-       afb_event_broadcast(evt_failed, json_tokener_parse("{\"message\":\"The unplugged device wasn't the user key!\"}"));
+       return NULL;
 }
 
+/**
+ * @brief API's verb 'getuser'. Try to get user informations.
+ * @param[in] req The request object.
+ */
 static void verb_getuser(struct afb_req req)
 {
        if (!current_device || !current_user)
@@ -169,36 +313,54 @@ static void verb_getuser(struct afb_req req)
        afb_req_success(req, result, NULL);
 }
 
+/**
+ * @brief Do the cleanup when init fails.
+ * @param[in] error Error message.
+ * @param[in] retcode Error code to return.
+ * @return An exit code equals to @c retcode.
+ */
+static inline int ll_auth_init_cleanup(const char* error, int retcode)
+{
+       AFB_ERROR_V2("%s", error);
+       free_string(&current_user);
+       free_string(&current_device);
+       
+       free_udev_monitor(&udev_mon);
+       free_udev_context(&udev_context);
+       return retcode;
+}
+
+/**
+ * @brief Initialize the binding.
+ */
 int ll_auth_init()
 {
-       current_user = NULL;
-       current_device = NULL;
        evt_login = afb_daemon_make_event("login");
        evt_logout = afb_daemon_make_event("logout");
        evt_failed = afb_daemon_make_event("failed");
 
-       if (afb_event_is_valid(evt_login) && afb_event_is_valid(evt_logout))
-               return 0;
-
-       AFB_ERROR("Can't create events");
-               return -1;
+       if (!afb_event_is_valid(evt_login) || !afb_event_is_valid(evt_logout) || !afb_event_is_valid(evt_failed))
+               return ll_auth_init_cleanup("Can't create events", -1);
+               
+       udev_context = udev_new();
+       if (!udev_context)
+               return ll_auth_init_cleanup("Can't initialize udev's context", -1);
+       
+       udev_mon = udev_monitor_new_from_netlink(udev_context, "udev");
+       if (!udev_mon)
+               return ll_auth_init_cleanup("Can't initialize udev's monitor", -1);
+       
+       udev_monitor_filter_add_match_subsystem_devtype(udev_mon, "block", NULL);
+       udev_monitor_enable_receiving(udev_mon);
+       
+       if (pthread_create(&udev_monitoring_thread_handle, NULL, udev_monitoring_thread, NULL))
+               return ll_auth_init_cleanup("Can't start the udev's monitoring thread", -1);
+       
+       AFB_INFO("ll-auth-binding is ready");
+       return 0;
 }
 
 static const afb_verb_v2 _ll_auth_binding_verbs[]= {
-       {
-               .verb = "login",
-               .callback = verb_login,
-               .auth = NULL,
-               .info = NULL,
-               .session = AFB_SESSION_NONE_V2
-       },
-       {
-               .verb = "logout",
-               .callback = verb_logout,
-               .auth = NULL,
-               .info = NULL,
-               .session = AFB_SESSION_NONE_V2
-       },
        {
                .verb = "getuser",
                .callback = verb_getuser,