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>
30 #include <glib/gstdio.h>
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <sys/inotify.h>
40 #include <glib/gstdio.h>
41 #include <glib-unix.h>
42 #include <security/pam_appl.h>
45 #include "tlm-utils.h"
47 #include "tlm-config.h"
48 #include "tlm-config-general.h"
50 #define HOST_NAME_SIZE 256
53 g_clear_string (gchar **str)
62 tlm_user_get_name (uid_t user_id)
66 pwent = getpwuid (user_id);
70 return pwent->pw_name;
74 tlm_user_get_uid (const gchar *username)
78 pwent = getpwnam (username);
86 tlm_user_get_gid (const gchar *username)
90 pwent = getpwnam (username);
98 tlm_user_get_home_dir (const gchar *username)
100 struct passwd *pwent;
102 pwent = getpwnam (username);
106 return pwent->pw_dir;
110 tlm_user_get_shell (const gchar *username)
112 struct passwd *pwent;
114 pwent = getpwnam (username);
118 return pwent->pw_shell;
122 tlm_utils_delete_dir (
128 if (!dir || !(gdir = g_dir_open(dir, 0, NULL))) {
132 const gchar *fname = NULL;
134 gchar *filepath = NULL;
135 while ((fname = g_dir_read_name (gdir)) != NULL) {
136 if (g_strcmp0 (fname, ".") == 0 ||
137 g_strcmp0 (fname, "..") == 0) {
141 filepath = g_build_filename (dir, fname, NULL);
143 retval = lstat(filepath, &sent);
145 /* recurse the directory */
146 if (S_ISDIR (sent.st_mode)) {
147 retval = (gint)!tlm_utils_delete_dir (filepath);
149 retval = g_remove (filepath);
161 if (g_remove (dir) != 0) {
170 const gchar *tty_name)
173 const gchar *tmp = tty_name;
176 if (isdigit (*tmp)) {
187 const gchar *hostname)
189 gchar *hostaddress = NULL;
190 struct addrinfo hints, *info = NULL;
192 if (!hostname) return NULL;
194 memset (&hints, 0, sizeof (hints));
195 hints.ai_flags = AI_ADDRCONFIG;
197 if (getaddrinfo (hostname, NULL, &hints, &info) == 0) {
199 if (info->ai_family == AF_INET) {
200 struct sockaddr_in *sa = (struct sockaddr_in *) info->ai_addr;
201 hostaddress = g_malloc0 (sizeof(struct in_addr));
202 memcpy (hostaddress, &(sa->sin_addr), sizeof (struct in_addr));
203 } else if (info->ai_family == AF_INET6) {
204 struct sockaddr_in6 *sa = (struct sockaddr_in6 *) info->ai_addr;
205 hostaddress = g_malloc0 (sizeof(struct in6_addr));
206 memcpy (hostaddress, &(sa->sin6_addr),
207 sizeof (struct in6_addr));
217 const gchar *tty1_name,
218 const gchar *tty2_name)
220 gchar *tty1 = NULL, *tty2 = NULL;
221 gboolean res = FALSE;
223 if (tty1_name == tty2_name) return TRUE;
224 if (!tty1_name || !tty2_name) return FALSE;
226 if (*tty1_name == '/') tty1 = g_strdup (tty1_name);
227 else tty1 = g_strdup_printf ("/dev/%s", tty1_name);
228 if (*tty2_name == '/') tty2 = g_strdup (tty2_name);
229 else tty2 = g_strdup_printf ("/dev/%s", tty2_name);
231 res = (g_strcmp0 (tty1_name, tty2_name) == 0);
241 gchar *name = g_malloc0 (HOST_NAME_SIZE);
242 if (gethostname (name, HOST_NAME_SIZE) != 0) {
250 tlm_utils_log_utmp_entry (const gchar *username)
255 struct utmp *ut_tmp = NULL;
256 gchar *hostname = NULL, *hostaddress = NULL;
257 const gchar *tty_name = NULL;
258 gchar *tty_no_dev_name = NULL, *tty_id = NULL;
260 DBG ("Log session entry to utmp/wtmp");
262 hostname = _get_host_name ();
263 hostaddress = _get_host_address (hostname);
264 tty_name = ttyname (0);
266 tty_no_dev_name = g_strdup (strncmp(tty_name, "/dev/", 5) == 0 ?
267 tty_name + 5 : tty_name);
269 tty_id = _get_tty_id (tty_no_dev_name);
271 utmpname (_PATH_UTMP);
274 while ((ut_tmp = getutent())) {
275 if ( (ut_tmp->ut_pid == pid) &&
276 (ut_tmp->ut_id[0] != '\0') &&
277 (ut_tmp->ut_type == LOGIN_PROCESS ||
278 ut_tmp->ut_type == USER_PROCESS) &&
279 (_is_tty_same (ut_tmp->ut_line, tty_name))) {
284 if (ut_tmp) memcpy (&ut_ent, ut_tmp, sizeof (ut_ent));
285 else memset (&ut_ent, 0, sizeof (ut_ent));
287 ut_ent.ut_type = USER_PROCESS;
290 strncpy (ut_ent.ut_id, tty_id, sizeof (ut_ent.ut_id));
292 strncpy (ut_ent.ut_user, username, sizeof (ut_ent.ut_user));
294 strncpy (ut_ent.ut_line, tty_no_dev_name, sizeof (ut_ent.ut_line));
296 strncpy (ut_ent.ut_host, hostname, sizeof (ut_ent.ut_host));
298 memcpy (&ut_ent.ut_addr_v6, hostaddress, sizeof (ut_ent.ut_addr_v6));
300 ut_ent.ut_session = getsid (0);
301 gettimeofday (&tv, NULL);
303 ut_ent.ut_tv.tv_sec = tv.tv_sec;
304 ut_ent.ut_tv.tv_usec = tv.tv_usec;
306 ut_ent.ut_time = tv.tv_sec;
312 updwtmp (_PATH_WTMP, &ut_ent);
314 g_free (hostaddress);
316 g_free (tty_no_dev_name);
321 _split_command_line_with_regex(const char *command, GRegex *regex) {
322 gchar **temp_strv = NULL;
323 gchar **temp_iter = NULL, **args_iter = NULL;
326 temp_strv = regex ? g_regex_split (regex, command, G_REGEX_MATCH_NOTEMPTY)
327 : g_strsplit (command, " ", -1);
329 WARN("Failed to split command: %s", command);
333 argv = g_new0 (gchar *, g_strv_length (temp_strv));
334 for (temp_iter = temp_strv, args_iter = argv;
338 gchar *item = g_strstrip (*temp_iter);
340 item_len = strlen (item);
344 if ((item[0] == '\"' && item[item_len - 1] == '\"') ||
345 (item[0] == '\'' && item[item_len - 1] == '\'')) {
346 item[item_len - 1] = '\0';
347 memmove (item, item + 1, item_len - 1);
349 *args_iter = g_strcompress (item);
352 g_strfreev (temp_strv);
358 tlm_utils_split_command_line(const gchar *command) {
359 const gchar *pattern = "('.*?'|\".*?\"|\\S+)";
360 GError *error = NULL;
361 GRegex *regex = NULL;
365 WARN("Cannot pase NULL arguments string");
369 if (!(regex = g_regex_new(pattern, 0, G_REGEX_MATCH_NOTEMPTY, &error))) {
370 WARN("Failed to create regex: %s", error->message);
374 argv = _split_command_line_with_regex (command, regex);
376 g_regex_unref (regex);
382 tlm_utils_split_command_lines (const GList const *commands_list) {
383 const gchar *pattern = "('.*?'|\".*?\"|\\S+)";
384 GError *error = NULL;
385 GRegex *regex = NULL;
386 GList *argv_list = NULL;
387 const GList *tmp_list = NULL;
389 if (!commands_list) {
393 if (!(regex = g_regex_new(pattern, 0, G_REGEX_MATCH_NOTEMPTY, &error))) {
394 WARN("Failed to create regex: %s", error->message);
398 for (tmp_list = commands_list; tmp_list; tmp_list = tmp_list->next) {
399 argv_list = g_list_append (argv_list, _split_command_line_with_regex (
400 (const gchar *)tmp_list->data, regex));
403 g_regex_unref (regex);
410 GHashTable *dir_table; /* { gchar*: GList* } */
411 GHashTable *wd_table; /* { int: const gchar* } */
422 WatchInfo *info = g_slice_new0 (WatchInfo);
425 info->userdata = userdata;
426 info->dir_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
427 info->wd_table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
433 _destroy_dir_table_entry (gpointer key, gpointer value, gpointer userdata) {
436 g_list_free_full ((GList *)value, g_free);
440 _destroy_wd_table_entry (gpointer key, gpointer value, gpointer userdata) {
442 int ifd = GPOINTER_TO_INT(userdata);
443 int wd = GPOINTER_TO_INT(key);
445 inotify_rm_watch (ifd, wd);
449 _watch_info_free (WatchInfo *info)
453 if (info->dir_table) {
454 g_hash_table_foreach (info->dir_table, _destroy_dir_table_entry, NULL);
455 g_hash_table_unref (info->dir_table);
457 if (info->wd_table) {
458 g_hash_table_foreach (info->wd_table, _destroy_wd_table_entry,
459 GINT_TO_POINTER(info->ifd));
460 g_hash_table_unref (info->wd_table);
462 if (info->ifd) close(info->ifd);
464 g_slice_free (WatchInfo, info);
474 _add_watch (int ifd, char *file_path, WatchInfo *info) {
475 GList *file_list = NULL;
477 gchar *file_name = NULL;
479 AddWatchResults res = WATCH_FAILED;
481 if (!file_path) return WATCH_FAILED;
483 file_name = g_path_get_basename (file_path);
484 if (!file_name) return WATCH_FAILED;
486 dir = g_path_get_dirname (file_path);
491 if ((file_list = (GList *)g_hash_table_lookup (
492 info->dir_table, (gconstpointer)dir))) {
493 file_list = g_list_append (file_list, file_name);
497 file_list = g_list_append (NULL, file_name);
498 g_hash_table_insert (info->dir_table, g_strdup(dir), file_list);
500 /* add watch on directory if its not existing */
501 if (g_access (dir, 0)) {
502 return _add_watch (ifd, dir, info);
505 DBG("Adding watch for file '%s' in dir '%s'", file_name, dir);
506 if ((wd = inotify_add_watch (ifd, dir, IN_CREATE)) == -1) {
507 WARN ("failed to add inotify watch on %s: %s", dir, strerror (errno));
509 goto remove_and_return;
512 if (!g_access (file_path, 0)) {
513 /* socket is ready, need not have a inotify watch for this */
514 inotify_rm_watch (ifd, wd);
516 goto remove_and_return;
519 g_hash_table_insert (info->wd_table, GINT_TO_POINTER(wd), dir);
524 g_hash_table_remove (info->dir_table, (gconstpointer)dir);
525 g_list_free_full (file_list, (GDestroyNotify)g_free);
531 _inotify_watcher_cb (gint ifd, GIOCondition condition, gpointer userdata)
533 WatchInfo *info = (WatchInfo *)userdata;
534 struct inotify_event *ie = NULL;
535 gsize size = sizeof (struct inotify_event) + PATH_MAX + 1;
536 guint nwatch = g_hash_table_size (info->wd_table);
538 ie = (struct inotify_event *) g_slice_alloc0(size);
540 read (ifd, ie, size) > (ssize_t)sizeof (struct inotify_event)) {
541 GList *file_list = NULL;
542 GList *element = NULL;
543 GList *pending_list = NULL;
544 gboolean is_first = FALSE;
545 gchar *file_path = NULL;
546 const gchar *dir = NULL;
548 dir = (gchar *)g_hash_table_lookup (
549 info->wd_table, GINT_TO_POINTER(ie->wd));
552 file_list = g_hash_table_lookup (info->dir_table, (gconstpointer)dir);
553 element = g_list_find_custom (file_list,
554 (gpointer)ie->name, (GCompareFunc)g_strcmp0);
556 DBG("Ignoring '%s' file creation", ie->name);
559 is_first = (file_list == element);
561 g_free (element->data);
562 file_list = g_list_delete_link(file_list, element);
564 g_hash_table_remove (info->dir_table, dir);
565 g_hash_table_remove (info->wd_table, GINT_TO_POINTER(ie->wd));
566 inotify_rm_watch (ifd, ie->wd);
568 } else if (is_first) {
569 g_hash_table_insert (info->dir_table, g_strdup (dir), file_list);
572 file_path = g_build_filename (dir, ie->name, NULL);
573 if ((pending_list = (GList *)g_hash_table_lookup (info->dir_table,
574 (gconstpointer)file_path)) != NULL) {
577 // as we are about add real inotify watch, first remove from dir_table
578 g_hash_table_steal (info->dir_table, (gconstpointer)file_path);
580 // Add watches to all the files depend on this directory
581 for (tmp = pending_list; tmp; tmp = tmp->next) {
582 gchar *file_name = (gchar *)tmp->data;
583 gchar *new_file_path = g_build_filename (file_path, file_name, NULL);
584 AddWatchResults res = _add_watch (ifd, new_file_path, info);
585 if (res == WATCH_READY) {
587 info->cb (new_file_path, nwatch == 0, NULL, info->userdata);
589 } else if (res == WATCH_ADDED) nwatch++;
591 WARN ("Couldn't add watch on '%s'", new_file_path);
594 g_free (new_file_path);
596 g_list_free (pending_list);
598 DBG("%s", file_path);
599 if (info->cb) info->cb (file_path, nwatch == 0, NULL, info->userdata);
604 g_slice_free1 (size, ie);
606 return nwatch ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
610 _expand_file_path (const gchar *file_path)
613 gchar **tmp_item =NULL;
614 gchar *expanded_path = NULL;
616 if (!file_path) return NULL;
619 * FIXME: we are not considering filename which having \$ in it
621 if (g_strrstr (file_path, "$") == NULL) return g_strdup(file_path);
623 items = g_strsplit (file_path, G_DIR_SEPARATOR_S, -1);
624 /* soemthing wrong in file path */
625 if (!items) { return g_strdup (file_path); }
627 for (tmp_item = items; *tmp_item; tmp_item++) {
628 char *item = *tmp_item;
629 if (item[0] == '$') {
630 const gchar *env = g_getenv (item+1);
632 *tmp_item = g_strdup (env ? env : "");
636 expanded_path = g_strjoinv (G_DIR_SEPARATOR_S, items);
640 return expanded_path;
644 tlm_utils_watch_for_files (
645 const gchar **watch_list,
651 WatchInfo *w_info = NULL;
653 if (!watch_list) return 0;
655 if ((ifd = inotify_init1 (IN_NONBLOCK | IN_CLOEXEC)) < 0) {
656 WARN("Failed to start inotify: %s", strerror(errno));
660 w_info = _watch_info_new (ifd, cb, userdata);
662 for (; *watch_list; watch_list++) {
663 char *socket_path = _expand_file_path (*watch_list);
664 AddWatchResults res = _add_watch (ifd, socket_path, w_info);
665 if (res == WATCH_FAILED) {
666 WARN ("Failed to watch for '%s'", socket_path);
667 } else if (res == WATCH_READY) {
668 gboolean is_final = !nwatch && !*(watch_list + 1);
669 if (cb) cb (socket_path, is_final, NULL, userdata);
676 _watch_info_free (w_info);
680 return g_unix_fd_add_full (G_PRIORITY_DEFAULT, ifd, G_IO_IN,
681 _inotify_watcher_cb, w_info, (GDestroyNotify)_watch_info_free);
684 typedef struct _TlmLoginInfo
690 static int func_conv(
692 const struct pam_message **msgs,
693 struct pam_response **resps,
697 const char *login_prompt = "login";
698 const char *pwd_prompt = "Password";
703 TlmLoginInfo *info = (TlmLoginInfo *)pdata;
704 *resps = g_malloc0 (num_msg * sizeof(struct pam_response));
706 for (i=0; i<num_msg; ++i) {
707 struct pam_response *resp = *resps + i;
710 if (msgs[i]->msg_style == PAM_PROMPT_ECHO_ON &&
711 strncmp(msgs[i]->msg, login_prompt, strlen(login_prompt)) == 0) {
712 resp->resp = strndup (info->username, PAM_MAX_RESP_SIZE - 1);
714 else if (msgs[i]->msg_style == PAM_PROMPT_ECHO_OFF &&
715 strncmp(msgs[i]->msg, pwd_prompt, strlen(pwd_prompt)) == 0) {
716 resp->resp = strndup (info->password, PAM_MAX_RESP_SIZE - 1);
721 resp->resp_retcode = PAM_SUCCESS;
728 tlm_authenticate_user (
730 const gchar *username,
731 const gchar *password)
733 pam_handle_t *pam_h = NULL;
734 gboolean ret_auth = FALSE;
736 const gchar *service = NULL;
737 TlmLoginInfo *info = NULL;
739 if (!password || !username) {
740 WARN("username or password would be NULL");
744 // If TLM_CONFIG_PAM_AUTHENTICATION_SERVICE is not specified in tlm.conf
745 // use "system-auth" as defult.
746 service = tlm_config_get_string(config, TLM_CONFIG_GENERAL,
747 // TLM_CONFIG_PAM_AUTHENTICATION_SERVICE);
748 TLM_CONFIG_GENERAL_PAM_SERVICE);
750 service = "system-auth";
752 info = g_malloc0 (sizeof (*info));
753 info->username = strndup (username, PAM_MAX_RESP_SIZE - 1);
754 info->password = strndup (password, PAM_MAX_RESP_SIZE - 1);
755 const struct pam_conv conv = {func_conv, info};
757 ret = pam_start (service, username, &conv, &pam_h);
758 if (ret != PAM_SUCCESS) {
759 WARN("Failed to pam_start: %d", ret);
763 ret = pam_authenticate (pam_h, PAM_SILENT);
764 if (ret == PAM_SUCCESS)
766 else if (ret == PAM_AUTH_ERR)
767 WARN("Failed to get authentication! username: %s", username);
769 WARN("Failed to pam_authenticate: %d", ret);
773 free(info->username);
774 free(info->password);