diff options
Diffstat (limited to 'uploaderslist.c')
-rw-r--r-- | uploaderslist.c | 1520 |
1 files changed, 1520 insertions, 0 deletions
diff --git a/uploaderslist.c b/uploaderslist.c new file mode 100644 index 0000000..3e50a36 --- /dev/null +++ b/uploaderslist.c @@ -0,0 +1,1520 @@ +/* This file is part of "reprepro" + * Copyright (C) 2005,2006,2007,2009,2011 Bernhard R. Link + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA + */ +#include <config.h> + +#include <errno.h> +#include <assert.h> +#include <unistd.h> +#include <stdlib.h> +#include <alloca.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include "error.h" +#include "mprintf.h" +#include "strlist.h" +#include "names.h" +#include "atoms.h" +#include "signature.h" +#include "globmatch.h" +#include "uploaderslist.h" +#include "configparser.h" +#include "ignore.h" + +struct upload_condition { + /* linked list of all sub-nodes */ + /*@null@*/struct upload_condition *next; + + enum upload_condition_type type; + const struct upload_condition *next_if_true, *next_if_false; + bool accept_if_true, accept_if_false; + enum { + /* none matching means false, at least one being from + * the set means true */ + needs_any = 0, + /* one not matching means false, otherwise true */ + needs_all, + /* one not matching means false, + * otherwise true iff there is at least one */ + needs_existsall, + /* having a candidate means true, otherwise false */ + needs_anycandidate + } needs; + union { + /* uc_SECTIONS, uc_BINARIES, uc_SOURCENAME, uc_BYHAND, + * uc_CODENAME, */ + struct strlist strings; + /* uc_COMPONENTS, uc_ARCHITECTURES */ + struct atomlist atoms; + }; +}; +struct upload_conditions { + /* condition currently tested */ + const struct upload_condition *current; + /* current state of top most condition */ + bool matching; + /* top most condition will not be true unless cleared*/ + bool needscandidate; + /* always use last next, then decrement */ + int count; + const struct upload_condition *conditions[]; +}; + +static retvalue upload_conditions_add(struct upload_conditions **c_p, const struct upload_condition *a) { + int newcount; + struct upload_conditions *n; + + if (a->type == uc_REJECTED) { + /* due to groups, there can be empty conditions. + * Don't include those in this list... */ + return RET_OK; + } + + if (*c_p == NULL) + newcount = 1; + else + newcount = (*c_p)->count + 1; + n = realloc(*c_p, sizeof(struct upload_conditions) + + newcount * sizeof(const struct upload_condition*)); + if (FAILEDTOALLOC(n)) + return RET_ERROR_OOM; + n->current = NULL; + n->count = newcount; + n->conditions[newcount - 1] = a; + *c_p = n; + return RET_OK; +} + +struct fileposition { + const struct filebeingparsed { + struct filebeingparsed *next, *includedby; + char *filename; + unsigned long lineno; + FILE *f; + int depth; + } *file; + unsigned long lineno; +}; + +#define set_position(at, fbp) ({ \ + (at).file = fbp; \ + (at).lineno = fbp->lineno; \ +}) +#define unset_pos(fileposition) ((fileposition).lineno == 0) +#define errorcol(fbp, column, format, ...) ({ \ + fprintf(stderr, "%s:%lu:%u: ", (fbp)->filename, (fbp)->lineno, (column)); \ + fprintf(stderr, format "\n" , ## __VA_ARGS__); \ + print_include_trace((fbp)->includedby); \ +}) +#define errorline(fbp, format, ...) ({ \ + fprintf(stderr, "%s:%lu: ", (fbp)->filename, (fbp)->lineno); \ + fprintf(stderr, format "\n" , ## __VA_ARGS__); \ + print_include_trace((fbp)->includedby); \ +}) +#define errorpos(pos, format, ...) ({ \ + fprintf(stderr, "%s:%lu: ", (pos).file->filename, (pos).lineno); \ + fprintf(stderr, format "\n" , ## __VA_ARGS__); \ +}) + +static void print_include_trace(struct filebeingparsed *includedby) { + for ( ; includedby != NULL ; includedby = includedby->includedby ) { + fprintf(stderr, "included from '%s' line %lu\n", + includedby->filename, + includedby->lineno); + } +} + + +struct uploadergroup { + struct uploadergroup *next; + size_t len; + char *name; + /* NULL terminated list of pointers, or NULL for none */ + const struct uploadergroup **memberof; + struct upload_condition permissions; + /* line numbers (if != 0) to allow some diagnostics */ + struct fileposition firstmemberat, emptyat, firstusedat, unusedat; +}; + +struct uploader { + struct uploader *next; + /* NULL terminated list of pointers, or NULL for none */ + const struct uploadergroup **memberof; + size_t len; + char *reversed_fingerprint; + struct upload_condition permissions; + bool allow_subkeys; +}; + +static struct uploaders { + struct uploaders *next; + size_t reference_count; + char *filename; + size_t filename_len; + + struct uploadergroup *groups; + struct uploader *by_fingerprint; + struct upload_condition anyvalidkeypermissions; + struct upload_condition unsignedpermissions; + struct upload_condition anybodypermissions; +} *uploaderslists = NULL; + +static void uploadpermission_release(struct upload_condition *p) { + struct upload_condition *h, *f = NULL; + + assert (p != NULL); + + do { + h = p->next; + switch (p->type) { + case uc_BINARIES: + case uc_SECTIONS: + case uc_SOURCENAME: + case uc_BYHAND: + case uc_CODENAME: + strlist_done(&p->strings); + break; + + case uc_ARCHITECTURES: + atomlist_done(&p->atoms); + break; + + case uc_ALWAYS: + case uc_REJECTED: + break; + } + free(f); + /* next one must be freed: */ + f = h; + /* and processed: */ + p = h; + } while (p != NULL); +} + +static void uploadergroup_free(struct uploadergroup *u) { + if (u == NULL) + return; + free(u->name); + free(u->memberof); + uploadpermission_release(&u->permissions); + free(u); +} + +static void uploader_free(struct uploader *u) { + if (u == NULL) + return; + free(u->reversed_fingerprint); + free(u->memberof); + uploadpermission_release(&u->permissions); + free(u); +} + +static void uploaders_free(struct uploaders *u) { + if (u == NULL) + return; + while (u->by_fingerprint != NULL) { + struct uploader *next = u->by_fingerprint->next; + + uploader_free(u->by_fingerprint); + u->by_fingerprint = next; + } + while (u->groups != NULL) { + struct uploadergroup *next = u->groups->next; + + uploadergroup_free(u->groups); + u->groups = next; + } + uploadpermission_release(&u->anyvalidkeypermissions); + uploadpermission_release(&u->anybodypermissions); + uploadpermission_release(&u->unsignedpermissions); + free(u->filename); + free(u); +} + +void uploaders_unlock(struct uploaders *u) { + if (u->reference_count > 1) { + u->reference_count--; + } else { + struct uploaders **p = &uploaderslists; + + assert (u->reference_count == 1); + /* avoid double free: */ + if (u->reference_count == 0) + return; + + while (*p != NULL && *p != u) + p = &(*p)->next; + assert (p != NULL && *p == u); + if (*p == u) { + *p = u->next; + uploaders_free(u); + } + } +} + +static retvalue upload_conditions_add_group(struct upload_conditions **c_p, const struct uploadergroup **groups) { + const struct uploadergroup *group; + retvalue r; + + while ((group = *(groups++)) != NULL) { + r = upload_conditions_add(c_p, &group->permissions); + if (!RET_WAS_ERROR(r) && group->memberof != NULL) + r = upload_conditions_add_group(c_p, group->memberof); + if (RET_WAS_ERROR(r)) + return r; + } + return RET_OK; +} + +static retvalue find_key_and_add(struct uploaders *u, struct upload_conditions **c_p, const struct signature *s) { + size_t len, i, primary_len; + char *reversed; + const char *fingerprint, *primary_fingerprint; + char *reversed_primary_key; + const struct uploader *uploader; + retvalue r; + + assert (u != NULL); + + fingerprint = s->keyid; + assert (fingerprint != NULL); + len = strlen(fingerprint); + reversed = alloca(len+1); + if (FAILEDTOALLOC(reversed)) + return RET_ERROR_OOM; + for (i = 0 ; i < len ; i++) { + char c = fingerprint[len-i-1]; + if (c >= 'a' && c <= 'f') + c -= 'a' - 'A'; + else if (c == 'x' && len-i-1 == 1 && fingerprint[0] == '0') + break; + if ((c < '0' || c > '9') && (c <'A' || c > 'F')) { + fprintf(stderr, +"Strange character '%c'(=%hhu) in fingerprint '%s'.\n" +"Search for appropriate rules in the uploaders file might fail.\n", + c, c, fingerprint); + break; + } + reversed[i] = c; + } + len = i; + reversed[len] = '\0'; + + /* hm, this only sees the key is expired when it is kind of late... */ + primary_fingerprint = s->primary_keyid; + primary_len = strlen(primary_fingerprint); + reversed_primary_key = alloca(len+1); + if (FAILEDTOALLOC(reversed_primary_key)) + return RET_ERROR_OOM; + + for (i = 0 ; i < primary_len ; i++) { + char c = primary_fingerprint[primary_len-i-1]; + if (c >= 'a' && c <= 'f') + c -= 'a' - 'A'; + else if (c == 'x' && primary_len-i-1 == 1 && + primary_fingerprint[0] == '0') + break; + if ((c < '0' || c > '9') && (c <'A' || c > 'F')) { + fprintf(stderr, +"Strange character '%c'(=%hhu) in fingerprint/key-id '%s'.\n" +"Search for appropriate rules in the uploaders file might fail.\n", + c, c, primary_fingerprint); + break; + } + reversed_primary_key[i] = c; + } + primary_len = i; + reversed_primary_key[primary_len] = '\0'; + + for (uploader = u->by_fingerprint ; uploader != NULL ; + uploader = uploader->next) { + /* TODO: allow ignoring */ + if (s->state != sist_valid) + continue; + if (uploader->allow_subkeys) { + if (uploader->len > primary_len) + continue; + if (memcmp(uploader->reversed_fingerprint, + reversed_primary_key, + uploader->len) != 0) + continue; + } else { + if (uploader->len > len) + continue; + if (memcmp(uploader->reversed_fingerprint, + reversed, uploader->len) != 0) + continue; + } + r = upload_conditions_add(c_p, &uploader->permissions); + if (!RET_WAS_ERROR(r) && uploader->memberof != NULL) + r = upload_conditions_add_group(c_p, + uploader->memberof); + if (RET_WAS_ERROR(r)) + return r; + /* no break here, as a key might match + * multiple specifications of different length */ + } + return RET_OK; +} + +retvalue uploaders_permissions(struct uploaders *u, const struct signatures *signatures, struct upload_conditions **c_p) { + struct upload_conditions *conditions = NULL; + retvalue r; + int j; + + r = upload_conditions_add(&conditions, + &u->anybodypermissions); + if (RET_WAS_ERROR(r)) + return r; + if (signatures == NULL) { + /* signatures.count might be 0 meaning there is + * something lile a gpg header but we could not get + * keys, because of a gpg error or because of being + * compiling without libgpgme */ + r = upload_conditions_add(&conditions, + &u->unsignedpermissions); + if (RET_WAS_ERROR(r)) { + free(conditions); + return r; + } + } + if (signatures != NULL && signatures->validcount > 0) { + r = upload_conditions_add(&conditions, + &u->anyvalidkeypermissions); + if (RET_WAS_ERROR(r)) { + free(conditions); + return r; + } + } + if (signatures != NULL) { + for (j = 0 ; j < signatures->count ; j++) { + r = find_key_and_add(u, &conditions, + &signatures->signatures[j]); + if (RET_WAS_ERROR(r)) { + free(conditions); + return r; + } + } + } + *c_p = conditions; + return RET_OK; +} + +/* uc_FAILED means rejected, uc_ACCEPTED means can go in */ +enum upload_condition_type uploaders_nextcondition(struct upload_conditions *c) { + + if (c->current != NULL) { + if (c->matching && !c->needscandidate) { + if (c->current->accept_if_true) + return uc_ACCEPTED; + c->current = c->current->next_if_true; + } else { + if (c->current->accept_if_false) + return uc_ACCEPTED; + c->current = c->current->next_if_false; + } + } + + /* return the first non-trivial one left: */ + while (true) { + while (c->current != NULL) { + assert (c->current->type > uc_REJECTED); + if (c->current->type == uc_ALWAYS) { + if (c->current->accept_if_true) + return uc_ACCEPTED; + c->current = c->current->next_if_true; + } else { + /* empty set fulfills all conditions, + but not an exists condition */ + switch (c->current->needs) { + case needs_any: + c->matching = false; + c->needscandidate = false; + break; + case needs_all: + c->matching = true; + c->needscandidate = false; + break; + case needs_existsall: + case needs_anycandidate: + c->matching = true; + c->needscandidate = true; + break; + } + return c->current->type; + } + } + if (c->count == 0) + return uc_REJECTED; + c->count--; + c->current = c->conditions[c->count]; + } + /* not reached */ +} + +static bool match_namecheck(const struct strlist *strings, const char *name) { + int i; + + for (i = 0 ; i < strings->count ; i++) { + if (globmatch(name, strings->values[i])) + return true; + } + return false; +} + +bool uploaders_verifystring(struct upload_conditions *conditions, const char *name) { + const struct upload_condition *c = conditions->current; + + assert (c != NULL); + assert (c->type == uc_BINARIES || c->type == uc_SECTIONS || + c->type == uc_CODENAME || + c->type == uc_SOURCENAME || c->type == uc_BYHAND); + + conditions->needscandidate = false; + switch (conditions->current->needs) { + case needs_all: + case needs_existsall: + /* once one condition is false, the case is settled */ + + if (conditions->matching && + !match_namecheck(&c->strings, name)) + conditions->matching = false; + /* but while it is true, more info is needed */ + return conditions->matching; + case needs_any: + /* once one condition is true, the case is settled */ + if (!conditions->matching && + match_namecheck(&c->strings, name)) + conditions->matching = true; + conditions->needscandidate = false; + /* but while it is false, more info is needed */ + return !conditions->matching; + case needs_anycandidate: + /* we are settled, no more information needed */ + return false; + } + /* NOT REACHED */ + assert (conditions->current->needs != conditions->current->needs); +} + +bool uploaders_verifyatom(struct upload_conditions *conditions, atom_t atom) { + const struct upload_condition *c = conditions->current; + + assert (c != NULL); + assert (c->type == uc_ARCHITECTURES); + + conditions->needscandidate = false; + switch (conditions->current->needs) { + case needs_all: + case needs_existsall: + /* once one condition is false, the case is settled */ + + if (conditions->matching && + !atomlist_in(&c->atoms, atom)) + conditions->matching = false; + /* but while it is true, more info is needed */ + return conditions->matching; + case needs_any: + /* once one condition is true, the case is settled */ + if (!conditions->matching && + atomlist_in(&c->atoms, atom)) + conditions->matching = true; + /* but while it is false, more info is needed */ + return !conditions->matching; + case needs_anycandidate: + /* we are settled, no more information needed */ + return false; + } + /* NOT REACHED */ + assert (conditions->current->needs != conditions->current->needs); +} + +static struct uploader *addfingerprint(struct uploaders *u, const char *fingerprint, size_t len, bool allow_subkeys) { + size_t i; + char *reversed = malloc(len+1); + struct uploader *uploader, **last; + + if (FAILEDTOALLOC(reversed)) + return NULL; + for (i = 0 ; i < len ; i++) { + char c = fingerprint[len-i-1]; + if (c >= 'a' && c <= 'f') + c -= 'a' - 'A'; + assert ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')); + reversed[i] = c; + } + reversed[len] = '\0'; + last = &u->by_fingerprint; + for (uploader = u->by_fingerprint ; + uploader != NULL ; + uploader = *(last = &uploader->next)) { + if (uploader->len != len) + continue; + if (memcmp(uploader->reversed_fingerprint, reversed, len) != 0) + continue; + if (uploader->allow_subkeys != allow_subkeys) + continue; + free(reversed); + return uploader; + } + assert (*last == NULL); + uploader = zNEW(struct uploader); + if (FAILEDTOALLOC(uploader)) + return NULL; + *last = uploader; + uploader->reversed_fingerprint = reversed; + uploader->len = len; + uploader->allow_subkeys = allow_subkeys; + return uploader; +} + +static struct uploadergroup *addgroup(struct uploaders *u, const char *name, size_t len) { + struct uploadergroup *group, **last; + + last = &u->groups; + for (group = u->groups ; + group != NULL ; group = *(last = &group->next)) { + if (group->len != len) + continue; + if (memcmp(group->name, name, len) != 0) + continue; + return group; + } + assert (*last == NULL); + group = zNEW(struct uploadergroup); + if (FAILEDTOALLOC(group)) + return NULL; + group->name = strndup(name, len); + group->len = len; + if (FAILEDTOALLOC(group->name)) { + free(group); + return NULL; + } + *last = group; + return group; +} + +static inline const char *overkey(const char *p) { + while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') + || (*p >= 'A' && *p <= 'F')) { + p++; + } + return p; +} + +static retvalue parse_stringpart(/*@out@*/struct strlist *strings, const char **pp, const struct filebeingparsed *fbp, int column) { + const char *p = *pp; + retvalue r; + + strlist_init(strings); + do { + const char *startp, *endp; + char *n; + + while (*p != '\0' && xisspace(*p)) + p++; + if (*p != '\'') { + errorcol(fbp, column + (int)(p - *pp), +"starting \"'\" expected!"); + return RET_ERROR; + } + p++; + startp = p; + while (*p != '\0' && *p != '\'') + p++; + if (*p == '\0') { + errorcol(fbp, column + (int)(p - *pp), +"closing \"'\" expected!"); + return RET_ERROR; + } + assert (*p == '\''); + endp = p; + p++; + n = strndup(startp, endp - startp); + if (FAILEDTOALLOC(n)) + return RET_ERROR_OOM; + r = strlist_adduniq(strings, n); + if (RET_WAS_ERROR(r)) + return r; + while (*p != '\0' && xisspace(*p)) + p++; + column += (p - *pp); + *pp = p; + if (**pp == '|') { + p++; + } + } while (**pp == '|'); + *pp = p; + return RET_OK; +} + +static retvalue parse_architectures(/*@out@*/struct atomlist *atoms, const char **pp, const struct filebeingparsed *fbp, int column) { + const char *p = *pp; + retvalue r; + + atomlist_init(atoms); + do { + const char *startp, *endp; + atom_t atom; + + while (*p != '\0' && xisspace(*p)) + p++; + if (*p != '\'') { + errorcol(fbp, column + (int)(p - *pp), +"starting \"'\" expected!"); + return RET_ERROR; + } + p++; + startp = p; + while (*p != '\0' && *p != '\'' && *p != '*' && *p != '?') + p++; + if (*p == '*' || *p == '?') { + errorcol(fbp, column + (int)(p - *pp), +"Wildcards are not allowed in architectures!"); + return RET_ERROR; + } + if (*p == '\0') { + errorcol(fbp, column + (int)(p - *pp), +"closing \"'\" expected!"); + return RET_ERROR; + } + assert (*p == '\''); + endp = p; + p++; + atom = architecture_find_l(startp, endp - startp); + if (!atom_defined(atom)) { + errorcol(fbp, column + (int)(startp-*pp), +"Unknown architecture '%.*s'! (Did you mistype?)", + (int)(endp-startp), startp); + return RET_ERROR; + } + r = atomlist_add_uniq(atoms, atom); + if (RET_WAS_ERROR(r)) + return r; + while (*p != '\0' && xisspace(*p)) + p++; + column += (p - *pp); + *pp = p; + if (**pp == '|') { + p++; + } + } while (**pp == '|'); + *pp = p; + return RET_OK; +} + +static retvalue parse_condition(const struct filebeingparsed *fbp, int column, const char **pp, /*@out@*/struct upload_condition *condition) { + const char *p = *pp; + struct upload_condition *fallback, *last, *or_scope; + + setzero(struct upload_condition, condition); + + /* allocate a new fallback-node: + * (this one is used to make it easier to concatenate those decision + * trees, especially it keeps open the possibility to have deny + * decisions) */ + fallback = zNEW(struct upload_condition); + if (FAILEDTOALLOC(fallback)) + return RET_ERROR_OOM; + fallback->type = uc_ALWAYS; + assert(!fallback->accept_if_true); + + /* the queue with next has all nodes, so they can be freed + * (or otherwise modified) */ + condition->next = fallback; + + + last = condition; + or_scope = condition; + + while (true) { + if (strncmp(p, "not", 3) == 0 && + xisspace(p[3])) { + p += 3; + while (*p != '\0' && xisspace(*p)) + p++; + /* negate means false is good and true + * is bad: */ + last->accept_if_false = true; + last->accept_if_true = false; + last->next_if_false = NULL; + last->next_if_true = fallback; + } else { + last->accept_if_false = false; + last->accept_if_true = true; + last->next_if_false = fallback; + last->next_if_true = NULL; + } + if (p[0] == '*' && xisspace(p[1])) { + last->type = uc_ALWAYS; + p++; + } else if (strncmp(p, "architectures", 13) == 0 && + strchr(" \t'", p[13]) != NULL) { + retvalue r; + + last->type = uc_ARCHITECTURES; + last->needs = needs_all; + p += 13; + while (*p != '\0' && xisspace(*p)) + p++; + if (strncmp(p, "contain", 7) == 0 && + strchr(" \t'", p[7]) != NULL) { + last->needs = needs_any; + p += 7; + } + + r = parse_architectures(&last->atoms, &p, + fbp, column + (p-*pp)); + if (RET_WAS_ERROR(r)) { + uploadpermission_release(condition); + return r; + } + } else if (strncmp(p, "binaries", 8) == 0 && + strchr(" \t'", p[8]) != NULL) { + retvalue r; + + last->type = uc_BINARIES; + last->needs = needs_all; + p += 8; + while (*p != '\0' && xisspace(*p)) + p++; + if (strncmp(p, "contain", 7) == 0 && + strchr(" \t'", p[7]) != NULL) { + last->needs = needs_any; + p += 7; + } + + r = parse_stringpart(&last->strings, &p, + fbp, column + (p-*pp)); + if (RET_WAS_ERROR(r)) { + uploadpermission_release(condition); + return r; + } + } else if (strncmp(p, "byhand", 6) == 0 && + strchr(" \t'", p[6]) != NULL) { + retvalue r; + + last->type = uc_BYHAND; + last->needs = needs_existsall; + p += 8; + while (*p != '\0' && xisspace(*p)) + p++; + if (*p != '\'') { + strlist_init(&last->strings); + r = RET_OK; + } else + r = parse_stringpart(&last->strings, &p, + fbp, column + (p-*pp)); + if (RET_WAS_ERROR(r)) { + uploadpermission_release(condition); + return r; + } + } else if (strncmp(p, "sections", 8) == 0 && + strchr(" \t'", p[8]) != NULL) { + retvalue r; + + last->type = uc_SECTIONS; + last->needs = needs_all; + p += 8; + while (*p != '\0' && xisspace(*p)) + p++; + if (strncmp(p, "contain", 7) == 0 && + strchr(" \t'", p[7]) != NULL) { + last->needs = needs_any; + p += 7; + } + + r = parse_stringpart(&last->strings, &p, + fbp, column + (p-*pp)); + if (RET_WAS_ERROR(r)) { + uploadpermission_release(condition); + return r; + } + } else if (strncmp(p, "source", 6) == 0 && + strchr(" \t'", p[6]) != NULL) { + retvalue r; + + last->type = uc_SOURCENAME; + p += 6; + + r = parse_stringpart(&last->strings, &p, + fbp, column + (p-*pp)); + if (RET_WAS_ERROR(r)) { + uploadpermission_release(condition); + return r; + } + + } else if (strncmp(p, "distribution", 12) == 0 && + strchr(" \t'", p[12]) != NULL) { + retvalue r; + + last->type = uc_CODENAME; + p += 12; + + r = parse_stringpart(&last->strings, &p, + fbp, column + (p-*pp)); + if (RET_WAS_ERROR(r)) { + uploadpermission_release(condition); + return r; + } + + } else { + errorcol(fbp, column + (int)(p - *pp), +"condition expected after 'allow' keyword!"); + uploadpermission_release(condition); + return RET_ERROR; + } + while (*p != '\0' && xisspace(*p)) + p++; + if (strncmp(p, "and", 3) == 0 && xisspace(p[3])) { + struct upload_condition *n, *c; + + p += 3; + + n = zNEW(struct upload_condition); + if (FAILEDTOALLOC(n)) { + uploadpermission_release(condition); + return RET_ERROR_OOM; + } + /* everything that yet made it succeed makes it need + * to check this condition: */ + for (c = condition ; c != NULL ; c = c->next) { + if (c->accept_if_true) { + c->next_if_true = n; + c->accept_if_true = false; + } + if (c->accept_if_false) { + c->next_if_false = n; + c->accept_if_false = false; + } + } + /* or will only bind to this one */ + or_scope = n; + + /* add it to queue: */ + assert (last->next == fallback); + n->next = fallback; + last->next = n; + last = n; + } else if (strncmp(p, "or", 2) == 0 && xisspace(p[2])) { + struct upload_condition *n, *c; + + p += 2; + + n = zNEW(struct upload_condition); + if (FAILEDTOALLOC(n)) { + uploadpermission_release(condition); + return RET_ERROR_OOM; + } + /* everything in current scope that made it fail + * now makes it check this: (currently that will + * only be true at most for c == last, but with + * parentheses this all will be needed) */ + for (c = or_scope ; c != NULL ; c = c->next) { + if (c->next_if_true == fallback) + c->next_if_true = n; + if (c->next_if_false == fallback) + c->next_if_false = n; + } + /* add it to queue: */ + assert (last->next == fallback); + n->next = fallback; + last->next = n; + last = n; + } else if (strncmp(p, "by", 2) == 0 && xisspace(p[2])) { + p += 2; + break; + } else { + errorcol(fbp, column + (int)(p - *pp), +"'by','and' or 'or' keyword expected!"); + uploadpermission_release(condition); + setzero(struct upload_condition, condition); + return RET_ERROR; + } + while (*p != '\0' && xisspace(*p)) + p++; + } + *pp = p; + return RET_OK; +} + +static void condition_add(struct upload_condition *permissions, struct upload_condition *c) { + if (permissions->next == NULL) { + /* first condition, as no fallback yet allocated */ + *permissions = *c; + setzero(struct upload_condition, c); + } else { + struct upload_condition *last; + + last = permissions->next; + assert (last != NULL); + while (last->next != NULL) + last = last->next; + + /* the very last is always the fallback-node to which all + * other conditions fall back if they have no decision */ + assert(last->type == uc_ALWAYS); + assert(!last->accept_if_true); + + *last = *c; + setzero(struct upload_condition, c); + } +} + +static retvalue find_group(struct uploadergroup **g, struct uploaders *u, const char **pp, const struct filebeingparsed *fbp, const char *buffer) { + const char *p, *q; + struct uploadergroup *group; + + p = *pp; + q = p; + while ((*q >= 'a' && *q <= 'z') || (*q >= 'A' && *q <= 'Z') || + (*q >= '0' && *q <= '9') || *q == '-' + || *q == '_' || *q == '.') + q++; + if (*p == '\0' || (q-p == 3 && memcmp(p, "add", 3) == 0) + || (q-p == 5 && memcmp(p, "empty", 5) == 0) + || (q-p == 6 && memcmp(p, "unused", 6) == 0) + || (q-p == 8 && memcmp(p, "contains", 8) == 0)) { + errorcol(fbp, (int)(1 + p - buffer), +"group name expected!"); + return RET_ERROR; + } + if (*q != '\0' && *q != ' ' && *q != '\t') { + errorcol(fbp, (int)(1 +p -buffer), +"invalid group name!"); + return RET_ERROR; + } + *pp = q; + group = addgroup(u, p, q-p); + if (FAILEDTOALLOC(group)) + return RET_ERROR_OOM; + *g = group; + return RET_OK; +} + +static retvalue find_uploader(struct uploader **u_p, struct uploaders *u, const char *p, const struct filebeingparsed *fbp, const char *buffer) { + struct uploader *uploader; + bool allow_subkeys = false; + const char *q, *qq; + + if (p[0] == '0' && p[1] == 'x') + p += 2; + q = overkey(p); + if (*p == '\0' || (*q !='\0' && !xisspace(*q) && *q != '+') || q==p) { + errorcol(fbp, (int)(1 + q - buffer), +"key id or fingerprint expected!"); + return RET_ERROR; + } + if (q - p > 16) { + if (!IGNORABLE(longkeyid)) + errorcol(fbp, (int)(1 + p - buffer), +"key id most likely too long for gpgme to understand\n" +"(at most 16 hex digits should be safe. Use --ignore=longkeyid to ignore)"); + } + qq = q; + while (xisspace(*qq)) + qq++; + if (*qq == '+') { + qq++; + allow_subkeys = true; + } + while (xisspace(*qq)) + qq++; + if (*qq != '\0') { + errorcol(fbp, (int)(1 +qq - buffer), +"unexpected data after 'key <fingerprint>' statement!"); + if (*q == ' ') + fprintf(stderr, +" Hint: no spaces allowed in fingerprint specification.\n"); + return RET_ERROR; + } + uploader = addfingerprint(u, p, q-p, allow_subkeys); + if (FAILEDTOALLOC(uploader)) + return RET_ERROR_OOM; + *u_p = uploader; + return RET_OK; +} + +static retvalue include_group(struct uploadergroup *group, const struct uploadergroup ***memberof_p, const struct filebeingparsed *fbp) { + size_t n; + const struct uploadergroup **memberof = *memberof_p; + + n = 0; + if (memberof != NULL) { + while (memberof[n] != NULL) { + if (memberof[n] == group) { + errorline(fbp, +"member added to group %s a second time!", + group->name); + return RET_ERROR; + } + n++; + } + } + if (n == 0 || (n & 15) == 15) { + /* let's hope no static checker is confused here ;-> */ + memberof = realloc(memberof, + ((n+17)&~15) * sizeof(struct uploadergroup*)); + if (FAILEDTOALLOC(memberof)) + return RET_ERROR_OOM; + *memberof_p = memberof; + } + memberof[n] = group; + memberof[n+1] = NULL; + if (unset_pos(group->firstmemberat)) + set_position(group->firstmemberat, fbp);; + if (!unset_pos(group->emptyat)) { + errorline(fbp, +"cannot add members to group '%s' marked empty!", group->name); + errorpos(group->emptyat, +"here it was marked as empty"); + return RET_ERROR; + } + return RET_OK; +} + +static bool is_included_in(const struct uploadergroup *needle, const struct uploadergroup *chair) { + const struct uploadergroup **g; + + if (needle->memberof == NULL) + return false; + for (g = needle->memberof ; *g != NULL ; g++) { + if (*g == chair) + return true; + if (is_included_in(*g, chair)) + return true; + } + return false; +} + +static inline bool trim_line(const struct filebeingparsed *fbp, char *buffer) { + size_t l = strlen(buffer); + if (l == 0 || buffer[l-1] != '\n') { + if (l >= 1024) + errorcol(fbp, 1024, "Overlong line!"); + else + errorcol(fbp, (int)l, "Unterminated line!"); + return false; + } + do { + buffer[--l] = '\0'; + } while (l > 0 && xisspace(buffer[l-1])); + return true; +} + +static inline retvalue parseuploaderline(char *buffer, const struct filebeingparsed *fbp, struct uploaders *u) { + retvalue r; + const char *p, *q; + struct upload_condition condition; + + p = buffer; + while (*p != '\0' && xisspace(*p)) + p++; + if (*p == '\0' || *p == '#') + return RET_NOTHING; + + if (strncmp(p, "group", 5) == 0 && (*p == '\0' || xisspace(p[5]))) { + struct uploadergroup *group; + + p += 5; + while (*p != '\0' && xisspace(*p)) + p++; + r = find_group(&group, u, &p, fbp, buffer); + if (RET_WAS_ERROR(r)) + return r; + while (*p != '\0' && xisspace(*p)) + p++; + if (strncmp(p, "add", 3) == 0) { + struct uploader *uploader; + + p += 3; + while (*p != '\0' && xisspace(*p)) + p++; + r = find_uploader(&uploader, u, p, fbp, buffer); + if (RET_WAS_ERROR(r)) + return r; + r = include_group(group, &uploader->memberof, fbp); + if (RET_WAS_ERROR(r)) + return r; + return RET_OK; + } else if (strncmp(p, "contains", 8) == 0) { + struct uploadergroup *member; + + p += 8; + while (*p != '\0' && xisspace(*p)) + p++; + q = p; + r = find_group(&member, u, &q, fbp, buffer); + if (RET_WAS_ERROR(r)) + return r; + if (group == member) { + errorline(fbp, +"cannot add group '%s' to itself!", member->name); + return RET_ERROR; + } + if (is_included_in(group, member)) { + /* perhaps offer a winning coupon for the first + * one triggering this? */ + errorline(fbp, +"cannot add group '%s' to group '%s' as the later is already member of the former!", + member->name, group->name); + return RET_ERROR; + } + r = include_group(group, &member->memberof, fbp); + if (RET_WAS_ERROR(r)) + return r; + if (unset_pos(member->firstusedat)) + set_position(member->firstusedat, fbp);; + if (!unset_pos(member->unusedat)) { + errorline(fbp, +"cannot use group '%s' marked as unused!", member->name); + errorpos(member->unusedat, +"here it got marked as unused."); + return RET_ERROR; + } + } else if (strncmp(p, "empty", 5) == 0) { + q = p + 5; + if (!unset_pos(group->emptyat)) { + errorline(fbp, +"group '%s' marked as empty again", group->name); + errorpos(group->emptyat, +"here it was marked empty the first time"); + } + if (!unset_pos(group->firstmemberat)) { + errorline(fbp, +"group '%s' cannot be marked empty as it already has members!", + group->name); + errorpos(group->firstmemberat, +"here a member was added the first time"); + return RET_ERROR; + } + set_position(group->emptyat, fbp);; + } else if (strncmp(p, "unused", 6) == 0) { + q = p + 6; + if (!unset_pos(group->unusedat)) { + errorline(fbp, +"group '%s' marked as unused again!", group->name); + errorpos(group->unusedat, +"here it was already marked unused"); + } + if (!unset_pos(group->firstusedat)) { + errorline(fbp, +"group '%s' cannot be marked unused as it was already used!", group->name); + errorpos(group->firstusedat, +"here it was used the first time"); + return RET_ERROR; + } + set_position(group->unusedat, fbp);; + } else { + errorcol(fbp, (int)(1 + p - buffer), +"missing 'add', 'contains', 'unused' or 'empty' keyword."); + return RET_ERROR; + } + while (*q != '\0' && xisspace(*q)) + q++; + if (*q != '\0') { + errorcol(fbp, (int)(1 + p - buffer), +"unexpected data at end of group statement!"); + return RET_ERROR; + } + return RET_OK; + } + if (strncmp(p, "allow", 5) != 0 || !xisspace(p[5])) { + errorcol(fbp, (int)(1 +p - buffer), +"'allow' or 'group' keyword expected!" +" (no other statement has yet been implemented)"); + return RET_ERROR; + } + p+=5; + while (*p != '\0' && xisspace(*p)) + p++; + r = parse_condition(fbp, (1+p-buffer), &p, &condition); + if (RET_WAS_ERROR(r)) + return r; + while (*p != '\0' && xisspace(*p)) + p++; + if (strncmp(p, "key", 3) == 0 && (p[3] == '\0' || xisspace(p[3]))) { + struct uploader *uploader; + + p += 3; + while (*p != '\0' && xisspace(*p)) + p++; + r = find_uploader(&uploader, u, p, fbp, buffer); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + uploadpermission_release(&condition); + return r; + } + condition_add(&uploader->permissions, &condition); + } else if (strncmp(p, "group", 5) == 0 + && (p[5] == '\0' || xisspace(p[5]))) { + struct uploadergroup *group; + + p += 5; + while (*p != '\0' && xisspace(*p)) + p++; + r = find_group(&group, u, &p, fbp, buffer); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + uploadpermission_release(&condition); + return r; + } + assert (group != NULL); + while (*p != '\0' && xisspace(*p)) + p++; + if (*p != '\0') { + errorcol(fbp, (int)(1 + p - buffer), +"unexpected data at end of group statement!"); + uploadpermission_release(&condition); + return RET_ERROR; + } + if (unset_pos(group->firstusedat)) + set_position(group->firstusedat, fbp);; + if (!unset_pos(group->unusedat)) { + errorline(fbp, +"cannot use group '%s' marked as unused!", group->name); + errorpos(group->unusedat, +"here it was marked as unused."); + uploadpermission_release(&condition); + return RET_ERROR; + } + condition_add(&group->permissions, &condition); + } else if (strncmp(p, "unsigned", 8) == 0 + && (p[8]=='\0' || xisspace(p[8]))) { + p+=8; + if (*p != '\0') { + errorcol(fbp, (int)(1 + p - buffer), +"unexpected data after 'unsigned' statement!"); + uploadpermission_release(&condition); + return RET_ERROR; + } + condition_add(&u->unsignedpermissions, &condition); + } else if (strncmp(p, "any", 3) == 0 && xisspace(p[3])) { + p+=3; + while (*p != '\0' && xisspace(*p)) + p++; + if (strncmp(p, "key", 3) != 0 + || (p[3]!='\0' && !xisspace(p[3]))) { + errorcol(fbp, (int)(1 + p - buffer), +"'key' keyword expected after 'any' keyword!"); + uploadpermission_release(&condition); + return RET_ERROR; + } + p += 3; + if (*p != '\0') { + errorcol(fbp, (int)(1 + p - buffer), +"unexpected data after 'any key' statement!"); + uploadpermission_release(&condition); + return RET_ERROR; + } + condition_add(&u->anyvalidkeypermissions, &condition); + } else if (strncmp(p, "anybody", 7) == 0 + && (p[7] == '\0' || xisspace(p[7]))) { + p+=7; + while (*p != '\0' && xisspace(*p)) + p++; + if (*p != '\0') { + errorcol(fbp, (int)(1 + p - buffer), +"unexpected data after 'anybody' statement!"); + uploadpermission_release(&condition); + return RET_ERROR; + } + condition_add(&u->anybodypermissions, &condition); + } else { + errorcol(fbp, (int)(1 + p - buffer), +"'key', 'unsigned', 'anybody' or 'any key' expected!"); + uploadpermission_release(&condition); + return RET_ERROR; + } + return RET_OK; +} + +static retvalue openfiletobeparsed(struct filebeingparsed *includedby, const char *filename, struct filebeingparsed **fbp_p, struct filebeingparsed **root_p) { + struct filebeingparsed *fbp; + + if (includedby != NULL && includedby->depth > 100) { + errorcol(includedby, 0, +"Too deeply nested include directives (> 100). Built some recursion?"); + return RET_ERROR; + } + + fbp = calloc(1, sizeof(struct filebeingparsed)); + if (FAILEDTOALLOC(fbp)) + return RET_ERROR_OOM; + + fbp->filename = configfile_expandname(filename, NULL); + if (FAILEDTOALLOC(fbp->filename)) { + free(fbp); + return RET_ERROR_OOM; + } + fbp->f = fopen(fbp->filename, "r"); + if (fbp->f == NULL) { + int e = errno; + fprintf(stderr, "Error opening '%s': %s\n", + fbp->filename, strerror(e)); + print_include_trace(includedby); + free(fbp->filename); + free(fbp); + return RET_ERRNO(e); + } + fbp->depth = (includedby != NULL)?(includedby->depth+1):0; + fbp->includedby = includedby; + *fbp_p = fbp; + fbp->next = *root_p; + *root_p = fbp; + return RET_OK; +} + +static void filebeingparsed_free(struct filebeingparsed *fbp) { + while (fbp != NULL) { + struct filebeingparsed *n = fbp->next; + if (fbp->f != NULL) + (void)fclose(fbp->f); + free(fbp->filename); + free(fbp); + fbp = n; + } +} + +static inline retvalue close_file(struct filebeingparsed **p) { + int i; + struct filebeingparsed *fbp = *p; + assert (p != NULL); + + *p = fbp->includedby; + i = fclose(fbp->f); + fbp->f = NULL; + if (i != 0) { + int e = errno; + fprintf(stderr, "Error reading '%s': %s\n", + fbp->filename, strerror(e)); + print_include_trace(fbp->includedby); + return RET_ERRNO(e); + } else + return RET_OK; +} + +static inline retvalue include_file(struct filebeingparsed **fbp_p, struct filebeingparsed **root_p, const char *buffer) { + const char *filename = buffer; + + while (*filename != '\0' && xisspace(*filename)) + filename++; + if (*filename == '\0') { + errorcol(*fbp_p, 1+(int)(filename - buffer), +"Missing filename after include directive!"); + return RET_ERROR; + } + return openfiletobeparsed(*fbp_p, filename, fbp_p, root_p); +} + +static retvalue uploaders_load(/*@out@*/struct uploaders **list, const char *fname) { + char buffer[1025]; + struct uploaders *u; + struct uploadergroup *g; + retvalue r; + struct filebeingparsed *fbp = NULL; + struct filebeingparsed *filesroot = NULL; + + r = openfiletobeparsed(NULL, fname, &fbp, &filesroot); + if (RET_WAS_ERROR(r)) + return r; + + u = zNEW(struct uploaders); + if (FAILEDTOALLOC(u)) { + filebeingparsed_free(filesroot); + return RET_ERROR_OOM; + } + /* reject by default */ + u->unsignedpermissions.type = uc_ALWAYS; + u->anyvalidkeypermissions.type = uc_ALWAYS; + u->anybodypermissions.type = uc_ALWAYS; + + while (fbp != NULL) { + while (fgets(buffer, 1024, fbp->f) != NULL) { + fbp->lineno++; + if (!trim_line(fbp, buffer)) { + filebeingparsed_free(filesroot); + uploaders_free(u); + return RET_ERROR; + } + if (strncmp(buffer, "include", 7) == 0) + r = include_file(&fbp, &filesroot, buffer + 7); + else + r = parseuploaderline(buffer, fbp, u); + if (RET_WAS_ERROR(r)) { + filebeingparsed_free(filesroot); + uploaders_free(u); + return r; + } + } + r = close_file(&fbp); + if (RET_WAS_ERROR(r)) { + filebeingparsed_free(filesroot); + uploaders_free(u); + return r; + } + } + for (g = u->groups ; g != NULL ; g = g->next) { + if ((unset_pos(g->firstmemberat) && unset_pos(g->emptyat)) && + !unset_pos(g->firstusedat)) + errorpos(g->firstusedat, +"Warning: group '%s' gets used but never gets any members", + g->name); + if ((unset_pos(g->firstusedat) && unset_pos(g->unusedat)) && + !unset_pos(g->firstmemberat)) + // TODO: avoid this if the group is from a include? + errorpos(g->firstmemberat, +"Warning: group '%s' gets members but is not used in any rule", + g->name); + } + assert (fbp == NULL); + /* only free file information once filenames are no longer needed: */ + filebeingparsed_free(filesroot); + *list = u; + return RET_OK; +} + +retvalue uploaders_get(/*@out@*/struct uploaders **list, const char *filename) { + retvalue r; + struct uploaders *u; + size_t len; + + assert (filename != NULL); + + len = strlen(filename); + u = uploaderslists; + while (u != NULL && (u->filename_len != len || + memcmp(u->filename, filename, len) != 0)) + u = u->next; + if (u == NULL) { + r = uploaders_load(&u, filename); + if (!RET_IS_OK(r)) + return r; + assert (u != NULL); + u->filename = strdup(filename); + if (FAILEDTOALLOC(u->filename)) { + uploaders_free(u); + return RET_ERROR_OOM; + } + u->filename_len = len; + u->next = uploaderslists; + u->reference_count = 1; + uploaderslists = u; + } else + u->reference_count++; + *list = u; + return RET_OK; +} |