diff options
author | Joseph Hunkeler <jhunkeler@gmail.com> | 2021-07-14 12:53:16 -0400 |
---|---|---|
committer | Joseph Hunkeler <jhunkeler@gmail.com> | 2021-07-14 12:53:16 -0400 |
commit | 01e648b1391dd84f8e63494f75f5e6a9dfe375b6 (patch) | |
tree | 9ed0d618923004b60ce4157a8cdb221769092683 | |
download | idcmp-01e648b1391dd84f8e63494f75f5e6a9dfe375b6.tar.gz |
Initial commit
-rw-r--r-- | .gitignore | 13 | ||||
-rw-r--r-- | CMakeLists.txt | 6 | ||||
-rw-r--r-- | LICENSE | 29 | ||||
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | main.c | 290 |
5 files changed, 339 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17ed007 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.idea/ +cmake-build-*/ +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..18a7186 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8) +project(idcmp C) + +set(CMAKE_C_STANDARD 99) + +add_executable(idcmp main.c) @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020, Joseph Hunkeler +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1773bba --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# idcmp @@ -0,0 +1,290 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <grp.h> +#include <pwd.h> +#include <limits.h> +#include <unistd.h> + +#if defined(__APPLE__) + // Force Darwin to emit more than 32 damn groups + #undef NGROUPS_MAX + #define NGROUPS_MAX 0x10000 +#endif + +struct Groups { + gid_t *gids; + int ngids; + struct group **group; +}; + +struct User { + struct passwd *pw; + struct Groups *gr; +}; + +struct User *user_init() { + struct User *result; + + result = malloc(sizeof(*result)); + result->pw = malloc(sizeof(*result->pw)); + result->gr = malloc(sizeof(*result->gr)); + result->gr->ngids = NGROUPS_MAX; + result->gr->gids = malloc(NGROUPS_MAX * sizeof(*result->gr->gids)); + result->gr->group = calloc(result->gr->ngids, sizeof(**result->gr->group)); + return result; +} + +struct User *get_account_info(char *user) { + struct passwd *pw; + struct group *gr; + struct User *result; + + if ((pw = getpwnam(user)) == NULL) { + return NULL; + } + + // getpwnam returns static storage, so copy contents. + result = user_init(); + result->pw->pw_name = strdup(pw->pw_name); + result->pw->pw_gid = pw->pw_gid; + result->pw->pw_dir = strdup(pw->pw_dir); + result->pw->pw_gecos = strdup(pw->pw_gecos); + result->pw->pw_shell = strdup(pw->pw_shell); + result->pw->pw_uid = pw->pw_uid; + result->pw->pw_passwd = strdup(pw->pw_passwd); + + // populate gid list + if (getgrouplist(result->pw->pw_name, result->pw->pw_gid, result->gr->gids, &result->gr->ngids) == -1) { + perror("oh no"); + exit(EXIT_FAILURE); + } + + // getgrgid returns static storage, so copy contents. + for (size_t i = 0; i < result->gr->ngids; i++) { + result->gr->group[i] = calloc(1, sizeof(*result->gr->group[i])); + gr = getgrgid(result->gr->gids[i]); + if (!gr) { + fprintf(stderr, "Failed to read group information for GID: %d\n", result->gr->gids[i]); + result->gr->group[i]->gr_name = strdup("(no name)"); + result->gr->group[i]->gr_gid = result->gr->gids[i]; + result->gr->group[i]->gr_mem = NULL; + result->gr->group[i]->gr_passwd = strdup(""); + continue; + } + result->gr->group[i]->gr_name = strdup(gr->gr_name); + result->gr->group[i]->gr_gid = gr->gr_gid; + + size_t j = 0; + for (j = 0; gr->gr_mem[j] != NULL; j++); + result->gr->group[i]->gr_mem = calloc(j + 1, sizeof(*result->gr->group[i]->gr_mem)); + + for (j = 0; gr->gr_mem[j] != NULL; j++) { + result->gr->group[i]->gr_mem[j] = strdup(gr->gr_mem[j]); + } + + result->gr->group[i]->gr_passwd = strdup(gr->gr_passwd); + } + + return result; +} + +void free_account_info(struct User *user) { + free(user->pw->pw_name); + free(user->pw->pw_shell); + free(user->pw->pw_gecos); + free(user->pw->pw_dir); + free(user->pw->pw_passwd); + free(user->pw); + + for (size_t i = 0; i < user->gr->ngids; i++) { + for (size_t j = 0; user->gr->group[i]->gr_mem != NULL && user->gr->group[i]->gr_mem[j] != NULL; j++) { + free(user->gr->group[i]->gr_mem[j]); + } + free(user->gr->group[i]->gr_mem); + free(user->gr->group[i]->gr_passwd); + free(user->gr->group[i]->gr_name); + free(user->gr->group[i]); + } + free(user->gr->group); + free(user->gr->gids); + free(user->gr); + free(user); +} + +int has_group(struct User *user, char *name) { + int found; + + found = 0; + for (size_t i = 0; i < user->gr->ngids; i++) { + if (user->gr->group[i] == NULL) { + continue; + } + if (strcmp(user->gr->group[i]->gr_name, name) == 0) { + found = 1; + break; + } + } + return found; +} + +int group_compare(struct User *a, struct User *b, char *name) { + int b_contains_a, a_contains_b; + + a_contains_b = has_group(b, name); + b_contains_a = has_group(a, name); + + if(a_contains_b < b_contains_a) { + return -1; + } else if (a_contains_b > b_contains_a) { + return 1; + } + return 0; +} + +static int group_sort_name(const void *a, const void *b) { + struct group *aa = (*(struct group **) a); + struct group *bb = (*(struct group **) b); + + if (strcmp(aa->gr_name, bb->gr_name) < 0) { + return -1; + } else if (strcmp(aa->gr_name, bb->gr_name) > 0) { + return 1; + } + return 0; +} + +static int group_sort_id(const void *a, const void *b) { + struct group *aa = (*(struct group **) a); + struct group *bb = (*(struct group **) b); + + if (aa->gr_gid < bb->gr_gid) { + return -1; + } else if (aa->gr_gid > bb->gr_gid) { + return 1; + } + return 0; +} + +/** + * Return the length of the longest string in an array + */ +size_t strmax(char **arr) { + size_t result; + + result = 0; + for (int i = 1; arr[i] != NULL; i++) { + if (strlen(arr[i]) > strlen(arr[result])) { + result = i; + } + } + return strlen(arr[result]); +} + + +typedef int (*compar)(const void *, const void *); + +int main(int argc, char *argv[]) { + struct User *user_a, *user_b; + int rec; + int ngroup_all; + struct group **group_all; + char **group_names; + compar fn_sort = &group_sort_id; + + if (argc < 3) { + fprintf(stderr, "usage: %s [-n] {user_a} {user_b}\n", argv[0]); + exit(EXIT_FAILURE); + } + + int arg; + size_t positional; + char *users[2]; + for (arg = 1, positional = 0; arg < argc; arg++) { + if (strcmp(argv[arg], "-n") == 0) { + fn_sort = &group_sort_name; + continue; + } + if (positional > 1) { + break; + } + users[positional] = argv[arg]; + positional++; + } + + if ((user_a = get_account_info(users[0])) == NULL) { + fprintf(stderr, "Invalid user: '%s'\n", argv[1]); + exit(EXIT_FAILURE); + } + + if ((user_b = get_account_info(users[1])) == NULL) { + fprintf(stderr, "Invalid user: '%s'\n", argv[2]); + exit(EXIT_FAILURE); + } + + ngroup_all = user_a->gr->ngids + user_b->gr->ngids; + group_all = calloc(ngroup_all, sizeof(struct group *)); + group_names = calloc(ngroup_all, sizeof(char *)); + + // Merge all group into a single list + rec = 0; + for (rec = 0; rec < user_a->gr->ngids; rec++) { + group_all[rec] = user_a->gr->group[rec]; + } + for (int i = 0; i < user_b->gr->ngids; i++) { + if (has_group(user_a, user_b->gr->group[i]->gr_name)) { + continue; + } + group_all[rec] = user_b->gr->group[i]; + rec++; + } + + qsort(group_all, rec, sizeof(struct group *), fn_sort); + + // Extract all group names from both users + for (size_t i = 0; group_all[i] != NULL; i++) { + group_names[i] = group_all[i]->gr_name; + } + + // Construct dynamic width output header + int maxfmt = 255; + char *fmthdr = malloc(maxfmt); + char *fmt = malloc(maxfmt); + char *hdr[] = { + "GID NAME", "GID", "COMMON TO", NULL + }; + size_t longest_group = strmax(group_names); + + // Generate column titles + sprintf(fmthdr, "%%-%zus %%-5s %%s\n", longest_group); + printf(fmthdr, hdr[0], hdr[1], hdr[2]); + + // Display records + sprintf(fmt, "%%-%zus | %%-5d | ", longest_group); + for (size_t i = 0; i < rec; i++) { + int contains; + + printf(fmt, group_all[i]->gr_name, group_all[i]->gr_gid); + + contains = group_compare(user_a, user_b, group_all[i]->gr_name); + if (contains < 0) { + puts(user_a->pw->pw_name); + } else if (contains > 0){ + puts(user_b->pw->pw_name); + } else { + puts("both"); + } + } + + free(fmthdr); + free(fmt); + free_account_info(user_a); + free_account_info(user_b); + free(group_names); + for (size_t i = 0; i < ngroup_all; i++) { + group_all[i] = NULL; + free(group_all[i]); + } + free(group_all); + return 0; +} |