#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace boost::locale; enum class Type { Howdy, Pam }; /** * Inspect the status code returned by the compare process * @param code The status code * @param conv_function The PAM conversation function * @return A PAM return code */ int on_howdy_auth(int code, function conv_function) { // If the process has exited if (!WIFEXITED(code)) { // Get the status code returned code = WEXITSTATUS(code); switch (code) { // Status 10 means we couldn't find any face models case 10: conv_function(PAM_ERROR_MSG, dgettext("pam", "There is no face model known")); syslog(LOG_NOTICE, "Failure, no face model known"); break; // Status 11 means we exceded the maximum retry count case 11: syslog(LOG_INFO, "Failure, timeout reached"); break; // Status 12 means we aborted case 12: syslog(LOG_INFO, "Failure, general abort"); break; // Status 13 means the image was too dark case 13: conv_function(PAM_ERROR_MSG, dgettext("pam", "Face detection image too dark")); syslog(LOG_INFO, "Failure, image too dark"); break; // Otherwise, we can't describe what happened but it wasn't successful default: conv_function(PAM_ERROR_MSG, string(dgettext("pam", "Unknown error:") + to_string(code)).c_str()); syslog(LOG_INFO, "Failure, unknown error %d", code); } } // As this function is only called for error status codes, signal an error to // PAM return PAM_AUTH_ERR; } /** * Format and send a message to PAM * @param conv PAM conversation function * @param type Type of PAM message * @param message String to show the user * @return Returns the conversation function return code */ int send_message(function conv, int type, const char *message) { // Formet the message as PAM expects it // No need to free this, it's allocated on the stack const struct pam_message msg = {.msg_style = type, .msg = message}; const struct pam_message *msgp = &msg; // Create a variable for the response to be stored in struct pam_response res_ = {}; struct pam_response *resp_ = &res_; // Call the conversation function with the constructed arguments return conv(1, &msgp, &resp_, nullptr); } /** * The main function, runs the identification and authentication * @param pamh The handle to interface directly with PAM * @param flags Flags passed on to us by PAM, XORed * @param argc Amount of rules in the PAM config (disregared) * @param argv Options defined in the PAM config * @param auth_tok True if we should ask for a password too * @return Returns a PAM return code */ int identify(pam_handle_t *pamh, int flags, int argc, const char **argv, bool auth_tok) { // Open and read the config file INIReader reader("/lib/security/howdy/config.ini"); // Open the system log so we can write to it openlog("pam_howdy", 0, LOG_AUTHPRIV); // Will contain PAM conversation function struct pam_conv *conv = nullptr; // Will contain the responses from PAM functions int pam_res = PAM_IGNORE; // Try to get the conversation function and error out if we can't if ((pam_res = pam_get_item(pamh, PAM_CONV, (const void **)&conv)) != PAM_SUCCESS) { syslog(LOG_ERR, "Failed to acquire conversation"); return pam_res; } // Wrap the PAM conversation function in our own, easier function auto conv_function = bind(send_message, conv->conv, placeholders::_1, placeholders::_2); // Error out if we could not ready the config file if (reader.ParseError() < 0) { syslog(LOG_ERR, "Failed to parse the configuration file"); return PAM_SYSTEM_ERR; } // Stop executing if Howdy has been disabled in the config if (reader.GetBoolean("core", "disabled", false)) { syslog(LOG_INFO, "Skipped authentication, Howdy is disabled"); return PAM_AUTHINFO_UNAVAIL; } // Stop if we're in a remote shell and configured to exit if (reader.GetBoolean("core", "ignore_ssh", true)) { if (getenv("SSH_CONNECTION") != nullptr || getenv("SSH_CLIENT") != nullptr || getenv("SSHD_OPTS") != nullptr) { syslog(LOG_INFO, "Skipped authentication, SSH session detected"); return PAM_AUTHINFO_UNAVAIL; } } // Try to detect the laptop lid state and stop if it's closed if (reader.GetBoolean("core", "ignore_closed_lid", true)) { glob_t glob_result{}; // Get any files containing lid state int return_value = glob("/proc/acpi/button/lid/*/state", 0, nullptr, &glob_result); // TODO: We ignore the result if (return_value != 0) { globfree(&glob_result); } // For each lid status file found for (size_t i = 0; i < glob_result.gl_pathc; ++i) { // Read the file contents ifstream file(string(glob_result.gl_pathv[i])); string lid_state; getline(file, lid_state, (char)file.eof()); // Stop if the lid is closed if (lid_state.find("closed") != std::string::npos) { globfree(&glob_result); syslog(LOG_INFO, "Skipped authentication, closed lid detected"); return PAM_AUTHINFO_UNAVAIL; } } globfree(&glob_result); } // If enabled, send a notice to the user that facial login is being attempted if (reader.GetBoolean("core", "detection_notice", false)) { if ((pam_res = conv_function(PAM_TEXT_INFO, dgettext("pam", "Attempting facial authentication"))) != PAM_SUCCESS) { syslog(LOG_ERR, "Failed to send detection notice"); } } // Get the username from PAM, needed to match correct face model char *user_ptr = nullptr; if ((pam_res = pam_get_user(pamh, (const char **)&user_ptr, nullptr)) != PAM_SUCCESS) { syslog(LOG_ERR, "Failed to get username"); return pam_res; } posix_spawn_file_actions_t file_actions; posix_spawn_file_actions_init(&file_actions); // We close stdout and stderr for the child posix_spawn_file_actions_addclose(&file_actions, STDOUT_FILENO); posix_spawn_file_actions_addclose(&file_actions, STDERR_FILENO); // Prepare the python command that will run the facial authentication const char *const args[] = {"/usr/bin/python3", "/lib/security/howdy/compare.py", user_ptr, nullptr}; pid_t child_pid; // Start the python subprocess if (posix_spawnp(&child_pid, "/usr/bin/python3", &file_actions, nullptr, (char *const *)args, nullptr) < 0) { syslog(LOG_ERR, "Can't spawn the howdy process: %s", strerror(errno)); return PAM_SYSTEM_ERR; } std::mutex m; std::condition_variable cv; Type t; packaged_task child_task([&] { int status; wait(&status); { unique_lock lk(m); t = Type::Howdy; } cv.notify_all(); return status; }); auto child_future = child_task.get_future(); thread child_thread(move(child_task)); packaged_task pass_task([&] { char *auth_tok_ptr = nullptr; int pam_res = pam_get_authtok(pamh, PAM_AUTHTOK, (const char **)&auth_tok_ptr, nullptr); { unique_lock lk(m); t = Type::Pam; } cv.notify_all(); return pam_res; }); auto pass_future = pass_task.get_future(); thread pass_thread; if (auth_tok) { pass_thread = thread(move(pass_task)); } { unique_lock lk(m); cv.wait(lk); } if (t == Type::Howdy) { if (auth_tok) { // We cancel the thread using pthread, pam_get_authtok seems to be a // cancellation point auto native_hd = pass_thread.native_handle(); pthread_cancel(native_hd); pass_thread.join(); } child_thread.join(); int howdy_status = child_future.get(); if (howdy_status == 0) { if (!reader.GetBoolean("core", "no_confirmation", true)) { // Construct confirmation text from i18n string string confirm_text = dgettext("pam", "Identified face as {}"); string identify_msg(confirm_text.replace(confirm_text.find("{}"), 2, string(user_ptr))); // Send confirmation message to user conv_function(PAM_TEXT_INFO, identify_msg.c_str()); } syslog(LOG_INFO, "Login approved"); return PAM_SUCCESS; } else { return on_howdy_auth(howdy_status, conv_function); } } else { kill(child_pid, SIGTERM); pass_thread.join(); auto pam_res = pass_future.get(); if (pam_res != PAM_SUCCESS) return pam_res; return PAM_IGNORE; } } // Called by PAM when a user needs to be authenticated, for example by running // the sudo command PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { return identify(pamh, flags, argc, argv, true); } // Called by PAM when a session is started, such as by the su command PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { return identify(pamh, flags, argc, argv, false); } // The functions below are required by PAM, but not needed in this module PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_IGNORE; } PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_IGNORE; } PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_IGNORE; } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_IGNORE; }