From e043fd27f0be97ce06a7eced3c2a9740fb9d0493 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Lo=C3=AFc=20Collignon?= Date: Mon, 31 Jul 2017 18:14:46 +0200 Subject: [PATCH] use libudev instead of a udev rule and a script. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Change-Id: Id19f4cd48d525621adc9ded9fc9e08d856dc08a5 Signed-off-by: Loïc Collignon --- ll-auth-binding/conf.d/cmake/config.cmake | 5 + ll-auth-binding/src/CMakeLists.txt | 2 +- ll-auth-binding/src/ll-auth-binding.c | 398 +++++++++++++++++++++--------- 3 files changed, 286 insertions(+), 119 deletions(-) diff --git a/ll-auth-binding/conf.d/cmake/config.cmake b/ll-auth-binding/conf.d/cmake/config.cmake index f46ce4c..d4c141b 100644 --- a/ll-auth-binding/conf.d/cmake/config.cmake +++ b/ll-auth-binding/conf.d/cmake/config.cmake @@ -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() diff --git a/ll-auth-binding/src/CMakeLists.txt b/ll-auth-binding/src/CMakeLists.txt index 2b682bd..cdf131c 100644 --- a/ll-auth-binding/src/CMakeLists.txt +++ b/ll-auth-binding/src/CMakeLists.txt @@ -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 diff --git a/ll-auth-binding/src/ll-auth-binding.c b/ll-auth-binding/src/ll-auth-binding.c index 78e9733..37c47e0 100644 --- a/ll-auth-binding/src/ll-auth-binding.c +++ b/ll-auth-binding/src/ll-auth-binding.c @@ -4,156 +4,300 @@ #include #include #include +#include +#include +#include #define AFB_BINDING_VERSION 2 #include -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(¤t_device); + free_string(¤t_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(¤t_user); + free_string(¤t_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, -- 2.16.6