/
githubmirror
/
libuser
Обзор
Документация
Войти
/
githubmirror
/
libuser
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
lib/util.c
903 строки
20 KB
Tomas Halman
selinux: replace deprecated items
23 июл 2021, 12:03
23 июл 2021, 12:03
537a83d
Код
Авторство
О чём код?
/* * Copyright (C) 2000-2002, 2007, 2008 Red Hat, Inc. * * This 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 program 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 * General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <config.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/time.h> #include <crypt.h> #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #ifdef WITH_SELINUX #include <selinux/selinux.h> #include <selinux/label.h> #endif #define LU_DEFAULT_SALT_TYPE "$1$" #define LU_DEFAULT_SALT_LEN 8 #define LU_MAX_LOCK_ATTEMPTS 6 #define LU_LOCK_TIMEOUT 2 #include "user_private.h" #include "internal.h" #define HASH_ROUNDS_MIN 1000 #define HASH_ROUNDS_MAX 999999999 #if (defined CRYPT_GENSALT_IMPLEMENTS_AUTO_ENTROPY && \ CRYPT_GENSALT_IMPLEMENTS_AUTO_ENTROPY) #define USE_XCRYPT_GENSALT 1 #else #define USE_XCRYPT_GENSALT 0 #endif #if ((defined XCRYPT_VERSION_NUM && \ XCRYPT_VERSION_NUM >= ((4 << 16) | 3)) && \ USE_XCRYPT_GENSALT) #define HAVE_YESCRYPT 1 #else #define HAVE_YESCRYPT 0 #endif struct lu_lock { int fd; struct flock lock; }; /* A wrapper for strcasecmp(). */ gint lu_strcasecmp(gconstpointer v1, gconstpointer v2) { g_return_val_if_fail(v1 != NULL, 0); g_return_val_if_fail(v2 != NULL, 0); return g_ascii_strcasecmp((char *) v1, (char *) v2); } /* A wrapper for strcmp(). */ gint lu_strcmp(gconstpointer v1, gconstpointer v2) { g_return_val_if_fail(v1 != NULL, 0); g_return_val_if_fail(v2 != NULL, 0); return strcmp((char *) v1, (char *) v2); } #if !USE_XCRYPT_GENSALT /* A list of allowed salt characters, according to SUSv2. */ #define ACCEPTABLE "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "abcdefghijklmnopqrstuvwxyz" \ "./0123456789" static gboolean is_acceptable(const char c) { if (c == 0) { return FALSE; } return (strchr(ACCEPTABLE, c) != NULL); } static gboolean fill_urandom(char *output, size_t length) { int fd; size_t got = 0; fd = open("/dev/urandom", O_RDONLY); if (fd == -1) return FALSE; memset(output, '\0', length); while (got < length) { ssize_t len; len = read(fd, output + got, length - got); if (len == -1) { if (errno == EINTR) continue; else { close(fd); return FALSE; } } while (len != 0 && isprint((unsigned char)output[got]) && !isspace((unsigned char)output[got]) && is_acceptable(output[got])) { got++; len--; } } close(fd); return TRUE; } #endif static const struct { const char initial[5]; char separator[2]; size_t salt_length; gboolean sha_rounds; } salt_type_info[] = { {"$1$", "$", 8, FALSE }, /* FIXME: number of rounds, base64 of 128 bits */ {"$2b$", "$", 8, FALSE }, {"$5$", "$", 16, TRUE }, {"$6$", "$", 16, TRUE }, #if HAVE_YESCRYPT {"$y$", "$", 24, FALSE }, #endif { "", "", 2 }, }; const char * lu_make_crypted(const char *plain, const char *previous) { char salt[2048]; size_t i, len = 0; #if USE_XCRYPT_GENSALT unsigned long rounds = 0; #endif if (previous == NULL) { previous = LU_DEFAULT_SALT_TYPE; } for (i = 0; i < G_N_ELEMENTS(salt_type_info); i++) { len = strlen(salt_type_info[i].initial); if (strncmp(previous, salt_type_info[i].initial, len) == 0) { break; } } g_assert(i < G_N_ELEMENTS(salt_type_info)); if (salt_type_info[i].sha_rounds != FALSE && strncmp(previous + len, "rounds=", strlen("rounds=")) == 0) { #if USE_XCRYPT_GENSALT const char *start; char *end; start = previous + len + strlen("rounds="); rounds = strtoul (start, &end, 10); if (rounds < HASH_ROUNDS_MIN) rounds = HASH_ROUNDS_MIN; else if (rounds > HASH_ROUNDS_MAX) rounds = HASH_ROUNDS_MAX; } g_assert(CRYPT_GENSALT_OUTPUT_SIZE <= sizeof(salt)); crypt_gensalt_rn(previous, rounds, NULL, 0, salt, sizeof(salt)); #else const char *start, *end; start = previous + len + strlen("rounds="); end = strchr(start, '$'); if (end != NULL && end <= start + strlen(G_STRINGIFY(HASH_ROUNDS_MAX))) len = (end + 1) - previous; } g_assert(len + salt_type_info[i].salt_length + strlen(salt_type_info[i].separator) < sizeof(salt)); memcpy(salt, previous, len); if (fill_urandom(salt + len, salt_type_info[i].salt_length) == FALSE) return NULL; strcpy(salt + len + salt_type_info[i].salt_length, salt_type_info[i].separator); #endif return crypt(plain, salt); } static const char * parse_hash_rounds(struct lu_context *context, const char *key, unsigned long *value) { const char *s; s = lu_cfg_read_single(context, key, NULL); if (s != NULL) { char *end; errno = 0; *value = strtoul(s, &end, 10); if (errno != 0 || *end != 0 || end == s) { g_warning("Invalid %s value '%s'", key, s); s = NULL; } } return s; } static unsigned long select_hash_rounds(struct lu_context *context) { const char *min_s, *max_s; unsigned long min, max, rounds; min_s = parse_hash_rounds(context, "defaults/hash_rounds_min", &min); max_s = parse_hash_rounds(context, "defaults/hash_rounds_max", &max); if (min_s == NULL && max_s == NULL) return 0; if (min_s != NULL && max_s != NULL) { if (min <= max) { if (max > HASH_ROUNDS_MAX) /* To avoid overflow in (max + 1) below */ max = HASH_ROUNDS_MAX; rounds = g_random_int_range(min, max + 1); } else rounds = min; } else if (min_s != NULL) rounds = min; else /* max_s != NULL */ rounds = max; if (rounds < HASH_ROUNDS_MIN) rounds = HASH_ROUNDS_MIN; else if (rounds > HASH_ROUNDS_MAX) rounds = HASH_ROUNDS_MAX; return rounds; } char * lu_util_default_salt_specifier(struct lu_context *context) { static const struct { const char *name, *initializer; gboolean sha_rounds; } salt_types[] = { { "des", "", FALSE }, { "md5", "$1$", FALSE }, { "blowfish", "$2b$", FALSE }, { "sha256", "$5$", TRUE }, { "sha512", "$6$", TRUE }, #if HAVE_YESCRYPT { "yescrypt", "$y$", FALSE }, #endif }; const char *salt_type; size_t i; g_return_val_if_fail(context != NULL, g_strdup("")); salt_type = lu_cfg_read_single(context, "defaults/crypt_style", "des"); for (i = 0; i < G_N_ELEMENTS(salt_types); i++) { if (strcasecmp(salt_types[i].name, salt_type) == 0) goto found; } return g_strdup(""); found: if (salt_types[i].sha_rounds != FALSE) { unsigned long rounds = 0; rounds = select_hash_rounds(context); #if USE_XCRYPT_GENSALT return g_strdup(crypt_gensalt(salt_types[i].initializer, rounds, NULL, 0)); #else if (rounds != 0) return g_strdup_printf("%srounds=%lu$", salt_types[i].initializer, rounds); #endif } return g_strdup(salt_types[i].initializer); } gpointer lu_util_lock_obtain(int fd, struct lu_error ** error) { int i; int maxtries = LU_MAX_LOCK_ATTEMPTS; int delay = LU_LOCK_TIMEOUT; struct lu_lock *ret; LU_ERROR_CHECK(error); g_assert(fd != -1); ret = g_malloc0(sizeof(*ret)); for (;;) { struct timeval tv; ret->fd = fd; ret->lock.l_type = F_RDLCK; if (write(ret->fd, NULL, 0) == 0) ret->lock.l_type = F_WRLCK; i = fcntl(ret->fd, F_SETLK, &ret->lock); if (i != -1) return ret; if (errno != EINTR && errno != EAGAIN) break; if (maxtries-- <= 0) break; memset(&tv, 0, sizeof(tv)); tv.tv_usec = (delay *= 2); select(0, NULL, NULL, NULL, &tv); } lu_error_new(error, lu_error_lock, _("error locking file: %s"), strerror(errno)); g_free(ret); return NULL; } void lu_util_lock_free(gpointer lock) { struct lu_lock *ret; int i; g_return_if_fail(lock != NULL); ret = (struct lu_lock*) lock; do { ret->lock.l_type = F_UNLCK; i = fcntl(ret->fd, F_SETLK, &ret->lock); } while ((i == -1) && ((errno == EINTR) || (errno == EAGAIN))); g_free(ret); } char * lu_util_line_get_matchingx(int fd, const char *part, int field, struct lu_error **error) { char *contents, *contents_end; struct stat st; off_t offset; char *ret = NULL, *line; gboolean mapped = FALSE; size_t part_len; LU_ERROR_CHECK(error); g_assert(fd != -1); g_assert(part != NULL); g_assert(field > 0); offset = lseek(fd, 0, SEEK_CUR); if (offset == -1) { lu_error_new(error, lu_error_read, NULL); return NULL; } if (fstat(fd, &st) == -1) { lu_error_new(error, lu_error_stat, NULL); return NULL; } contents = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (contents == MAP_FAILED) { contents = g_malloc(st.st_size); if (lseek(fd, 0, SEEK_SET) == -1 || read(fd, contents, st.st_size) != st.st_size || lseek(fd, offset, SEEK_SET) == -1) { lu_error_new(error, lu_error_read, NULL); g_free(contents); return NULL; } } else { mapped = TRUE; } contents_end = contents + st.st_size; part_len = strlen(part); line = contents; for (;;) { char *line_end, *field_start; line_end = memchr(line, '\n', contents_end - line); if (field == 1) field_start = line; else { int i; char *p; field_start = NULL; i = 1; for (p = line; p < contents_end && *p != '\n'; p++) { if (*p == ':') { i++; if (i >= field) { field_start = p + 1; break; } } } } if (field_start != NULL && contents_end - field_start >= part_len) { char *expected_field_end; expected_field_end = field_start + part_len; if (strncmp(field_start, part, part_len) == 0 && (expected_field_end == contents_end || *expected_field_end == ':' || *expected_field_end == '\n')) { if (line_end == NULL) line_end = contents_end; ret = g_strndup(line, line_end - line); break; } } if (line_end == NULL) break; line = line_end + 1; } if (mapped) { munmap(contents, st.st_size); } else { g_free(contents); } return ret; } char * lu_util_line_get_matching1(int fd, const char *part, struct lu_error **error) { LU_ERROR_CHECK(error); return lu_util_line_get_matchingx(fd, part, 1, error); } char * lu_util_line_get_matching3(int fd, const char *part, struct lu_error **error) { LU_ERROR_CHECK(error); return lu_util_line_get_matchingx(fd, part, 3, error); } char * lu_util_field_read(int fd, const char *first, unsigned int field, struct lu_error **error) { struct stat st; char *buf, *buf_end; char *pattern; char *line, *start = NULL; char *ret; size_t len; gboolean mapped = FALSE; LU_ERROR_CHECK(error); g_assert(fd != -1); g_assert(first != NULL); g_assert(strlen(first) != 0); g_assert(field >= 1); if (fstat(fd, &st) == -1) { lu_error_new(error, lu_error_stat, NULL); return NULL; } buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) { buf = g_malloc(st.st_size); if (lseek(fd, 0, SEEK_SET) == -1 || read(fd, buf, st.st_size) != st.st_size) { lu_error_new(error, lu_error_read, NULL); g_free(buf); return NULL; } } else { mapped = TRUE; } buf_end = buf + st.st_size; pattern = g_strdup_printf("%s:", first); len = strlen(pattern); line = buf; for (;;) { if (buf_end - line >= len && memcmp (line, pattern, len) == 0) goto found_line; line = memchr(line, '\n', buf_end - line); if (line == NULL) break; line++; } lu_error_new(error, lu_error_search, NULL); ret = NULL; goto err; found_line: /* find the start of the field */ if (field == 1) start = line; else { unsigned i = 1; char *p; start = NULL; for (p = line; p < buf_end && *p != '\n'; p++) { if (*p == ':') { i++; if (i >= field) { start = p + 1; break; } } } } /* find the end of the field */ if (start != NULL) { char *end; end = start; while (end < buf_end && *end != '\n' && *end != ':') end++; g_assert(end == buf_end || *end == '\n' || *end == ':'); ret = g_strndup(start, end - start); } else { ret = g_strdup(""); } err: g_free(pattern); if (mapped) { munmap(buf, st.st_size); } else { g_free(buf); } return ret; } gboolean lu_util_field_write(int fd, const char *first, unsigned int field, const char *value, struct lu_error ** error) { struct stat st; char *buf; char *pattern; char *line, *start = NULL, *end = NULL; gboolean ret = FALSE; unsigned fi = 1; size_t len; LU_ERROR_CHECK(error); g_assert(fd != -1); g_assert(field >= 1); first = first ? : ""; value = value ? : ""; if (fstat(fd, &st) == -1) { lu_error_new(error, lu_error_stat, NULL); return FALSE; } if (lseek(fd, 0, SEEK_SET) == -1) { lu_error_new(error, lu_error_read, NULL); return FALSE; } buf = g_malloc0(st.st_size + 1 + strlen(value) + field); if (read(fd, buf, st.st_size) != st.st_size) { lu_error_new(error, lu_error_read, NULL); goto err_buf; } pattern = g_strdup_printf("\n%s:", first); if (strncmp(buf, pattern + 1, strlen(pattern) - 1) == 0) { /* found it on the first line */ line = buf; } else if ((line = strstr(buf, pattern)) != NULL) { /* found it somewhere in the middle */ line++; } if (line == NULL) { lu_error_new(error, lu_error_search, NULL); goto err_pattern; } /* find the start of the field */ if (fi == field) start = line; else { char *p; start = NULL; for (p = line; fi < field && *p != '\n' && *p != '\0'; p++) { if (*p == ':') { fi++; if (fi >= field) { start = p + 1; break; } } } } /* find the end of the field */ if (start != NULL) { end = start; while ((*end != '\0') && (*end != '\n') && (*end != ':')) { end++; } } else { lu_error_new(error, lu_error_search, NULL); goto err_pattern; } if (start != NULL) { /* insert the text here, after moving the data around */ memmove(start + strlen(value), end, st.st_size - (end - buf) + 1); memcpy(start, value, strlen(value)); } else { /* FIXME: this code currently can't execute */ /* fi contains the number of fields, so the difference between * field and fi is the number of colons we need to add to the * end of the line to create the field */ for (end = line; *end != '\0' && *end != '\n'; end++) ; start = end; memmove(start + strlen(value) + (field - fi), end, st.st_size - (end - buf) + 1); memset(start, ':', field - fi); memcpy(start + (field - fi), value, strlen(value)); } if (lseek(fd, 0, SEEK_SET) == -1) { lu_error_new(error, lu_error_write, NULL); goto err_pattern; } len = strlen(buf); if (write(fd, buf, len) != len) { lu_error_new(error, lu_error_write, NULL); goto err_pattern; } if (ftruncate(fd, len) == -1) { lu_error_new(error, lu_error_write, NULL); goto err_pattern; } ret = TRUE; err_pattern: g_free(pattern); err_buf: g_free(buf); return ret; } /* Return current date in days since the epoch (suitable for LU_SHADOW*), or -1 if the current date is unknown or obviously implausible (e.g. on a system without a RTC). */ long lu_util_shadow_current_date_or_minus_1(void) { const struct tm *gmt; time_t now; GDate *today, *epoch; long days; now = time(NULL); if (now == (time_t)-1) return -1; gmt = gmtime(&now); today = g_date_new_dmy(gmt->tm_mday, gmt->tm_mon + 1, gmt->tm_year + 1900); epoch = g_date_new_dmy(1, 1, 1970); days = g_date_get_julian(today) - g_date_get_julian(epoch); g_date_free(today); g_date_free(epoch); /* Refuse to return 0 (Jan 1, 1970): it is unquestionably incorrect in the real world, which is not really libuser's concern, but it is also a special case for LU_SHADOWLASTCHANGE (marking the account for forced password change) and LU_SHADOWEXPIRE (forbidden in shadow(5)). In both cases, setting the value to -1 deactivates the real-time-related actions, which is a reasonable thing to do when the only available RTC is incorrect. */ if (days == 0) return -1; return days; } /* Set the shadow last-changed field to today's date. */ void lu_util_update_shadow_last_change(struct lu_ent *ent) { lu_ent_set_long(ent, LU_SHADOWLASTCHANGE, lu_util_shadow_current_date_or_minus_1()); } #ifdef WITH_SELINUX /* Store current fscreate context to ctx. */ gboolean lu_util_fscreate_save(char **ctx, struct lu_error **error) { *ctx = NULL; if (is_selinux_enabled() > 0 && getfscreatecon(ctx) < 0) { lu_error_new(error, lu_error_generic, _("couldn't get default security context: %s"), strerror(errno)); return FALSE; } return TRUE; } /* Restore fscreate context from ctx, and free it. */ void lu_util_fscreate_restore(char *ctx) { if (is_selinux_enabled() > 0) { (void)setfscreatecon(ctx); if (ctx) freecon(ctx); } } /* Set fscreate context from context of fd. Use path only for diagnostics. */ gboolean lu_util_fscreate_from_fd(int fd, const char *path, struct lu_error **error) { if (is_selinux_enabled() > 0) { char *ctx; if (fgetfilecon(fd, &ctx) < 0) { lu_error_new(error, lu_error_stat, _("couldn't get security context of " "`%s': %s"), path, strerror(errno)); return FALSE; } if (setfscreatecon(ctx) < 0) { lu_error_new(error, lu_error_generic, _("couldn't set default security context " "to `%s': %s"), ctx, strerror(errno)); freecon(ctx); return FALSE; } freecon(ctx); } return TRUE; } /* Set fscreate context from context of file. */ gboolean lu_util_fscreate_from_file(const char *file, struct lu_error **error) { if (is_selinux_enabled() > 0) { char *ctx; if (getfilecon(file, &ctx) < 0) { lu_error_new(error, lu_error_stat, _("couldn't get security context of " "`%s': %s"), file, strerror(errno)); return FALSE; } if (setfscreatecon(ctx) < 0) { lu_error_new(error, lu_error_generic, _("couldn't set default security context " "to `%s': %s"), ctx, strerror(errno)); freecon(ctx); return FALSE; } freecon(ctx); } return TRUE; } /* Set fscreate context from context of file, not resolving it if it is a symlink. */ gboolean lu_util_fscreate_from_lfile(const char *file, struct lu_error **error) { if (is_selinux_enabled() > 0) { char *ctx; if (lgetfilecon(file, &ctx) < 0) { lu_error_new(error, lu_error_stat, _("couldn't get security context of " "`%s': %s"), file, strerror(errno)); return FALSE; } if (setfscreatecon(ctx) < 0) { lu_error_new(error, lu_error_generic, _("couldn't set default security context " "to `%s': %s"), ctx, strerror(errno)); freecon(ctx); return FALSE; } freecon(ctx); } return TRUE; } /* Set fscreate context for creating a file at path, with file type specified by mode. */ gboolean lu_util_fscreate_for_path(const char *path, mode_t mode, struct lu_error **error) { if (is_selinux_enabled() > 0) { char *ctx; struct selabel_handle *label_handle = NULL; label_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0); if (!label_handle) { lu_error_new(error, lu_error_open, _("couldn't obtain selabel file " "context handle: %s"), strerror(errno)); return FALSE; } if (selabel_lookup(label_handle, &ctx, path, mode) < 0) { if (errno == ENOENT) ctx = NULL; else { lu_error_new(error, lu_error_stat, _("couldn't determine security " "context for `%s': %s"), path, strerror(errno)); selabel_close(label_handle); return FALSE; } } selabel_close(label_handle); if (setfscreatecon(ctx) < 0) { lu_error_new(error, lu_error_generic, _("couldn't set default security context " "to `%s': %s"), ctx != NULL ? ctx : "<<none>>", strerror(errno)); freecon(ctx); return FALSE; } freecon(ctx); } return TRUE; } #endif /* Append a copy of VALUES to DEST */ void lu_util_append_values(GValueArray *dest, GValueArray *values) { size_t i; for (i = 0; i < values->n_values; i++) g_value_array_append(dest, g_value_array_get_nth(values, i)); }