static int EscalateTestMockHelperProcess(int stdin_fd, int stdout_fd) { GIOChannel *stdin_stream = g_io_channel_unix_new(stdin_fd); GIOChannel *stdout_stream = g_io_channel_unix_new(stdout_fd); GError *error = NULL; gboolean success = FALSE; g_assert(mock_helper_expected_messages); g_assert(mock_helper_response_messages); for (guint i = 0; i < g_strv_length(mock_helper_expected_messages); i++) { EscalateMessage *expected = NULL; EscalateMessage *message = NULL; EscalateMessage *response = NULL; expected = EscalateMessageLoad(mock_helper_expected_messages[i], &error); g_assert_no_error(error); g_assert(expected); message = EscalateMessageRead(stdin_stream, &error); g_assert_no_error(error); g_assert(message); if (mock_helper_response_messages[i]) { response = EscalateMessageLoad(mock_helper_response_messages[i], &error); g_assert_no_error(error); g_assert(response); } g_assert_cmpint(expected->type, ==, message->type); if (!g_variant_equal(expected->values, message->values)) { gchar *variant_str = g_variant_print(expected->values, FALSE); g_message("Expected: %s", variant_str); g_free(variant_str); variant_str = g_variant_print(message->values, FALSE); g_message("Got: %s", variant_str); g_free(variant_str); g_error("Message values didn't match what was expected"); } if (response) { success = EscalateMessageWrite(response, stdout_stream, &error); g_assert_no_error(error); g_assert(success); EscalateMessageUnref(response); } EscalateMessageUnref(expected); EscalateMessageUnref(message); } g_io_channel_shutdown(stdin_stream, FALSE, NULL); g_io_channel_shutdown(stdout_stream, FALSE, NULL); g_io_channel_unref(stdin_stream); g_io_channel_unref(stdout_stream); return 0; }
static void AssertMessage(GIOChannel *channel, const gchar *expected_str) { GError *error = NULL; EscalateMessage *expected = EscalateMessageLoad(expected_str, &error); g_assert_no_error(error); g_assert(expected); EscalateMessage *message = EscalateMessageRead(channel, &error); g_assert_no_error(error); g_assert(message); g_assert_cmpint(expected->type, ==, message->type); g_assert(g_variant_equal(expected->values, message->values)); EscalateMessageUnref(expected); EscalateMessageUnref(message); }
/** * EscalateHelperPrompt: * @self: #EscalateHelper instance. * @conv_request: Conversation message to send. * @conv_response: Conversation response contents received. * * Returns: PAM_SUCCESS if one message was sent and response was read, or * PAM_CONV_ERROR on failure. Error are logged with pam_syslog. */ static int EscalateHelperPrompt(EscalateHelper *self, const struct pam_message *conv_request, struct pam_response *conv_response) { EscalateMessage *request = NULL; GError *error = NULL; EscalateMessage *response = NULL; gchar *response_msg = NULL; int response_retcode = 0; int result = PAM_CONV_ERR; request = EscalateMessageNew(ESCALATE_MESSAGE_TYPE_CONV_MESSAGE, conv_request->msg_style, conv_request->msg); if (!EscalateMessageWrite(request, self->writer, &error)) { pam_syslog(self->pamh, LOG_WARNING, "Failed to write conversation request: %s", error->message); g_clear_error(&error); goto done; } response = EscalateHelperRecv(self, ESCALATE_MESSAGE_TYPE_CONV_RESPONSE, &error); if (!response) { pam_syslog(self->pamh, LOG_WARNING, "Failed to read conversation response: %s", error->message); g_clear_error(&error); goto done; } EscalateMessageGetValues(response, &response_msg, &response_retcode); if (response_msg) { conv_response->resp = strdup(response_msg); } else { conv_response->resp = NULL; } conv_response->resp_retcode = response_retcode; result = PAM_SUCCESS; done: if (request) EscalateMessageUnref(request); if (response) EscalateMessageUnref(response); g_free(response_msg); return result; }
/** * EscalateModuleHandleNext: * @self: PAM module that's receiving messages from the helper process. * @error: (out)(allow-none): Error return location or #NULL. * * Returns: #TRUE if a message was read and handled without an error. */ gboolean EscalateModuleHandleNext(EscalateModule *self, GError **error) { EscalateMessage *message = NULL; gboolean result = FALSE; message = EscalateSubprocessRecv(self->child, error); if (!message) goto done; switch (EscalateMessageGetType(message)) { case ESCALATE_MESSAGE_TYPE_CONV_MESSAGE: result = EscalateModuleHandleConversation(self, message, error); break; case ESCALATE_MESSAGE_TYPE_FINISH: result = EscalateModuleHandleFinish(self, message, error); break; default: g_set_error(error, ESCALATE_MODULE_ERROR, ESCALATE_MODULE_ERROR_MESSAGE_TYPE, "Unexpected message type: %d", EscalateMessageGetType(message)); break; } done: if (message) EscalateMessageUnref(message); return result; }
/** * EscalateModuleStart: * @self: PAM module being described in the start message. * @action: Action being called on this module, like * #ESCALATE_MESSAGE_ACTION_AUTHENTICATE when pam_sm_authenticate() is being * used. * @error: (out)(allow-none): Error return location or #NULL. * * Returns: #TRUE if the message was sent. */ gboolean EscalateModuleStart(EscalateModule *self, EscalateMessageAction action, GError **error) { GVariantBuilder *items = NULL; EscalateMessage *message = NULL; gboolean result = FALSE; items = g_variant_builder_new(G_VARIANT_TYPE("a{ims}")); for (guint i = 0; i < G_N_ELEMENTS(escalate_module_include_items); i++) { gint item = escalate_module_include_items[i]; if (!EscalateModuleStartAddItem(self, items, item, error)) { goto done; } } // TODO(vonhollen): Include environment variables? message = EscalateMessageNew(ESCALATE_MESSAGE_TYPE_START, action, self->flags, self->username, items); if (EscalateSubprocessSend(self->child, message, error)) result = TRUE; done: if (message) EscalateMessageUnref(message); g_variant_builder_unref(items); return result; }
static void TestWrite(gconstpointer user_data) { guint index = GPOINTER_TO_UINT(user_data); gint fds [] = { 0, 0 }; GIOChannel *reader = NULL; GIOChannel *writer = NULL; GThread *thread = NULL; EscalateMessage *message = NULL; GError *error = NULL; gboolean success = FALSE; gchar *result = NULL; g_assert(g_unix_open_pipe(fds, 0, NULL)); reader = g_io_channel_unix_new(fds[0]); writer = g_io_channel_unix_new(fds[1]); thread = g_thread_new("Reader", (GThreadFunc) ReaderThread, reader); message = EscalateMessageLoad(example_messages[index], NULL); g_assert(message); success = EscalateMessageWrite(message, writer, &error); g_assert_no_error(error); g_assert(success); g_assert_cmpint(G_IO_STATUS_NORMAL, ==, g_io_channel_shutdown(writer, TRUE, NULL)); result = (gchar *) g_thread_join(thread); result[strlen(result)-1] = '\0'; // Cut off newline. g_assert_cmpstr(example_messages[index], ==, result); g_io_channel_unref(reader); g_io_channel_unref(writer); EscalateMessageUnref(message); g_free(result); }
static void WriteMessage(GIOChannel *channel, const gchar *message_str) { GError *error = NULL; EscalateMessage *message = EscalateMessageLoad(message_str, &error); g_assert_no_error(error); g_assert(message); gboolean success = EscalateMessageWrite(message, channel, &error); g_assert_no_error(error); g_assert(success); EscalateMessageUnref(message); }
static void TestDump(gconstpointer user_data) { guint index = GPOINTER_TO_UINT(user_data); GError *error = NULL; EscalateMessage *message = EscalateMessageLoad(example_messages[index], &error); g_assert_no_error(error); g_assert(message); gchar *message_str = EscalateMessageDump(message); g_assert_cmpstr(example_messages[index], ==, message_str); g_free(message_str); EscalateMessageUnref(message); }
static void TestLoad(gconstpointer user_data) { guint index = GPOINTER_TO_UINT(user_data); GError *error = NULL; GVariant *expected_values = CreateExampleMessageValues(index); EscalateMessage *message = EscalateMessageLoad(example_messages[index], &error); g_assert_no_error(error); g_assert(message); g_assert_cmpint(EscalateMessageGetType(message), ==, example_message_types[index]); g_assert(g_variant_equal(expected_values, message->values)); g_variant_unref(expected_values); EscalateMessageUnref(message); }
/** * EscalateHelperRecv: * @self: #EscalateHelper instance. * @type: Only accept a message with this type. * @error: (out)(allow-none): Error return location or #NULL. * * Returns: New #EscalateMessage instance, or #NULL on error. */ static EscalateMessage *EscalateHelperRecv(EscalateHelper *self, EscalateMessageType type, GError **error) { EscalateMessage *message = EscalateMessageRead(self->reader, error); if (!message) return NULL; if (EscalateMessageGetType(message) == type) return message; g_set_error(error, ESCALATE_HELPER_ERROR, ESCALATE_HELPER_ERROR_UNEXPECTED_MESSAGE_TYPE, "Expected message type %d but got %d instead", type, EscalateMessageGetType(message)); EscalateMessageUnref(message); return NULL; }
/** * EscalateHelperDoAction: * @self: #EscalateHelper instance. * @error: (out)(allow-none): Error return location or #NULL. * * Return: #TRUE if the action specified in the start message (just * pam_authenticate for now) was called successfully and the finish message was * sent. */ gboolean EscalateHelperDoAction(EscalateHelper *self, GError **error) { int setcred_result = PAM_SUCCESS; EscalateMessage *message = NULL; GVariantBuilder *env = NULL; gboolean result = FALSE; // Run the action specified in the start message. switch (self->action) { case ESCALATE_MESSAGE_ACTION_AUTHENTICATE: self->result = pam_authenticate(self->pamh, self->flags); if (self->result == PAM_SUCCESS || self->result == PAM_NEW_AUTHTOK_REQD) { // Refresh things like Kerberos credentials. This is safe to do here // even if the client never calls pam_setcred() because the entire auth // stack succeeded. // TODO(vonhollen): Make this configurable by pam_escalate.so. setcred_result = pam_setcred(self->pamh, PAM_REINITIALIZE_CRED); if (setcred_result != PAM_SUCCESS) { pam_syslog(self->pamh, LOG_NOTICE, "pam_setcred() failed for user '%s': %s", self->username, pam_strerror(self->pamh, setcred_result)); } } break; default: self->result = PAM_SYSTEM_ERR; g_error("Unsupported action %d", self->action); } // Prevent this function from being run twice. self->action = ESCALATE_MESSAGE_ACTION_UNKNOWN; // Get the PAM environment to include in the result. env = EscalateUtilPamEnvToVariant(self->pamh, error); if (!env) { goto done; } // Send the final PAM result for the action and the complete environment. message = EscalateMessageNew(ESCALATE_MESSAGE_TYPE_FINISH, self->result, env); result = EscalateMessageWrite(message, self->writer, error); EscalateMessageUnref(message); g_variant_builder_unref(env); done: return result; }
/** * EscalateModuleHandleConversation: * @self: PAM module to forward conversation messages to. * @message: Conversation message from the helper process holding the same * values as one "struct pam_message". * @error: (out)(allow-none): Error return location or #NULL. * * Returns: #TRUE if a response message was sent back to the helper process. */ static gboolean EscalateModuleHandleConversation(EscalateModule *self, EscalateMessage *message, GError **error) { gchar *conv_message_str = NULL; struct pam_message conv_message = { 0, NULL }; const struct pam_message *conv_message_array [] = { &conv_message, NULL }; struct pam_response *conv_response = NULL; gint pam_status = PAM_SYSTEM_ERR; EscalateMessage *response = NULL; gboolean result = FALSE; EscalateMessageGetValues(message, &conv_message.msg_style, &conv_message_str); conv_message.msg = conv_message_str; // TODO(vonhollen): Support multiple requests/responses per call. pam_status = self->conv->conv(1, conv_message_array, &conv_response, self->conv->appdata_ptr); if (pam_status != PAM_SUCCESS) { g_set_error(error, ESCALATE_MODULE_ERROR, ESCALATE_MODULE_ERROR_CONV, "Conversation function failed: %s", pam_strerror(self->pamh, pam_status)); goto done; } response = EscalateMessageNew(ESCALATE_MESSAGE_TYPE_CONV_RESPONSE, conv_response[0].resp, conv_response[0].resp_retcode); if (EscalateSubprocessSend(self->child, response, error)) result = TRUE; done: g_free(conv_message_str); if (conv_response) { free(conv_response[0].resp); free(conv_response); } if (response) { EscalateMessageUnref(response); } return result; }
static void TestNew(gconstpointer user_data) { guint index = GPOINTER_TO_UINT(user_data); GVariantBuilder items; GVariantBuilder env; EscalateMessage *message = NULL; GVariant *expected_values = CreateExampleMessageValues(index); switch (index) { case 0: g_variant_builder_init(&items, G_VARIANT_TYPE_ARRAY); g_variant_builder_add(&items, "{ims}", 2, "testuser"); g_variant_builder_init(&env, G_VARIANT_TYPE_ARRAY); g_variant_builder_add(&env, "{ss}", "PATH", "/path"); message = EscalateMessageNew(ESCALATE_MESSAGE_TYPE_START, 1, 0, "testuser", &items, &env); break; case 1: message = EscalateMessageNew(ESCALATE_MESSAGE_TYPE_CONV_MESSAGE, 1, "Password: "******"testpass", 0); break; case 3: g_variant_builder_init(&env, G_VARIANT_TYPE_ARRAY); g_variant_builder_add(&env, "{ss}", "PATH", "/path"); message = EscalateMessageNew(ESCALATE_MESSAGE_TYPE_FINISH, 0, &env); break; default: g_error("No message available for index %d", index); } g_assert(message); g_assert_cmpint(EscalateMessageGetType(message), ==, example_message_types[index]); g_assert(g_variant_equal(expected_values, message->values)); EscalateMessageUnref(message); g_variant_unref(expected_values); }
/** * EscalateHelperDoAction: * @self: #EscalateHelper instance. * @error: (out)(allow-none): Error return location or #NULL. * * Return: #TRUE if the action specified in the start message (just * pam_authenticate for now) was called successfully and the finish message was * sent. */ gboolean EscalateHelperDoAction(EscalateHelper *self, GError **error) { EscalateMessage *message = NULL; gboolean success = FALSE; switch (self->action) { case ESCALATE_MESSAGE_ACTION_AUTHENTICATE: self->result = pam_authenticate(self->pamh, self->flags); break; default: self->result = PAM_SYSTEM_ERR; g_error("Unsupported action %d", self->action); } // Prevents this function from being run twice. self->action = ESCALATE_MESSAGE_ACTION_UNKNOWN; message = EscalateMessageNew(ESCALATE_MESSAGE_TYPE_FINISH, self->result); success = EscalateMessageWrite(message, self->writer, error); EscalateMessageUnref(message); return success; }
static void TestRead(gconstpointer user_data) { guint index = GPOINTER_TO_UINT(user_data); gint fds [] = { 0, 0 }; GIOChannel *reader = NULL; WriterThreadContext thread_ctx = { NULL, NULL }; GThread *thread = NULL; GError *error = NULL; EscalateMessage *message = NULL; GVariant *expected_values = CreateExampleMessageValues(index); g_assert(g_unix_open_pipe(fds, 0, NULL)); reader = g_io_channel_unix_new(fds[0]); thread_ctx.channel = g_io_channel_unix_new(fds[1]); thread_ctx.contents = example_messages[index]; thread = g_thread_new("Writer", (GThreadFunc) WriterThread, &thread_ctx); g_assert(thread); message = EscalateMessageRead(reader, &error); g_assert_no_error(error); g_assert(message); g_assert_cmpint(EscalateMessageGetType(message), ==, example_message_types[index]); g_assert(g_variant_equal(expected_values, message->values)); EscalateMessageUnref(message); message = EscalateMessageRead(reader, &error); g_assert_error(error, ESCALATE_MESSAGE_ERROR, ESCALATE_MESSAGE_ERROR_EOF); g_assert(!message); g_error_free(error); g_thread_join(thread); g_variant_unref(expected_values); g_io_channel_shutdown(reader, FALSE, NULL); g_io_channel_unref(reader); g_io_channel_unref(thread_ctx.channel); }
static void TestGetValues(gconstpointer user_data) { guint index = GPOINTER_TO_UINT(user_data); EscalateMessage *message = NULL; gint action = 0; gint flags = 0; gchar *username = NULL; gchar *value = NULL; gint item_key = 0; gchar *item_value = NULL; GVariantIter *items_iter = NULL; gchar *env_key = NULL; gchar *env_value = NULL; GVariantIter *env_iter = NULL; message = EscalateMessageLoad(example_messages[index], NULL); g_assert(message); switch (index) { case 0: EscalateMessageGetValues(message, &action, &flags, &username, &items_iter, &env_iter); g_assert_cmpint(1, ==, action); g_assert_cmpint(0, ==, flags); g_assert_cmpstr("testuser", ==, username); g_assert(g_variant_iter_next(items_iter, "{ims}", &item_key, &item_value)); g_assert_cmpint(2, ==, item_key); g_assert_cmpstr("testuser", ==, item_value); g_assert(!g_variant_iter_next(items_iter, "{ims}", NULL, NULL)); g_variant_iter_free(items_iter); g_assert(g_variant_iter_next(env_iter, "{ss}", &env_key, &env_value)); g_assert_cmpstr("PATH", ==, env_key); g_assert_cmpstr("/path", ==, env_value); g_assert(!g_variant_iter_next(env_iter, "{ss}", NULL, NULL)); g_variant_iter_free(env_iter); break; case 1: EscalateMessageGetValues(message, &flags, &value); g_assert_cmpint(1, ==, flags); g_assert_cmpstr("Password: "******"testpass", ==, value); g_assert_cmpint(0, ==, flags); break; case 3: EscalateMessageGetValues(message, &flags, &env_iter); g_assert_cmpint(0, ==, flags); g_assert(g_variant_iter_next(env_iter, "{ss}", &env_key, &env_value)); g_assert_cmpstr("PATH", ==, env_key); g_assert_cmpstr("/path", ==, env_value); g_assert(!g_variant_iter_next(env_iter, "{ss}", NULL, NULL)); g_variant_iter_free(env_iter); break; default: g_error("No message available for index %d", index); } g_free(username); g_free(value); g_free(item_value); g_free(env_key); g_free(env_value); EscalateMessageUnref(message); }
/** * EscalateHelperHandleStart: * @self: #EscalateHelper instance. * @error: (out)(allow-none): Error return location or #NULL. * * Return: #TRUE if pam_start() was called and it's OK to call * EscalateHelperDoAction(). #FALSE if there was an error and the finish message * was sent. */ gboolean EscalateHelperHandleStart(EscalateHelper *self, GError **error) { EscalateMessage *message = NULL; EscalateMessage *response = NULL; GVariantIter *items = NULL; GVariantIter *env = NULL; int pam_status = PAM_SYSTEM_ERR; int item_type = -1; const gchar *item_value = NULL; gboolean result = FALSE; // Support EscalateHelperHandleStart() being called multiple times. self->action = ESCALATE_MESSAGE_ACTION_UNKNOWN; self->flags = 0; g_free(self->username); self->username = NULL; self->result = PAM_SYSTEM_ERR; message = EscalateHelperRecv(self, ESCALATE_MESSAGE_TYPE_START, error); if (!message) goto done; EscalateMessageGetValues(message, &self->action, &self->flags, &self->username, &items, &env); if (!EscalateHelperIsUserAllowed(self, error)) goto done; // TODO(vonhollen): Safely allow calls to multiple services. pam_status = pam_start(ESCALATE_SERVICE_NAME, self->username, &self->conv, &self->pamh); if (pam_status != PAM_SUCCESS) { g_set_error(error, ESCALATE_HELPER_ERROR, ESCALATE_HELPER_ERROR_START_FAILED, "Failed to start PAM session: %s", pam_strerror(self->pamh, pam_status)); goto done; } while (g_variant_iter_loop(items, "{im&s}", &item_type, &item_value)) { if (!EscalateHelperIsSafeItem(item_type)) { g_set_error(error, ESCALATE_HELPER_ERROR, ESCALATE_HELPER_ERROR_UNSUPPORTED_ITEM, "Item type %d is not supported", item_type); goto done; } pam_status = pam_set_item(self->pamh, item_type, item_value); if (pam_status != PAM_SUCCESS) { g_set_error(error, ESCALATE_HELPER_ERROR, ESCALATE_HELPER_ERROR_SET_ITEM_FAILED, "Failed to set item type %d to '%s'", item_type, item_value); goto done; } } result = EscalateUtilPamEnvFromVariant(self->pamh, env, error); done: if (!result) { response = EscalateMessageNew(ESCALATE_MESSAGE_TYPE_FINISH, PAM_SYSTEM_ERR, NULL); EscalateMessageWrite(response, self->writer, NULL); EscalateMessageUnref(response); } if (items) g_variant_iter_free(items); if (env) g_variant_iter_free(env); if (message) EscalateMessageUnref(message); return result; }