commit - 0585ddfda1b9020371ee6b5bb3ac62739806d031
commit + 51b4fd27b0649d6573dbb0fc138470f29936bdce
blob - 4f58040aa6ba6e5d864b262e1d18cb04539cea04
blob + 25f46dc77a183eb2a710cbc22cf27c54f4e73349
--- gitwrapper/gitwrapper.c
+++ gitwrapper/gitwrapper.c
#endif
/* only needed to satisfy the linker */
-const char *
+struct gotd_secret *
gotd_secrets_get(struct gotd_secrets *secrets, enum gotd_secret_type t,
- const char *key)
+ const char *label)
{
return NULL;
}
blob - 6b87a0e60a950f2a87d40202550a866c319f4d9d
blob + 78e45385e723721f6e996d5f99378d14263aee4f
--- gotd/gotd-secrets.conf.5
+++ gotd/gotd-secrets.conf.5
Comments can be put at the start of the line using a hash mark
.Pq Sq # ,
and extend to the end of it.
-Blank lines are also ignored.
+Empty lines are also ignored.
.Pp
-The entries have the following syntax:
+Each entry is made by blanks-separated words.
+Arguments containing whitespaces should be surrounded by single or double
+quotes.
.Pp
-.Dl type key value
-.Pp
-with spaces or tabs to separate the fields.
-No quoting is supported, so a space or a tab can't appear as part of
-any field.
-.Pp
-The type is one of:
+The supported entries are:
.Bl -tag -width Ds
-.It Ic auth
+.It Ic auth Ar label Ic user Ar user Ic password Ar password
The entry is for HTTP Basic Authentication.
-.Ar key
-is the username and
-.ar value
-the password.
-The username is also used to identify this secret.
-.It Ic hmac
+.It Ic hmac Ar label Ar secret
The entry is for signing the notification HTTP payload with HMAC.
-The
-.Ar key
-is a label to identify this secret and
-.Ar value
-is the HMAC secret.
-.Pp
-Suitable secrets can be generated with
+A suitable
+.Ar secret
+can be generated with
.Xr openssl 1
as follows:
.Pp
.Dl $ openssl rand -base64 32
.El
.Pp
-The key must be unique between entries with the same type.
+The
+.Ar label
+must be unique between entries with the same type
+.Pq i.e. Ic auth No or Ic hmac .
.Sh FILES
.Bl -tag -width Ds -compact
.It Pa /etc/gotd-secrets.conf
HTTP authentication and the second for HMAC signing.
.Bd -literal -offset indent
# /etc/gotd-secrets.conf
-auth flan super-strong-password!
+auth mochi user "flan" password "super-strong-password!"
hmac hacker q0tcl8QhjYs7U75MW/2rwB30CpdbAhONkfLGxFHm/+8=
.Ed
.Pp
as:
.Bd -literal -offset indent
# /etc/gotd.conf
-repository "openbsd/ports" {
- path "/var/git/ports.git"
- permit rw :porters
- permit ro anonymous
+repository "openbsd/src" {
+ path "/var/git/src.git"
+ permit rw :hackers
notify {
- url https://flan.com/notify/ auth flan
- url https://hacker.com/notify/ hmac hacker
+ url https://flan.com/ci/ auth mochi hmac hacker
}
}
.El
blob - e48607fc5e589c30fbd8c878b44385f887c41998
blob + 79e8f6bd205c1b0270dd6d8a73049cb61bc8e7f8
--- gotd/gotd.c
+++ gotd/gotd.c
for (i = 0; i < n; ++i) {
struct iovec iov[5];
- int keylen, vallen;
s = &gotd.secrets->secrets[i];
- keylen = strlen(s->key) + 1;
- vallen = strlen(s->val) + 1;
-
iov[0].iov_base = &s->type;
iov[0].iov_len = sizeof(s->type);
- iov[1].iov_base = &keylen;
- iov[1].iov_len = sizeof(keylen);
+ iov[1].iov_base = s->label;
+ iov[1].iov_len = strlen(s->label) + 1;
- iov[2].iov_base = &vallen;
- iov[2].iov_len = sizeof(vallen);
+ iov[2].iov_base = s->user;
+ iov[2].iov_len = s->user ? strlen(s->user) + 1 : 0 ;
- iov[3].iov_base = s->key;
- iov[3].iov_len = keylen;
+ iov[3].iov_base = s->pass;
+ iov[3].iov_len = s->pass ? strlen(s->pass) + 1 : 0 ;
- iov[4].iov_base = s->val;
- iov[4].iov_len = vallen;
+ iov[4].iov_base = s->hmac;
+ iov[4].iov_len = s->hmac ? strlen(s->hmac) + 1 : 0 ;
if (imsg_composev(imsgbuf, GOTD_IMSG_SECRET,
0, 0, -1, iov, 5) == -1)
blob - 6c3bc476afd311f7dd08f2597ac7faee7cf44a83
blob + d1d273a304568c2b659dc92d117c0efde5f1718f
--- gotd/notify.c
+++ gotd/notify.c
notify_http(struct gotd_notification_target *target, const char *repo,
const char *username, int fd)
{
+ struct gotd_secret *secret;
const char *http_user = NULL, *http_pass = NULL, *hmac = NULL;
const char *argv[12];
int argc = 0;
argv[argc] = NULL;
if (target->conf.http.auth) {
- http_user = target->conf.http.auth;
- http_pass = gotd_secrets_get(&secrets, GOTD_SECRET_AUTH,
- http_user);
+ secret = gotd_secrets_get(&secrets, GOTD_SECRET_AUTH,
+ target->conf.http.auth);
+ http_user = secret->user;
+ http_pass = secret->pass;
}
if (target->conf.http.hmac) {
- hmac = gotd_secrets_get(&secrets, GOTD_SECRET_HMAC,
+ secret = gotd_secrets_get(&secrets, GOTD_SECRET_HMAC,
target->conf.http.hmac);
+ hmac = secret->hmac;
}
run_notification_helper(GOTD_PATH_PROG_NOTIFY_HTTP, argv, fd,
return NULL;
}
+
+static const struct got_error *
+notify_ibuf_get_str(char **ret, struct ibuf *ibuf)
+{
+ const char *str, *end;
+ size_t len;
+
+ *ret = NULL;
+ str = ibuf_data(ibuf);
+ len = ibuf_size(ibuf);
+
+ end = memchr(str, '\0', len);
+ if (end == NULL)
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ *ret = strdup(str);
+ if (*ret == NULL)
+ return got_error_from_errno("strdup");
+
+ if (ibuf_skip(ibuf, end - str + 1) == -1) {
+ free(*ret);
+ *ret = NULL;
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ }
+
+ return NULL;
+}
+
static void
notify_dispatch(int fd, short event, void *arg)
{
struct imsg imsg;
struct ibuf ibuf;
struct gotd_secret *s;
- int keylen, vallen;
- char *key, *val;
if (event & EV_READ) {
if ((n = imsg_read(imsgbuf)) == -1 && errno != EAGAIN)
s = &secrets.secrets[secrets.len++];
if (imsg_get_ibuf(&imsg, &ibuf) == -1)
fatal("imsg_get_ibuf");
- if (ibuf_get(&ibuf, &s->type, sizeof(s->type)) == -1 ||
- ibuf_get(&ibuf, &keylen, sizeof(keylen)) == -1 ||
- ibuf_get(&ibuf, &vallen, sizeof(vallen)) == -1 ||
- keylen <= 0 || vallen <= 0 ||
- ibuf_size(&ibuf) != (keylen + vallen) ||
- (key = ibuf_data(&ibuf)) == NULL ||
- (val = ibuf_seek(&ibuf, keylen, vallen)) == NULL ||
- key[keylen - 1] != '\0' || val[vallen - 1] != '\0')
+ if (ibuf_get(&ibuf, &s->type, sizeof(s->type)) == -1)
fatalx("corrupted GOTD_IMSG_SECRET");
- s->key = strdup(key);
- s->val = strdup(val);
- if (s->key == NULL || s->val == NULL)
- fatal("strdup");
+ err = notify_ibuf_get_str(&s->label, &ibuf);
+ if (err)
+ break;
+ if (s->type == GOTD_SECRET_AUTH) {
+ err = notify_ibuf_get_str(&s->user, &ibuf);
+ if (err)
+ break;
+ err = notify_ibuf_get_str(&s->pass, &ibuf);
+ if (err)
+ break;
+ } else {
+ err = notify_ibuf_get_str(&s->hmac, &ibuf);
+ if (err)
+ break;
+ }
+ if (ibuf_size(&ibuf) != 0)
+ fatalx("unexpected extra data in "
+ "GOTD_IMSG_SECRET");
break;
default:
log_debug("unexpected imsg %d", imsg.hdr.type);
blob - 3645645467b19a36a03d4f527df5743da2de2d17
blob + 3407009d5a8a939edea8ac031e24af4b599e567c
--- gotd/secrets.c
+++ gotd/secrets.c
#include "secrets.h"
static const struct got_error *
-push(struct gotd_secrets *s, const char *path, int lineno,
- const char *type, const char *key, const char *val)
+push(struct gotd_secrets *s, enum gotd_secret_type type, const char *label,
+ const char *user, const char *pass, const char *hmac)
{
size_t newcap, i;
void *t;
}
i = s->len;
- if (!strcmp(type, "auth"))
- s->secrets[i].type = GOTD_SECRET_AUTH;
- else if (!strcmp(type, "hmac"))
- s->secrets[i].type = GOTD_SECRET_HMAC;
+ memset(&s->secrets[i], 0, sizeof(s->secrets[i]));
+ s->secrets[i].type = type;
+ s->secrets[i].label = strdup(label);
+ if (s->secrets[i].label == NULL)
+ return got_error_from_errno("strdup");
+
+ if (type == GOTD_SECRET_AUTH) {
+ s->secrets[i].user = strdup(user);
+ if (s->secrets[i].user == NULL)
+ return got_error_from_errno("strdup");
+ s->secrets[i].pass = strdup(pass);
+ if (s->secrets[i].pass == NULL)
+ return got_error_from_errno("strdup");
+ } else {
+ s->secrets[i].hmac = strdup(hmac);
+ if (s->secrets[i].hmac == NULL)
+ return got_error_from_errno("strdup");
+ }
+
+ s->len++;
+ return NULL;
+}
+
+static char *
+read_word(char **word, const char *path, int lineno, char *s)
+{
+ char *p, quote = 0;
+ int escape = 0;
+
+ s += strspn(s, " \t");
+ if (*s == '\0') {
+ log_warnx("%s:%d syntax error", path, lineno);
+ return NULL;
+ }
+ *word = s;
+
+ p = s;
+ while (*s) {
+ if (escape) {
+ escape = 0;
+ *p++ = *s++;
+ continue;
+ }
+
+ if (*s == '\\') {
+ escape = 1;
+ s++;
+ continue;
+ }
+
+ if (*s == quote) {
+ quote = 0;
+ s++;
+ continue;
+ }
+
+ if (*s == '\'' || *s == '\"') {
+ quote = *s;
+ s++;
+ continue;
+ }
+
+ if (!quote && (*s == ' ' || *s == '\t')) {
+ *p = '\0';
+ return s + 1;
+ }
+
+ *p++ = *s++;
+ }
+
+ if (quote) {
+ log_warnx("%s:%d no closing quote", path, lineno);
+ return NULL;
+ }
+
+ if (escape) {
+ log_warnx("%s:%d unterminated escape at end of line",
+ path, lineno);
+ return NULL;
+ }
+
+ *p = '\0';
+ return s;
+}
+
+static char *
+read_keyword(char **kw, const char *path, int lineno, char *s)
+{
+ s += strspn(s, " \t");
+ if (*s == '\0') {
+ log_warnx("%s:%d syntax error", path, lineno);
+ return NULL;
+ }
+ *kw = s;
+
+ s += strcspn(s, " \t");
+ if (*s != '\0')
+ *s++ = '\0';
+ return s;
+}
+
+static const struct got_error *
+parse_line(struct gotd_secrets *secrets, const char *path, int lineno,
+ char *line)
+{
+ char *kw, *label;
+ char *user = NULL, *pass = NULL, *hmac = NULL;
+ enum gotd_secret_type type;
+
+ line = read_keyword(&kw, path, lineno, line);
+ if (line == NULL)
+ return got_error(GOT_ERR_PARSE_CONFIG);
+
+ if (!strcmp(kw, "auth"))
+ type = GOTD_SECRET_AUTH;
+ else if (!strcmp(kw, "hmac"))
+ type = GOTD_SECRET_HMAC;
else {
- log_warnx("%s:%d invalid type %s", path, lineno, type);
+ log_warnx("%s:%d syntax error", path, lineno);
return got_error(GOT_ERR_PARSE_CONFIG);
}
- if (gotd_secrets_get(s, s->secrets[i].type, key) != NULL) {
- log_warnx("%s:%d duplicate %s entry %s", path, lineno,
- type, key);
+ line = read_word(&label, path, lineno, line);
+ if (line == NULL)
return got_error(GOT_ERR_PARSE_CONFIG);
+
+ if (type == GOTD_SECRET_AUTH) {
+ line = read_keyword(&kw, path, lineno, line);
+ if (line == NULL)
+ return got_error(GOT_ERR_PARSE_CONFIG);
+ if (strcmp(kw, "user") != 0) {
+ log_warnx("%s:%d syntax error", path, lineno);
+ return got_error(GOT_ERR_PARSE_CONFIG);
+ }
+
+ line = read_word(&user, path, lineno, line);
+ if (line == NULL)
+ return got_error(GOT_ERR_PARSE_CONFIG);
+
+ line = read_keyword(&kw, path, lineno, line);
+ if (line == NULL)
+ return got_error(GOT_ERR_PARSE_CONFIG);
+ if (strcmp(kw, "password") != 0) {
+ log_warnx("%s:%d syntax error", path, lineno);
+ return got_error(GOT_ERR_PARSE_CONFIG);
+ }
+
+ line = read_word(&pass, path, lineno, line);
+ if (line == NULL)
+ return got_error(GOT_ERR_PARSE_CONFIG);
+ } else {
+ line = read_word(&hmac, path, lineno, line);
+ if (line == NULL)
+ return got_error(GOT_ERR_PARSE_CONFIG);
}
- s->secrets[i].key = strdup(key);
- if (s->secrets[i].key == NULL)
- return got_error_from_errno("strdup");
- s->secrets[i].val = strdup(val);
- if (s->secrets[i].val == NULL)
- return got_error_from_errno("strdup");
+ line += strspn(line, " \t");
+ if (*line != '\0') {
+ log_warnx("%s:%d syntax error", path, lineno);
+ return got_error(GOT_ERR_PARSE_CONFIG);
+ }
- s->len++;
- return NULL;
+ if (gotd_secrets_get(secrets, type, label) != NULL) {
+ log_warnx("%s:%d duplicate %s entry %s", path, lineno,
+ type == GOTD_SECRET_AUTH ? "auth" : "hmac", label);
+ return got_error(GOT_ERR_PARSE_CONFIG);
+ }
+
+ return push(secrets, type, label, user, pass, hmac);
}
const struct got_error *
char *line = NULL;
size_t linesize = 0;
ssize_t linelen;
- char *type, *key, *val, *t;
+ char *t;
struct gotd_secrets *secrets;
*s = NULL;
if (line[linelen - 1] == '\n')
line[--linelen] = '\0';
- if (*line == '\0' || *line == '#')
+ for (t = line; *t == ' ' || *t == '\t'; ++t)
+ /* nop */ ;
+
+ if (*t == '\0' || *t == '#')
continue;
- type = line;
-
- key = type + strcspn(type, " \t");
- *key++ = '\0';
- key += strspn(key, " \t");
-
- val = key + strcspn(key, " \t");
- *val++ = '\0';
- val += strspn(val, " \t");
-
- t = val + strcspn(val, " \t");
- if (*t != '\0') {
- log_warnx("%s:%d malformed entry\n", path, lineno);
- err = got_error(GOT_ERR_PARSE_CONFIG);
- break;
- }
-
- err = push(secrets, path, lineno, type, key, val);
+ err = parse_line(secrets, path, lineno, t);
if (err)
break;
}
return err;
}
-const char *
+struct gotd_secret *
gotd_secrets_get(struct gotd_secrets *s, enum gotd_secret_type type,
- const char *key)
+ const char *label)
{
size_t i;
for (i = 0; i < s->len; ++i) {
if (s->secrets[i].type != type)
continue;
- if (strcmp(s->secrets[i].key, key) != 0)
+ if (strcmp(s->secrets[i].label, label) != 0)
continue;
- return s->secrets[i].val;
+ return &s->secrets[i];
}
return NULL;
return;
for (i = 0; i < s->len; ++i) {
- free(s->secrets[i].key);
- free(s->secrets[i].val);
+ free(s->secrets[i].label);
+ free(s->secrets[i].user);
+ free(s->secrets[i].pass);
+ free(s->secrets[i].hmac);
}
free(s);
blob - 5fd139e32a18178b1d1232c954b80219a532d5ce
blob + c7aae88890d730e3497720296c513e7c2e639c1f
--- gotd/secrets.h
+++ gotd/secrets.h
struct gotd_secret {
enum gotd_secret_type type;
- char *key; /* label or username */
- char *val; /* hmac secret or password */
+ char *label;
+ char *user;
+ char *pass;
+ char *hmac;
};
struct gotd_secrets {
const struct got_error *gotd_secrets_parse(const char *, FILE *,
struct gotd_secrets **);
-const char *gotd_secrets_get(struct gotd_secrets *, enum gotd_secret_type,
- const char *);
+struct gotd_secret *gotd_secrets_get(struct gotd_secrets *,
+ enum gotd_secret_type, const char *);
void gotd_secrets_free(struct gotd_secrets *);
blob - c3b16b4b439df9cf2fee1a80ae0caffffa7ab0de
blob + 667368ea6d53f44cbd7bd376394d98cf5d3c8ad0
--- regress/gotd/Makefile
+++ regress/gotd/Makefile
fi
ensure_secrets:
- @echo 'auth flan password' > $(PWD)/gotd-secrets.conf
+ @echo 'auth flan user flan password password' > $(PWD)/gotd-secrets.conf
@echo 'hmac flan ${GOTD_TEST_HMAC_SECRET}' >> $(PWD)/gotd-secrets.conf
@chown root:0 $(PWD)/gotd-secrets.conf
@chmod 600 $(PWD)/gotd-secrets.conf
blob - /dev/null
blob + fa38aa565b73c665735383b9d5e89ab892024296 (mode 644)
--- /dev/null
+++ regress/secrets/01.conf
+# comment
+ # another comment
+
+hmac mochi MWZJblv6BNcmq4maua3XxV0VhsiVzNPXy4NdOdliD/o=
+
+auth mochi user "flan" password s3cr37!
+
blob - /dev/null
blob + e2bb78be82355cb00eafe6ba3b0e84c160ae71a5 (mode 644)
--- /dev/null
+++ regress/secrets/01.exp
+hmac mochi MWZJblv6BNcmq4maua3XxV0VhsiVzNPXy4NdOdliD/o=
+auth mochi user flan password s3cr37!
blob - /dev/null
blob + 022028b8dd8473cef44adda54fb260d5f9fc9636 (mode 644)
--- /dev/null
+++ regress/secrets/02.conf
+auth "mochi" user fl"a"'n' password "hello world"' !'
+auth mochi2 user flan password hello\ world\ \"!
blob - /dev/null
blob + 1afaa89044652a0e501b2126a22ade9a12df920e (mode 644)
--- /dev/null
+++ regress/secrets/02.exp
+auth mochi user flan password hello world !
+auth mochi2 user flan password hello world "!
blob - /dev/null
blob + 6d6bea38ab10b382b0a4f3edb21447810face661 (mode 644)
--- /dev/null
+++ regress/secrets/03.conf
+auth mochi user flan password "foo
blob - /dev/null
blob + 4283ca13d5e67b815966c80be3bef7c230110e8e (mode 644)
--- /dev/null
+++ regress/secrets/04.conf
+hmac mochi foo\
blob - /dev/null
blob + 4283ca13d5e67b815966c80be3bef7c230110e8e (mode 644)
--- /dev/null
+++ regress/secrets/05.conf
+hmac mochi foo\
blob - /dev/null
blob + a7171f20953ee46932809c2cdb7deac376f5d7ee (mode 644)
--- /dev/null
+++ regress/secrets/Makefile
+.PATH:${.CURDIR}/../../gotd/
+.PATH:${.CURDIR}/../../lib/
+
+PROG = secrets
+SRCS = secrets-parser.c error.c hash.c log.c secrets.c
+
+CPPFLAGS += -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
+CPPFLAGS += -I${.CURDIR}/../../gotd
+
+REGRESS_TARGETS = empty comments quotes unclosed invalid-escape syntax
+
+empty:
+ ./secrets /dev/null | diff -u /dev/null -
+
+comments:
+ ./secrets ${.CURDIR}/01.conf | diff -u ${.CURDIR}/01.exp -
+
+quotes:
+ ./secrets ${.CURDIR}/02.conf | diff -u ${.CURDIR}/02.exp -
+
+unclosed:
+ ! ./secrets ${.CURDIR}/03.conf
+ @echo "expected failure; it's OK"
+
+invalid-escape:
+ ! ./secrets ${.CURDIR}/04.conf
+ @echo "expected failure; it's OK"
+
+syntax:
+ ! ./secrets ${.CURDIR}/05.conf
+ @echo "expected failure; it's OK"
+
+.include <bsd.regress.mk>
blob - /dev/null
blob + 94fc117c61f36b5b7a021f2bf1c537b13e3b2555 (mode 644)
--- /dev/null
+++ regress/secrets/secrets-parser.c
+/*
+ * Copyright (c) 2024 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <err.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "got_error.h"
+
+#include "log.h"
+#include "secrets.h"
+
+static void __dead
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-v] file\n", getprogname());
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const struct got_error *error;
+ struct gotd_secrets *secrets;
+ struct gotd_secret *secret;
+ FILE *fp;
+ size_t i;
+ int ch, verbose = 0;
+
+ if (pledge("stdio rpath", NULL) == -1)
+ err(1, "pledge");
+
+ while ((ch = getopt(argc, argv, "v")) != -1) {
+ switch (ch) {
+ case 'v':
+ verbose = 1;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ log_init(1, LOG_USER);
+ log_procinit("secrets");
+ log_setverbose(verbose);
+
+ if ((fp = fopen(argv[0], "r")) == NULL)
+ err(1, "can't open %s", argv[0]);
+
+ if ((error = gotd_secrets_parse(argv[0], fp, &secrets)) != NULL)
+ errx(1, "failed to parse %s: %s", argv[0], error->msg);
+
+ for (i = 0; i < secrets->len; ++i) {
+ secret = &secrets->secrets[i];
+
+ if (secret->type == GOTD_SECRET_AUTH) {
+ printf("auth %s user %s password %s\n",
+ secret->label, secret->user, secret->pass);
+ } else {
+ printf("hmac %s %s\n", secret->label, secret->hmac);
+ }
+ }
+
+ gotd_secrets_free(secrets);
+ return 0;
+}