diff options
Diffstat (limited to 'src/sh_subuid.c')
-rw-r--r-- | src/sh_subuid.c | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/src/sh_subuid.c b/src/sh_subuid.c new file mode 100644 index 0000000..33bf407 --- /dev/null +++ b/src/sh_subuid.c @@ -0,0 +1,245 @@ +/* SAMHAIN file system integrity testing */ +/* Copyright (C) 2018 Rainer Wichmann */ +/* */ +/* This program is free software; you can redistribute it */ +/* and/or modify */ +/* it under the terms of the GNU 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 General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "config_xor.h" + +#undef FIL__ +#define FIL__ _("sh_subuid.c") + + +#include <sys/types.h> +#include <time.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <stdlib.h> +#include <errno.h> +#include <limits.h> + +#if defined(__linux__) + +#include "samhain.h" +#include "sh_unix.h" + +#define SH_SUBUID_FILE _("/etc/subuid") +#define SH_SUBGID_FILE _("/etc/subgid") + +struct subuid_t { + char name[32]; + unsigned long first; + unsigned long last; + struct subuid_t * next; +}; + +static time_t last_subuid = 0; +static time_t last_subgid = 0; + +struct subuid_t * list_subuid = NULL; +struct subuid_t * list_subgid = NULL; + +/* Check whether we need to re-read the subuid/subgid file + */ +static int needs_reread (char * file, time_t * last) +{ + int retval = S_FALSE; + struct stat buf; + int status = retry_lstat (FIL__, __LINE__, file, &buf); + + if (status == 0) + { + if ((buf.st_mtime - *last) > 1) + { + *last = buf.st_mtime; + retval = S_TRUE; + } + } + else if (status && errno == ENOENT) + { + /* If there was a file make sure we attempt to re-read + * to zero out the list. + */ + if (*last > 0) retval = S_TRUE; + *last = 0; + } + return retval; +} + +/* free the whole list + */ +static void free_subordinate(struct subuid_t * head) +{ + struct subuid_t * prev; + struct subuid_t * curr = head; + + while (curr) + { + prev = curr; + curr = curr->next; + SH_FREE(prev); + } + return; +} + +#define NFIELDS_SUBUID 3 + +static int get_ulong(char * str, unsigned long * result) +{ + char * endptr; + + errno = 0; + *result = strtoul(str, &endptr, 0); + if (*str != '\0' && *endptr == '\0' && errno != ERANGE) + return S_TRUE; + return S_FALSE; +} + +/* Parse a single line into name / startuid / lastuid + */ +static struct subuid_t * parse_subordinate(char * line) +{ + unsigned int nfields = NFIELDS_SUBUID; + size_t lengths[NFIELDS_SUBUID]; + unsigned long start, count; + struct subuid_t * new; + + char ** array = split_array(line, &nfields, ':', lengths); + + if (nfields != NFIELDS_SUBUID) + { SH_FREE(array); return NULL; } + + if (S_TRUE != get_ulong(array[1], &start)) + { SH_FREE(array); return NULL; } + if ((S_TRUE != get_ulong(array[2], &count)) || (count == 0)) + { SH_FREE(array); return NULL; } + if (lengths[0] == 0) + { SH_FREE(array); return NULL; } + + /* we have checked that count != 0 */ + --count; + + if (start > (ULONG_MAX - count)) + { SH_FREE(array); return NULL; } + + new = SH_ALLOC(sizeof(struct subuid_t)); + sl_strlcpy(new->name, array[0], 32); + new->first = start; + new->last = start + count; /* start+count-1, but we already did --count */ + new->next = NULL; + + SH_FREE(array); + return new; +} + +/* (re-)read the subuid/subgid file + */ +static void reread_subordinate (char * file, struct subuid_t ** head_ref) +{ + SL_TICKET fd = (-1); + char line[1024]; + + if (*head_ref) { free_subordinate(*head_ref); *head_ref = NULL; } + + fd = sl_open_read (FIL__, __LINE__, file, SL_YESPRIV); + if (!SL_ISERROR(fd)) + { + while ( sh_unix_getline(fd, line, sizeof(line)) > 0 ) + { + /* for invalid lines, NULL will be returned + */ + struct subuid_t * new = parse_subordinate(line); + + if (new) + { + new->next = *head_ref; + *head_ref = new; + } + } + sl_close(fd); + } + return; +} + +/* Return the username for a given subuid/subgid + */ +static char * get_name4id (unsigned long id, struct subuid_t * head) +{ + struct subuid_t * cur = head; + + while (cur) + { + if (id >= cur->first && id <= cur->last) + return cur->name; + cur = cur->next; + } + return NULL; +} + +/*********************************************** + * + * Public functions + * + ***********************************************/ + +/* Returns username or NULL for a subuid + */ +char * sh_get_subuid (unsigned long subuid) +{ + static int init = 0; + static char file[256]; + + if (!init) { sl_strlcpy(file, SH_SUBUID_FILE, sizeof(file)); init = 1; } + + if (S_TRUE == needs_reread(file, &last_subuid)) + reread_subordinate(file, &list_subuid); + + return get_name4id (subuid, list_subuid); +} + +/* Returns group name or NULL for subgid + */ +char * sh_get_subgid (unsigned long subgid) +{ + static int init = 0; + static char file[256]; + + if (!init) { sl_strlcpy(file, SH_SUBGID_FILE, sizeof(file)); init = 1; } + + if (S_TRUE == needs_reread(file, &last_subgid)) + reread_subordinate(file, &list_subgid); + + return get_name4id (subgid, list_subgid); +} + +/* Not Linux, hence no sub(u|g)id + */ +#else + +char * sh_get_subuid (unsigned long subuid) +{ + (void) subuid; + return NULL; +} + +char * sh_get_subgid (unsigned long subgid) +{ + (void) subgid; + return NULL; +} + +#endif |