/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2007 William Jon McCann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "gdm-common.h" #ifndef HAVE_MKDTEMP #include "mkdtemp.h" #endif const char * gdm_make_temp_dir (char *template) { return mkdtemp (template); } gboolean gdm_is_version_unstable (void) { char **versions; gboolean unstable; unstable = FALSE; versions = g_strsplit (VERSION, ".", 3); if (versions && versions [0] && versions [1]) { int major; major = atoi (versions [1]); if ((major % 2) != 0) { unstable = TRUE; } } g_strfreev (versions); return unstable; } gboolean gdm_get_pwent_for_name (const char *name, struct passwd **pwentp) { struct passwd *pwent; do { errno = 0; pwent = getpwnam (name); } while (pwent == NULL && errno == EINTR); if (pwentp != NULL) { *pwentp = pwent; } return (pwent != NULL); } int gdm_wait_on_and_disown_pid (int pid, int timeout) { int status; int ret; int num_tries; int flags; if (timeout > 0) { flags = WNOHANG; num_tries = 10 * timeout; } else { flags = 0; num_tries = 0; } wait_again: errno = 0; ret = waitpid (pid, &status, flags); if (ret < 0) { if (errno == EINTR) { goto wait_again; } else if (errno == ECHILD) { ; /* do nothing, child already reaped */ } else { g_debug ("GdmCommon: waitpid () should not fail"); } } else if (ret == 0) { num_tries--; if (num_tries > 0) { g_usleep (G_USEC_PER_SEC / 10); } else { char *path; char *command; path = g_strdup_printf ("/proc/%ld/cmdline", (long) pid); if (g_file_get_contents (path, &command, NULL, NULL)) {; g_warning ("GdmCommon: process (pid:%d, command '%s') isn't dying after %d seconds, now ignoring it.", (int) pid, command, timeout); g_free (command); } else { g_warning ("GdmCommon: process (pid:%d) isn't dying after %d seconds, now ignoring it.", (int) pid, timeout); } g_free (path); return 0; } goto wait_again; } g_debug ("GdmCommon: process (pid:%d) done (%s:%d)", (int) pid, WIFEXITED (status) ? "status" : WIFSIGNALED (status) ? "signal" : "unknown", WIFEXITED (status) ? WEXITSTATUS (status) : WIFSIGNALED (status) ? WTERMSIG (status) : -1); return status; } int gdm_wait_on_pid (int pid) { return gdm_wait_on_and_disown_pid (pid, 0); } int gdm_signal_pid (int pid, int signal) { int status = -1; /* perhaps block sigchld */ g_debug ("GdmCommon: sending signal %d to process %d", signal, pid); errno = 0; status = kill (pid, signal); if (status < 0) { if (errno == ESRCH) { g_warning ("Child process %d was already dead.", (int)pid); } else { g_warning ("Couldn't kill child process %d: %s", pid, g_strerror (errno)); } } /* perhaps unblock sigchld */ return status; } /* hex conversion adapted from D-Bus */ /** * Appends a two-character hex digit to a string, where the hex digit * has the value of the given byte. * * @param str the string * @param byte the byte */ static void _gdm_string_append_byte_as_hex (GString *str, int byte) { const char hexdigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; str = g_string_append_c (str, hexdigits[(byte >> 4)]); str = g_string_append_c (str, hexdigits[(byte & 0x0f)]); } /** * Encodes a string in hex, the way MD5 and SHA-1 are usually * encoded. (Each byte is two hex digits.) * * @param source the string to encode * @param start byte index to start encoding * @param dest string where encoded data should be placed * @param insert_at where to place encoded data * @returns #TRUE if encoding was successful, #FALSE if no memory etc. */ gboolean gdm_string_hex_encode (const GString *source, int start, GString *dest, int insert_at) { GString *result; const unsigned char *p; const unsigned char *end; gboolean retval; g_return_val_if_fail (source != NULL, FALSE); g_return_val_if_fail (dest != NULL, FALSE); g_return_val_if_fail (source != dest, FALSE); g_return_val_if_fail (start >= 0, FALSE); g_return_val_if_fail (dest >= 0, FALSE); g_assert (start <= source->len); result = g_string_new (NULL); retval = FALSE; p = (const unsigned char*) source->str; end = p + source->len; p += start; while (p != end) { _gdm_string_append_byte_as_hex (result, *p); ++p; } dest = g_string_insert (dest, insert_at, result->str); retval = TRUE; g_string_free (result, TRUE); return retval; } /** * Decodes a string from hex encoding. * * @param source the string to decode * @param start byte index to start decode * @param end_return return location of the end of the hex data, or #NULL * @param dest string where decoded data should be placed * @param insert_at where to place decoded data * @returns #TRUE if decoding was successful, #FALSE if no memory. */ gboolean gdm_string_hex_decode (const GString *source, int start, int *end_return, GString *dest, int insert_at) { GString *result; const unsigned char *p; const unsigned char *end; gboolean retval; gboolean high_bits; g_return_val_if_fail (source != NULL, FALSE); g_return_val_if_fail (dest != NULL, FALSE); g_return_val_if_fail (source != dest, FALSE); g_return_val_if_fail (start >= 0, FALSE); g_return_val_if_fail (dest >= 0, FALSE); g_assert (start <= source->len); result = g_string_new (NULL); retval = FALSE; high_bits = TRUE; p = (const unsigned char*) source->str; end = p + source->len; p += start; while (p != end) { unsigned int val; switch (*p) { case '0': val = 0; break; case '1': val = 1; break; case '2': val = 2; break; case '3': val = 3; break; case '4': val = 4; break; case '5': val = 5; break; case '6': val = 6; break; case '7': val = 7; break; case '8': val = 8; break; case '9': val = 9; break; case 'a': case 'A': val = 10; break; case 'b': case 'B': val = 11; break; case 'c': case 'C': val = 12; break; case 'd': case 'D': val = 13; break; case 'e': case 'E': val = 14; break; case 'f': case 'F': val = 15; break; default: goto done; } if (high_bits) { result = g_string_append_c (result, val << 4); } else { int len; unsigned char b; len = result->len; b = result->str[len - 1]; b |= val; result->str[len - 1] = b; } high_bits = !high_bits; ++p; } done: dest = g_string_insert (dest, insert_at, result->str); if (end_return) { *end_return = p - (const unsigned char*) source->str; } retval = TRUE; g_string_free (result, TRUE); return retval; } static gboolean _fd_is_character_device (int fd) { struct stat file_info; if (fstat (fd, &file_info) < 0) { return FALSE; } return S_ISCHR (file_info.st_mode); } static gboolean _read_bytes (int fd, char *bytes, gsize number_of_bytes, GError **error) { size_t bytes_left_to_read; size_t total_bytes_read = 0; gboolean premature_eof; bytes_left_to_read = number_of_bytes; premature_eof = FALSE; do { size_t bytes_read = 0; errno = 0; bytes_read = read (fd, ((guchar *) bytes) + total_bytes_read, bytes_left_to_read); if (bytes_read > 0) { total_bytes_read += bytes_read; bytes_left_to_read -= bytes_read; } else if (bytes_read == 0) { premature_eof = TRUE; break; } else if ((errno != EINTR)) { break; } } while (bytes_left_to_read > 0); if (premature_eof) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "No data available"); return FALSE; } else if (bytes_left_to_read > 0) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), "%s", g_strerror (errno)); return FALSE; } return TRUE; } /** * Pulls a requested number of bytes from /dev/urandom * * @param size number of bytes to pull * @param error error if read fails * @returns The requested number of random bytes or #NULL if fail */ char * gdm_generate_random_bytes (gsize size, GError **error) { int fd; char *bytes; GError *read_error; /* We don't use the g_rand_* glib apis because they don't document * how much entropy they are seeded with, and it might be less * than the passed in size. */ errno = 0; fd = open ("/dev/urandom", O_RDONLY); if (fd < 0) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), "%s", g_strerror (errno)); close (fd); return NULL; } if (!_fd_is_character_device (fd)) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (ENODEV), _("/dev/urandom is not a character device")); close (fd); return NULL; } bytes = g_malloc (size); read_error = NULL; if (!_read_bytes (fd, bytes, size, &read_error)) { g_propagate_error (error, read_error); g_free (bytes); close (fd); return NULL; } close (fd); return bytes; }