/* 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 #include #include #include #include #include #include #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