summaryrefslogtreecommitdiffstats
path: root/src/sh_subuid.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sh_subuid.c')
-rw-r--r--src/sh_subuid.c245
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