From 463b5543f785f68c5a8978adc8010aab8f0fe0bb Mon Sep 17 00:00:00 2001 From: "R. Steve McKown" Date: Wed, 26 Sep 2012 15:26:24 -0600 Subject: [PATCH] Code for supporting git ACLs. acl_test is useful. --- .gitignore | 2 + Makefile | 16 +++- acl_test.c | 35 ++++++++ git_acl.c | 210 ++++++++++++++++++++++++++++++++++++++++++++ git_acl.cfg.example | 62 +++++++++++++ git_acl.h | 51 +++++++++++ mystrtok.c | 41 +++++++++ mystrtok.h | 23 +++++ mystrtok_test.c | 24 +++++ stra.c | 100 +++++++++++++++++++++ stra.h | 52 +++++++++++ utility.c | 84 ++++++++++++++++++ utility.h | 30 +++++++ 13 files changed, 727 insertions(+), 3 deletions(-) create mode 100644 acl_test.c create mode 100644 git_acl.c create mode 100644 git_acl.cfg.example create mode 100644 git_acl.h create mode 100644 mystrtok.c create mode 100644 mystrtok.h create mode 100644 mystrtok_test.c create mode 100644 stra.c create mode 100644 stra.h create mode 100644 utility.c create mode 100644 utility.h diff --git a/.gitignore b/.gitignore index f4255f9..c7859a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ repo_shell +acl_test +mystrtok_test version.c *.swp .svn diff --git a/Makefile b/Makefile index dc3b5f8..f625997 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PROGRAM := repo_shell +MAIN := repo_shell # Git derives its version "prefix" from tags # Add TMI's mkversion to render files xxx.in -> xxx, updating __appVersion__ @@ -11,7 +11,17 @@ INFILES := $(shell ls *.in 2>/dev/null) INFILES := $(INFILES:%.in=%) #$(warning INFILES $(INFILES)) -repo_shell: repo_shell.c inih/ini.c version.c +PROGRAMS = $(MAIN) mystrtok_test acl_test + +all: $(MAIN) + +repo_shell: repo_shell.c inih/ini.c mystrtok.c stra.c utility.c version.c + $(CC) -I inih $^ -o $@ + +mystrtok_test: mystrtok_test.c mystrtok.c utility.c + $(CC) -g $^ -o $@ + +acl_test: acl_test.c git_acl.c inih/ini.c mystrtok.c stra.c utility.c $(CC) -I inih $^ -o $@ $(INFILES):%: %.in @@ -19,6 +29,6 @@ $(INFILES):%: %.in @diff -q $@-tmp $@ >/dev/null 2>&1 && rm -f $@-tmp || mv $@-tmp $@ clean: - @rm -rf $(PROGRAM) $(INFILES) + @rm -rf $(PROGRAMS) $(INFILES) .PHONY: all clean $(INFILES) diff --git a/acl_test.c b/acl_test.c new file mode 100644 index 0000000..f8d5f71 --- /dev/null +++ b/acl_test.c @@ -0,0 +1,35 @@ +/* + * Copyright © 2012, Titanium Mirror, Inc. + * All Rights Reserved. + * + * This document is the proprietary and confidential property of + * Titanium Mirror, Inc. All use, distribution, reproduction or re-distribution + * is disallowed without the prior express written consent of + * Titanium Mirror, Inc. + */ + +/* + * Test parsing of the git acl config file + * + * @author R. Steve McKown + */ + +#include +#include "git_acl.h" + +int main(int argc, char **argv) +{ + char *file = "git_acl.cfg.example"; + perms_t p; + + if (argc != 3) + die("usage: %s \n", argv[0]); + + p = git_acl(argv[1], argv[2], file); /* file=NULL for default */ + + printf("config file %s\n" + "GITACL(%s@%s) = %s(%u)\n via repoid='%s', userid='%s'\n", + file, argv[1], argv[2], git_acl_perms_as_str(p), p, + git_acl_last_repoid(), git_acl_last_userid()); + return 0; +} diff --git a/git_acl.c b/git_acl.c new file mode 100644 index 0000000..e077f78 --- /dev/null +++ b/git_acl.c @@ -0,0 +1,210 @@ +/* + * Copyright © 2012, Titanium Mirror, Inc. + * All Rights Reserved. + * + * This document is the proprietary and confidential property of + * Titanium Mirror, Inc. All use, distribution, reproduction or re-distribution + * is disallowed without the prior express written consent of + * Titanium Mirror, Inc. + */ + +/* + * Parse the git_acl.cfg file to find the first permissions therein matching the + * requested user and repo names. + * + * @author R. Steve McKown + */ + +#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +#include +#include "ini.h" +#include "utility.h" +#include "mystrtok.h" +#include "stra.h" +#include "git_acl.h" + +enum { + DFLT_IDS_SZ = 32 +}; + +typedef struct { + char *repo; + char *user; + stra_t repoids; + stra_t userids; + perms_t perms; +} acl_t; + +const char* perm_str[PERMS_COUNT] = { + "NOTFOUND", "NONE", "READ", "READ_WRITE" +}; + +const char* lm_none = ""; +static char *lm_repoid = NULL; +static char *lm_userid = NULL; +static char lm_perms = PERMS_NOTFOUND; + +static char *get_lm_repoid() +{ + return (lm_repoid) ? lm_repoid : (char*)lm_none; +} + +static void set_lm_repoid(const char* newstr) +{ + if (lm_repoid) + free(lm_repoid); + if (newstr) + lm_repoid = xstrdup(newstr); +} + +static char *get_lm_userid() +{ + return (lm_userid) ? lm_userid : (char*)lm_none; +} + +static void set_lm_userid(const char* newstr) +{ + if (lm_userid) + free(lm_userid); + if (newstr) + lm_userid = xstrdup(newstr); +} + +static const char *perms_as_str(perms_t p) +{ + if (p < PERMS_NOTFOUND || p >= PERMS_COUNT) + die("perms_as_str: invalid perm %u", p); + return perm_str[p]; +} + +static perms_t perms_from_str(const char *str) +{ + perms_t p = PERMS_NOTFOUND; + + if (!str) + return PERMS_NOTFOUND; + else if (!*str) + return PERMS_NONE; + else if (!strcmp(str, "r")) + return PERMS_READ; + else if (!strcmp(str, "rw")) + return PERMS_READ_WRITE; + else + die("Invalid perms value '%s'", str); +} + +static acl_init(acl_t *acl) +{ + acl->repo = acl->user = NULL; + stra_init(&acl->repoids, DFLT_IDS_SZ); + stra_init(&acl->userids, DFLT_IDS_SZ); + acl->perms = PERMS_NOTFOUND; +} + +static acl_clear(acl_t *acl) +{ + stra_destroy(&acl->repoids); + stra_destroy(&acl->userids); +} + +static int str_has_word(const char* string, const char* word) +{ + char *_s = xstrdup(string); + char *s = _s; + char *p = my_strtok(&s, " \t\n"); + + while (p) { + if (!strcmp(p, word)) { + free(_s); + return true; + } + p = my_strtok(&s, " \t\n"); + } + free(_s); + return false; +} + +static int acl_handler(void* user, const char* section, const char* name, + const char* value) +{ + acl_t* acl = (acl_t*)user; + + if (acl->perms) + return 1; /* Already matched an ACL entry for repo+user */ + + //debug("section='%s', name='%s', value='%s'", section, name, value); + + if (!strcmp(section, "user_groups")) { + if (str_has_word(value, acl->user)) { + //debug("userids += '%s'", name); + stra_add(&acl->userids, name); + } + } else if (!strcmp(section, "repo_groups")) { + if (str_has_word(value, acl->repo)) { + //debug("repoids += '%s'", name); + stra_add(&acl->repoids, name); + } + } else if (!strncmp(section, "repo", 4)) { + char *_p = xstrdup(section + 4); + char *p = _p; + char *repo = my_strtok(&p, " \t\n"); + + if (!repo || my_strtok(&p, " \t\n")) + die("acl_handler: badly formatted section '%s'", section); + /* repo is repo name, name is userid, value is permission */ + if (stra_find(&acl->repoids, repo) >= 0 && + stra_find(&acl->userids, name) >= 0) { + acl->perms = perms_from_str(value); + set_lm_repoid(repo); + set_lm_userid(name); + lm_perms = acl->perms; + //debug("match: repoid='%s', userid='%s', perms='%s'(%u)", repo, name, + // value, acl->perms); + } + free(_p); + } else + die("acl_handler: unknown section='%s' name='%s'", section, name); + return 1; +} + +int git_acl(char *user, char *repo, char *file) +{ + acl_t acl; + + if (!file || !*file) + file = GIT_ACL_FILE; + if (!user || !*user || !repo || !*repo) { + die("git_acl: invalid args user='%s', repo='%s', file='%s'", user, repo, + file); + } + + set_lm_repoid(NULL); + set_lm_userid(NULL); + lm_perms = PERMS_NOTFOUND; + acl_init(&acl); + acl.user = user; + acl.repo = repo; + stra_add(&acl.userids, acl.user); + stra_add(&acl.repoids, acl.repo); + + //debug("Searching for '%s'@'%s'", acl.user, acl.repo); + if (ini_parse(file, acl_handler, &acl) < 0) + die("cannot read acl file %s", file); + acl_clear(&acl); /* acl.perms remains unchanged */ + return acl.perms; +} + +char *git_acl_last_repoid() { return get_lm_repoid(); } + +char *git_acl_last_userid() { return get_lm_userid(); } + +int git_acl_last_perms() { return lm_perms; } + +const char *git_acl_perms_as_str(perms_t p) { return perms_as_str(p); } diff --git a/git_acl.cfg.example b/git_acl.cfg.example new file mode 100644 index 0000000..0b85beb --- /dev/null +++ b/git_acl.cfg.example @@ -0,0 +1,62 @@ +# git_acl.cfg for use with repo_shell +# +# The [user_groups] section contains lines of the form: +# = user1 user2 ... userN +# +# The [repo_groups] section contains lines of the form: +# = repo1 repo2 ... repoN +# +# Nested user and repo groups are not supported. Therefore, user group names +# (ugroup or rgroup) used as an element defining another group will treat the +# former as a user or a repo, respectively. +# +# The [repo ] section contains lines of the form: +# = +# These lines define the permissions () for one or more 's +# (users or user groups) for the repository (or repository group) . +# +# The first line in the first [repo ] section matching the user +# and repo for which permissions are requested will satisfy the request. Any +# other possible matches later in the configuration file are irrelevant. +# +# A user/repo combination that has no match in the git_acl.cfg file is reported +# as the separate NOTFOUND permission, but is effectively treated the same as +# the NONE permission. + +[user_groups] +devs = smckown dlotton jobu +losers = frank bill ted mike + +[repo_groups] +mirrors = mirrors/tinyos.git mirrors/chibios.git +public = oss-web.git repo_shell.git cp210x.git +private = redmine.git nesc.git + +[repo nesc.git] +smckown = +ted = rw +losers = r + +[repo oss-web.git] +smckown = rw +devs = r + +[repo mirrors/tinyos.git] +jobu = rw +smckown = r +devs = - +losers = r + +[repo public] +dlotton = rw +devs = - +losers = r + +[repo private] +smckown = rw +dlotton = rw +devs = + +[repo mirrors] +jobu = r +devs = rw diff --git a/git_acl.h b/git_acl.h new file mode 100644 index 0000000..80440cc --- /dev/null +++ b/git_acl.h @@ -0,0 +1,51 @@ +/* + * Copyright © 2012, Titanium Mirror, Inc. + * All Rights Reserved. + * + * This document is the proprietary and confidential property of + * Titanium Mirror, Inc. All use, distribution, reproduction or re-distribution + * is disallowed without the prior express written consent of + * Titanium Mirror, Inc. + */ + +/* + * Parse the git_acl.cfg file to find the first permissions therein matching the + * requested user and repo names. + * + * @author R. Steve McKown + */ + +#ifndef GIT_ACL_H +#define GIT_ACL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ini.h" +#include "utility.h" +#include "mystrtok.h" +#include "stra.h" + +#define GIT_ACL_FILE "git_acl.cfg" + +typedef enum { + PERMS_NOTFOUND = 0, + PERMS_NONE, + PERMS_READ, + PERMS_READ_WRITE, + PERMS_COUNT +} perms_t; + +int git_acl(char *user, char *repo, char *file); +char *git_acl_last_repoid(); +char *git_acl_last_userid(); +int git_acl_last_perms(); +const char *git_acl_perms_as_str(perms_t p); + +#endif /* end of include guard: GIT_ACL_H */ diff --git a/mystrtok.c b/mystrtok.c new file mode 100644 index 0000000..f075683 --- /dev/null +++ b/mystrtok.c @@ -0,0 +1,41 @@ +/* + * Copyright © 2012, Titanium Mirror, Inc. + * All Rights Reserved. + * + * This document is the proprietary and confidential property of + * Titanium Mirror, Inc. All use, distribution, reproduction or re-distribution + * is disallowed without the prior express written consent of + * Titanium Mirror, Inc. + */ + +/* + * A custom strtok. This version skips any number and any combination of + * delimiter characters between tokens. + * + * @author R. Steve McKown + */ + +#include +#include + +char *my_strtok(char **begin, char* delims) +{ + char *p; + char *q; + + if (!begin) + return NULL; + if (!delims || !*delims || !*begin) + return (*begin = NULL); + + q = *begin; + while (*q && index(delims, *q)) q++; + if (!*q) + return (*begin = NULL); + p = q; + while (*q && !index(delims, *q)) q++; + if (*q) + *q++ = 0; + *begin = q; + return p; +} diff --git a/mystrtok.h b/mystrtok.h new file mode 100644 index 0000000..6968fa8 --- /dev/null +++ b/mystrtok.h @@ -0,0 +1,23 @@ +/* + * Copyright © 2012, Titanium Mirror, Inc. + * All Rights Reserved. + * + * This document is the proprietary and confidential property of + * Titanium Mirror, Inc. All use, distribution, reproduction or re-distribution + * is disallowed without the prior express written consent of + * Titanium Mirror, Inc. + */ + +/* + * A custom strtok. This version skips any number and any combination of + * delimiter characters between tokens. + * + * @author R. Steve McKown + */ + +#ifndef MYSTRTOK_H +#define MYSTRTOK_H + +char *my_strtok(char **begin, char* delims); + +#endif /* end of include guard: MYSTRTOK_H */ diff --git a/mystrtok_test.c b/mystrtok_test.c new file mode 100644 index 0000000..5a8756e --- /dev/null +++ b/mystrtok_test.c @@ -0,0 +1,24 @@ +#include +#include "utility.h" +#include "mystrtok.h" + +int main(int argc, char **argv) +{ + char *text; + char *p; + + if (argc != 3) { + fprintf(stderr, "usage: %s \"A string to parse\" \"delimiters\"\n", argv[0]); + return 0; + } + + text = argv[1]; + putchar('|'); + p = my_strtok(&text, argv[2]); + while (p) { + printf("%s|", p); + p = my_strtok(&text, argv[2]); + } + putchar('\n'); + return 0; +} diff --git a/stra.c b/stra.c new file mode 100644 index 0000000..4e2f04b --- /dev/null +++ b/stra.c @@ -0,0 +1,100 @@ +/* + * Copyright © 2012, Titanium Mirror, Inc. + * All Rights Reserved. + * + * This document is the proprietary and confidential property of + * Titanium Mirror, Inc. All use, distribution, reproduction or re-distribution + * is disallowed without the prior express written consent of + * Titanium Mirror, Inc. + */ + +/* + * A dynamic string array implementation. + * + * @author R. Steve McKown + */ + +#include +#include "utility.h" +#include "stra.h" + +void stra_init(stra_t *stra, size_t size) +{ + if (!stra) + die("stra_destroy: stra NULL reference"); + stra->items = xmalloc(sizeof(char*) * size); + stra->count = 0; + stra->size = size; +} + +void stra_destroy(stra_t *stra) +{ + unsigned i; + + if (!stra) + die("stra_destroy: stra NULL reference"); + for (i = 0; i < stra->count; i++) + free(stra->items[i]); + if (stra->size) { + free(stra->items); + stra->size = 0; + } +} + +int stra_add(stra_t *stra, const char *item) +{ + if (!stra) + die("stra_add: stra NULL reference"); + if (!item) + die("stra_in: item is NULL"); + if (stra->count > stra->size) + die("str_add: bad count=%u, size=%u\n", stra->count, stra->size); + else if (stra->count == stra->size) { + /* Expand items[] */ + if (stra->size) + stra->size *= 2; + else + stra->size = 1; + stra->items = xrealloc(stra->items, stra->size); + } + stra->items[stra->count] = xstrdup(item); + return stra->count++; +} + +int stra_find(stra_t *stra, const char *item) +{ + unsigned i; + + if (!stra) + die("stra_in: stra NULL reference"); + if (!item) + die("stra_in: item is NULL"); + for (i = 0; i < stra->count; i++) { + if (!strcmp(stra->items[i], item)) + return i; + } + return -1; +} + +size_t stra_count(stra_t *stra) +{ + if (!stra) + die("stra_count: stra NULL reference"); + return stra->count; +} + +size_t stra_size(stra_t *stra) +{ + if (!stra) + die("stra_size: stra NULL reference"); + return stra->size; +} + +char *stra_item(stra_t *stra, size_t ele) +{ + if (!stra) + die("stra_item: stra NULL reference"); + if (ele > stra->count) + die("stra_item: ele out of range (%u > %u)", ele, stra->count); + return stra->items[ele]; +} diff --git a/stra.h b/stra.h new file mode 100644 index 0000000..a2f43d6 --- /dev/null +++ b/stra.h @@ -0,0 +1,52 @@ +/* + * Copyright © 2012, Titanium Mirror, Inc. + * All Rights Reserved. + * + * This document is the proprietary and confidential property of + * Titanium Mirror, Inc. All use, distribution, reproduction or re-distribution + * is disallowed without the prior express written consent of + * Titanium Mirror, Inc. + */ + +/* + * A dynamic string array implementation. + * + * @author R. Steve McKown + */ + + +#ifndef STRA_H +#define STRA_H + +#include + +typedef int (*stra_fneq_t)(void *item1, void *item2); + +typedef struct { + size_t count; + size_t size; + char **items; +} stra_t; + +/* Initialize an stra structure, to hold up to size strings */ +void stra_init(stra_t *stra, size_t size); + +/* Destroy all dynamic memory associated with the stra. Use init to reuse */ +void stra_destroy(stra_t *stra); + +/* Add an item to the end of the string array, returning its ele# */ +int stra_add(stra_t *stra, const char *item); + +/* Return the ele# of the first string matching item */ +int stra_find(stra_t *stra, const char *item); + +/* Return the count of items in the stra */ +size_t stra_count(stra_t *stra); + +/* Return the current size of the stra */ +size_t stra_size(stra_t *stra); + +/* Return a pointer to the item at ele# ele */ +char *stra_item(stra_t *stra, size_t ele); + +#endif /* end of include guard: STRA_H */ diff --git a/utility.c b/utility.c new file mode 100644 index 0000000..ba7373b --- /dev/null +++ b/utility.c @@ -0,0 +1,84 @@ +/* + * Copyright © 2012, Titanium Mirror, Inc. + * All Rights Reserved. + * + * This document is the proprietary and confidential property of + * Titanium Mirror, Inc. All use, distribution, reproduction or re-distribution + * is disallowed without the prior express written consent of + * Titanium Mirror, Inc. + */ + +/* + * Common functions for command line utility programs + * + * @author R. Steve McKown + */ + +#include +#include +#include +#include +#include "utility.h" + +void debug(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "debug: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n" ); + va_end(ap); +} + +void die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "error: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n" ); + va_end(ap); + exit(1); +} + +void *xmalloc(size_t size) +{ + void *ret; + + if (!size) + die("xmalloc: size cannot be zero"); + ret = malloc(size); + if (!ret) + die("out of memory (malloc)"); + return ret; +} + +void *xrealloc(void *ptr, size_t size) +{ + void *ret; + + if (!size) + die("xrealloc: size cannot be zero"); + ret = realloc(ptr, size); + if (!ret) + die("out of memory (realloc)"); + return ret; +} + +char *xstrdup(const char *str) +{ + char *ret = strdup(str); + if (!ret) + die("out of memory (strdup)"); + return ret; +} + +char *xstrndup(const char *str, size_t n) +{ + char *ret = strndup(str, n); + if (!ret) + die("out of memory (strndup)"); + return ret; +} diff --git a/utility.h b/utility.h new file mode 100644 index 0000000..e17192c --- /dev/null +++ b/utility.h @@ -0,0 +1,30 @@ +/* + * Copyright © 2012, Titanium Mirror, Inc. + * All Rights Reserved. + * + * This document is the proprietary and confidential property of + * Titanium Mirror, Inc. All use, distribution, reproduction or re-distribution + * is disallowed without the prior express written consent of + * Titanium Mirror, Inc. + */ + +/* + * Common functions for command line utility programs + * + * @author R. Steve McKown + */ + +#ifndef UTILITY_H +#define UTILITY_H + +#include +#include + +void debug(const char *fmt, ...); +void die(const char *fmt, ...); +void *xmalloc(size_t size); +void *xrealloc(void *ptr, size_t size); +char *xstrdup(const char *str); +char *xstrndup(const char *str, size_t n); + +#endif /* end of include guard: UTILITY_H */ -- 2.39.2