1 /* vi: set et sw=4 ts=4 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
4 * This file is part of tlm (Tiny Login Manager)
6 * Copyright (C) 2013 Intel Corporation.
8 * Contact: Amarnath Valluri <amarnath.valluri@linux.intel.com>
9 * Jussi Laako <jussi.laako@linux.intel.com>
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this library; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
27 #include <sys/types.h>
31 #include <sys/inotify.h>
34 #include <glib/gstdio.h>
35 #include <glib-unix.h>
36 #include <security/pam_appl.h>
39 #include "tlm-utils.h"
41 #include "tlm-config.h"
42 #include "tlm-config-general.h"
45 g_clear_string (gchar **str)
54 tlm_user_get_name (uid_t user_id)
56 struct passwd *pwent = NULL;
57 struct passwd buf_pwent;
58 gchar *buf = NULL, *tmp = NULL, *pw_name = NULL;
60 glong pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
61 if (pw_size_max <= sizeof(struct passwd))
66 for (; NULL != (tmp = realloc(buf, size)); size*=2)
70 if (ERANGE == getpwuid_r(user_id, &buf_pwent, buf, size, &pwent))
77 pw_name = g_malloc(strlen(pwent->pw_name)+1);
80 memset(pw_name, 0, strlen(pwent->pw_name)+1);
81 memcpy(pw_name, pwent->pw_name, strlen(pwent->pw_name));
92 tlm_user_get_uid (const gchar *username)
95 struct passwd buf_pwent;
98 gchar *buf = NULL, *tmp = NULL;
100 glong pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
101 if (pw_size_max <= sizeof(struct passwd))
106 for (; NULL != (tmp = realloc(buf, size)); size*=2)
110 ret = getpwnam_r(username, &buf_pwent, buf, size, &pwent);
112 pw_uid = pwent->pw_uid;
113 else if (ERANGE == ret)
128 tlm_user_get_gid (const gchar *username)
130 struct passwd *pwent;
131 struct passwd buf_pwent;
134 gchar *buf = NULL, *tmp = NULL;
136 glong pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
137 if (pw_size_max <= sizeof(struct passwd))
142 for (; NULL != (tmp = realloc(buf, size)); size*=2)
146 ret = getpwnam_r(username, &buf_pwent, buf, size, &pwent);
148 pw_gid = pwent->pw_gid;
149 else if (ERANGE == ret)
164 tlm_user_get_home_dir (const gchar *username)
166 struct passwd *pwent = NULL;
167 struct passwd buf_pwent;
168 gchar *buf = NULL, *tmp = NULL, *pw_dir = NULL;
170 glong pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
171 if (pw_size_max <= sizeof(struct passwd))
176 for (; NULL != (tmp = realloc(buf, size)); size*=2)
180 if (ERANGE == getpwnam_r(username, &buf_pwent, buf, size, &pwent))
187 pw_dir = g_malloc(strlen(pwent->pw_dir)+1);
190 memset(pw_dir, 0, strlen(pwent->pw_dir)+1);
191 memcpy(pw_dir, pwent->pw_dir, strlen(pwent->pw_dir));
202 tlm_user_get_shell (const gchar *username)
204 struct passwd *pwent = NULL;
205 struct passwd buf_pwent;
206 gchar *buf = NULL, *tmp = NULL, *pw_shell = NULL;
208 glong pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
209 if (pw_size_max <= sizeof(struct passwd))
214 for (; NULL != (tmp = realloc(buf, size)); size*=2)
218 if (ERANGE == getpwnam_r(username, &buf_pwent, buf, size, &pwent))
225 pw_shell = g_malloc(strlen(pwent->pw_shell)+1);
228 memset(pw_shell, 0, strlen(pwent->pw_shell)+1);
229 memcpy(pw_shell, pwent->pw_shell, strlen(pwent->pw_shell));
240 tlm_utils_delete_dir (
246 if (!dir || !(gdir = g_dir_open(dir, 0, NULL))) {
250 const gchar *fname = NULL;
252 gchar *filepath = NULL;
253 while ((fname = g_dir_read_name (gdir)) != NULL) {
254 if (g_strcmp0 (fname, ".") == 0 ||
255 g_strcmp0 (fname, "..") == 0) {
259 filepath = g_build_filename (dir, fname, NULL);
261 retval = lstat(filepath, &sent);
263 /* recurse the directory */
264 if (S_ISDIR (sent.st_mode)) {
265 retval = (gint)!tlm_utils_delete_dir (filepath);
267 retval = g_remove (filepath);
279 if (g_remove (dir) != 0) {
287 _split_command_line_with_regex(const char *command, GRegex *regex) {
288 gchar **temp_strv = NULL;
289 gchar **temp_iter = NULL, **args_iter = NULL;
292 temp_strv = regex ? g_regex_split (regex, command, G_REGEX_MATCH_NOTEMPTY)
293 : g_strsplit (command, " ", -1);
295 WARN("Failed to split command: %s", command);
299 argv = g_new0 (gchar *, g_strv_length (temp_strv));
300 for (temp_iter = temp_strv, args_iter = argv;
304 gchar *item = g_strstrip (*temp_iter);
306 item_len = strlen (item);
310 if ((item[0] == '\"' && item[item_len - 1] == '\"') ||
311 (item[0] == '\'' && item[item_len - 1] == '\'')) {
312 item[item_len - 1] = '\0';
313 memmove (item, item + 1, item_len - 1);
315 *args_iter = g_strcompress (item);
318 g_strfreev (temp_strv);
324 tlm_utils_split_command_line(const gchar *command) {
325 const gchar *pattern = "('.*?'|\".*?\"|\\S+)";
326 GError *error = NULL;
327 GRegex *regex = NULL;
331 WARN("Cannot pase NULL arguments string");
335 if (!(regex = g_regex_new(pattern, 0, G_REGEX_MATCH_NOTEMPTY, &error))) {
336 WARN("Failed to create regex: %s", error->message);
340 argv = _split_command_line_with_regex (command, regex);
342 g_regex_unref (regex);
348 tlm_utils_split_command_lines (const GList *commands_list) {
349 const gchar *pattern = "('.*?'|\".*?\"|\\S+)";
350 GError *error = NULL;
351 GRegex *regex = NULL;
352 GList *argv_list = NULL;
353 const GList *tmp_list = NULL;
355 if (!commands_list) {
359 if (!(regex = g_regex_new(pattern, 0, G_REGEX_MATCH_NOTEMPTY, &error))) {
360 WARN("Failed to create regex: %s", error->message);
364 for (tmp_list = commands_list; tmp_list; tmp_list = tmp_list->next) {
365 argv_list = g_list_append (argv_list, _split_command_line_with_regex (
366 (const gchar *)tmp_list->data, regex));
369 g_regex_unref (regex);
376 GHashTable *dir_table; /* { gchar*: GList* } */
377 GHashTable *wd_table; /* { int: const gchar* } */
388 WatchInfo *info = g_slice_new0 (WatchInfo);
390 CRITICAL("g_slice_new0 memory allocation failure");
395 info->userdata = userdata;
396 info->dir_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
397 info->wd_table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
403 _destroy_dir_table_entry (gpointer key, gpointer value, gpointer userdata) {
406 g_list_free_full ((GList *)value, g_free);
410 _destroy_wd_table_entry (gpointer key, gpointer value, gpointer userdata) {
412 int ifd = GPOINTER_TO_INT(userdata);
413 int wd = GPOINTER_TO_INT(key);
415 inotify_rm_watch (ifd, wd);
419 _watch_info_free (WatchInfo *info)
423 if (info->dir_table) {
424 g_hash_table_foreach (info->dir_table, _destroy_dir_table_entry, NULL);
425 g_hash_table_unref (info->dir_table);
427 if (info->wd_table) {
428 g_hash_table_foreach (info->wd_table, _destroy_wd_table_entry,
429 GINT_TO_POINTER(info->ifd));
430 g_hash_table_unref (info->wd_table);
432 if (info->ifd) close(info->ifd);
434 g_slice_free (WatchInfo, info);
444 _add_watch (int ifd, char *file_path, WatchInfo *info) {
445 GList *file_list = NULL;
447 gchar *file_name = NULL;
449 AddWatchResults res = WATCH_FAILED;
451 if (!file_path) return WATCH_FAILED;
453 file_name = g_path_get_basename (file_path);
454 if (!file_name) return WATCH_FAILED;
456 dir = g_path_get_dirname (file_path);
461 if ((file_list = (GList *)g_hash_table_lookup (
462 info->dir_table, (gconstpointer)dir))) {
463 file_list = g_list_append (file_list, file_name);
467 file_list = g_list_append (NULL, file_name);
468 g_hash_table_insert (info->dir_table, g_strdup(dir), file_list);
470 /* add watch on directory if its not existing */
471 if (g_access (dir, 0)) {
472 return _add_watch (ifd, dir, info);
475 DBG("Adding watch for file '%s' in dir '%s'", file_name, dir);
476 if ((wd = inotify_add_watch (ifd, dir, IN_CREATE)) == -1) {
477 gchar strerr_buf[MAX_STRERROR_LEN] = {0,};
478 WARN ("failed to add inotify watch on %s: %s", dir, strerror_r(errno, strerr_buf, MAX_STRERROR_LEN));
480 goto remove_and_return;
483 if (!g_access (file_path, 0)) {
484 /* socket is ready, need not have a inotify watch for this */
485 inotify_rm_watch (ifd, wd);
487 goto remove_and_return;
490 g_hash_table_insert (info->wd_table, GINT_TO_POINTER(wd), dir);
495 g_hash_table_remove (info->dir_table, (gconstpointer)dir);
496 g_list_free_full (file_list, (GDestroyNotify)g_free);
502 _inotify_watcher_cb (gint ifd, GIOCondition condition, gpointer userdata)
504 WatchInfo *info = (WatchInfo *)userdata;
505 struct inotify_event *ie = NULL;
506 gsize size = sizeof (struct inotify_event) + PATH_MAX + 1;
507 guint nwatch = g_hash_table_size (info->wd_table);
509 ie = (struct inotify_event *) g_slice_alloc0(size);
511 CRITICAL("g_slice_alloc0 memory allocation failure");
512 return nwatch ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
516 read (ifd, ie, size) > (ssize_t)sizeof (struct inotify_event)) {
517 GList *file_list = NULL;
518 GList *element = NULL;
519 GList *pending_list = NULL;
520 gboolean is_first = FALSE;
521 gchar *file_path = NULL;
522 const gchar *dir = NULL;
524 dir = (gchar *)g_hash_table_lookup (
525 info->wd_table, GINT_TO_POINTER(ie->wd));
528 file_list = g_hash_table_lookup (info->dir_table, (gconstpointer)dir);
529 element = g_list_find_custom (file_list,
530 (gpointer)ie->name, (GCompareFunc)g_strcmp0);
532 DBG("Ignoring '%s' file creation", ie->name);
535 is_first = (file_list == element);
537 g_free (element->data);
538 file_list = g_list_delete_link(file_list, element);
540 g_hash_table_remove (info->dir_table, dir);
541 g_hash_table_remove (info->wd_table, GINT_TO_POINTER(ie->wd));
542 inotify_rm_watch (ifd, ie->wd);
544 } else if (is_first) {
545 g_hash_table_insert (info->dir_table, g_strdup (dir), file_list);
548 file_path = g_build_filename (dir, ie->name, NULL);
549 if ((pending_list = (GList *)g_hash_table_lookup (info->dir_table,
550 (gconstpointer)file_path)) != NULL) {
553 // as we are about add real inotify watch, first remove from dir_table
554 g_hash_table_steal (info->dir_table, (gconstpointer)file_path);
556 // Add watches to all the files depend on this directory
557 for (tmp = pending_list; tmp; tmp = tmp->next) {
558 gchar *file_name = (gchar *)tmp->data;
559 gchar *new_file_path = g_build_filename (file_path, file_name, NULL);
560 AddWatchResults res = _add_watch (ifd, new_file_path, info);
561 if (res == WATCH_READY) {
563 info->cb (new_file_path, nwatch == 0, NULL, info->userdata);
565 } else if (res == WATCH_ADDED) nwatch++;
567 WARN ("Couldn't add watch on '%s'", new_file_path);
570 g_free (new_file_path);
572 g_list_free (pending_list);
574 DBG("%s", file_path);
575 if (info->cb) info->cb (file_path, nwatch == 0, NULL, info->userdata);
580 g_slice_free1 (size, ie);
582 return nwatch ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
586 _expand_file_path (const gchar *file_path)
589 gchar **tmp_item =NULL;
590 gchar *expanded_path = NULL;
592 if (!file_path) return NULL;
595 * FIXME: we are not considering filename which having \$ in it
597 if (g_strrstr (file_path, "$") == NULL) return g_strdup(file_path);
599 items = g_strsplit (file_path, G_DIR_SEPARATOR_S, -1);
600 /* soemthing wrong in file path */
601 if (!items) { return g_strdup (file_path); }
603 for (tmp_item = items; *tmp_item; tmp_item++) {
604 char *item = *tmp_item;
605 if (item[0] == '$') {
606 const gchar *env = g_getenv (item+1);
608 *tmp_item = g_strdup (env ? env : "");
612 expanded_path = g_strjoinv (G_DIR_SEPARATOR_S, items);
616 return expanded_path;
620 tlm_utils_watch_for_files (
621 const gchar **watch_list,
627 WatchInfo *w_info = NULL;
629 if (!watch_list) return 0;
631 if ((ifd = inotify_init1 (IN_NONBLOCK | IN_CLOEXEC)) < 0) {
632 gchar strerr_buf[MAX_STRERROR_LEN] = {0,};
633 WARN("Failed to start inotify: %s", strerror_r(errno, strerr_buf, MAX_STRERROR_LEN));
637 w_info = _watch_info_new (ifd, cb, userdata);
639 for (; *watch_list; watch_list++) {
640 char *socket_path = _expand_file_path (*watch_list);
641 AddWatchResults res = _add_watch (ifd, socket_path, w_info);
642 if (res == WATCH_FAILED) {
643 WARN ("Failed to watch for '%s'", socket_path);
644 } else if (res == WATCH_READY) {
645 gboolean is_final = !nwatch && !*(watch_list + 1);
646 if (cb) cb (socket_path, is_final, NULL, userdata);
654 _watch_info_free (w_info);
658 return g_unix_fd_add_full (G_PRIORITY_DEFAULT, ifd, G_IO_IN,
659 _inotify_watcher_cb, w_info, (GDestroyNotify)_watch_info_free);
662 typedef struct _TlmLoginInfo
668 static int func_conv(
670 const struct pam_message **msgs,
671 struct pam_response **resps,
675 const char *login_prompt = "login";
676 const char *pwd_prompt = "Password";
681 TlmLoginInfo *info = (TlmLoginInfo *)pdata;
682 *resps = g_malloc0 (num_msg * sizeof(struct pam_response));
686 for (i=0; i<num_msg; ++i) {
687 struct pam_response *resp = *resps + i;
690 if (msgs[i]->msg_style == PAM_PROMPT_ECHO_ON &&
691 strncmp(msgs[i]->msg, login_prompt, strlen(login_prompt)) == 0) {
692 resp->resp = strndup (info->username, PAM_MAX_RESP_SIZE - 1);
694 else if (msgs[i]->msg_style == PAM_PROMPT_ECHO_OFF &&
695 strncmp(msgs[i]->msg, pwd_prompt, strlen(pwd_prompt)) == 0) {
696 resp->resp = strndup (info->password, PAM_MAX_RESP_SIZE - 1);
701 resp->resp_retcode = PAM_SUCCESS;
708 tlm_authenticate_user (
710 const gchar *username,
711 const gchar *password)
713 pam_handle_t *pam_h = NULL;
714 gboolean ret_auth = FALSE;
716 const gchar *service = NULL;
717 TlmLoginInfo *info = NULL;
719 if (!password || !username) {
720 WARN("username or password would be NULL");
724 // If TLM_CONFIG_PAM_AUTHENTICATION_SERVICE is not specified in tlm.conf
725 // use "system-auth" as defult.
726 service = tlm_config_get_string(config, TLM_CONFIG_GENERAL,
727 // TLM_CONFIG_PAM_AUTHENTICATION_SERVICE);
728 TLM_CONFIG_GENERAL_PAM_SERVICE);
730 service = "system-auth";
732 info = g_malloc0 (sizeof (*info));
734 CRITICAL("g_malloc0 memory allocation failure");
737 info->username = strndup (username, PAM_MAX_RESP_SIZE - 1);
738 info->password = strndup (password, PAM_MAX_RESP_SIZE - 1);
739 const struct pam_conv conv = {func_conv, info};
741 ret = pam_start (service, username, &conv, &pam_h);
742 if (ret != PAM_SUCCESS) {
743 WARN("Failed to pam_start: %d", ret);
744 free(info->username);
745 free(info->password);
750 ret = pam_authenticate (pam_h, PAM_SILENT);
751 if (ret == PAM_SUCCESS)
753 else if (ret == PAM_AUTH_ERR)
754 WARN("Failed to get authentication! username: %s", username);
756 WARN("Failed to pam_authenticate: %d", ret);
760 free(info->username);
761 free(info->password);