commit - c2b405718eabe04c7ac3fa5916c1951227ab99fe
commit + 05a7a31cc5fec292b1ca3e6298afb82029372a79
blob - bd50b7baeef6f944713df1b7a9cd66e274d6b537
blob + 8b09c32c7483d4e3129c291aa428fc5e16a84e47
--- gotd/gotd.h
+++ gotd/gotd.h
char *identifier;
};
STAILQ_HEAD(gotd_access_rule_list, gotd_access_rule);
+
+enum gotd_notification_target_type {
+ GOTD_NOTIFICATION_VIA_EMAIL,
+ GOTD_NOTIFICATION_VIA_HTTP
+};
+
+struct gotd_notification_target {
+ STAILQ_ENTRY(gotd_notification_target) entry;
+
+ enum gotd_notification_target_type type;
+ union {
+ struct {
+ char *recipient;
+ int with_diff;
+ int shortlog;
+ } email;
+ struct {
+ char *url;
+ int shortlog;
+ char *user;
+ char *password;
+ } http;
+ } conf;
+};
+STAILQ_HEAD(gotd_notification_targets, gotd_notification_target);
struct gotd_repo {
TAILQ_ENTRY(gotd_repo) entry;
struct got_pathlist_head protected_tag_namespaces;
struct got_pathlist_head protected_branch_namespaces;
struct got_pathlist_head protected_branches;
+
+ struct got_pathlist_head notification_refs;
+ struct got_pathlist_head notification_ref_namespaces;
+ int summarize_notifications;
+ struct gotd_notification_targets notification_targets;
};
TAILQ_HEAD(gotd_repolist, gotd_repo);
struct gotd_uid_connection_limit *gotd_find_uid_connection_limit(
struct gotd_uid_connection_limit *limits, size_t nlimits, uid_t uid);
int gotd_parseuid(const char *s, uid_t *uid);
+const struct got_error *gotd_parse_url(char **, char **, char **,
+ char **, const char *);
/* imsg.c */
const struct got_error *gotd_imsg_flush(struct imsgbuf *);
blob - 58db764d105aff68e65237083482558bcf17fb47
blob + 58651f6173da0ee1a7f94cb40e58aee574f62939
--- gotd/parse.y
+++ gotd/parse.y
struct gotd_repo *, char *);
static int conf_protect_branch(struct gotd_repo *,
char *);
+static int conf_notify_branch(struct gotd_repo *,
+ char *);
+static int conf_notify_ref_namespace(struct gotd_repo *,
+ char *);
+static void conf_notify_summarize(struct gotd_repo *);
+static int conf_notify_email(struct gotd_repo *,
+ char *, int, int);
+static int conf_notify_http(struct gotd_repo *,
+ char *, int, char *, char *);
static enum gotd_procid gotd_proc_id;
typedef struct {
%token PATH ERROR LISTEN ON USER REPOSITORY PERMIT DENY
%token RO RW CONNECTION LIMIT REQUEST TIMEOUT
-%token PROTECT NAMESPACE BRANCH TAG
+%token PROTECT NAMESPACE BRANCH TAG REFERENCE
+%token NOTIFY SUMMARIZE SHORTLOG EMAIL URL PASSWORD WITH DIFF
%token <v.string> STRING
%token <v.number> NUMBER
}
;
+notify : NOTIFY '{' optnl notifyflags_l '}'
+ | NOTIFY notifyflags
+
+notifyflags_l : notifyflags optnl notifyflags_l
+ | notifyflags optnl
+ ;
+
+notifyflags : BRANCH STRING {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_branch(new_repo, $2)) {
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ }
+ | REFERENCE NAMESPACE STRING {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_ref_namespace(new_repo, $3)) {
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ }
+ | SUMMARIZE {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE)
+ conf_notify_summarize(new_repo);
+ }
+ | EMAIL STRING {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_email(new_repo, $2, 0, 0)) {
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ }
+ | EMAIL STRING SHORTLOG {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_email(new_repo, $2, 1, 0)) {
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ }
+ | EMAIL STRING WITH DIFF {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_email(new_repo, $2, 0, 1)) {
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ }
+ | EMAIL STRING SHORTLOG WITH DIFF {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_email(new_repo, $2, 1, 1)) {
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ }
+ | URL STRING {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_http(new_repo, $2, 0, NULL,
+ NULL)) {
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ }
+ | URL STRING SHORTLOG {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_http(new_repo, $2, 1, NULL,
+ NULL)) {
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ }
+ | URL STRING USER STRING PASSWORD STRING {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_http(new_repo, $2, 1, $4, $6)) {
+ free($2);
+ free($4);
+ free($6);
+ YYERROR;
+ }
+ free($2);
+ free($4);
+ free($6);
+ }
+ }
+ | URL STRING SHORTLOG USER STRING PASSWORD STRING {
+ if (gotd_proc_id == PROC_GOTD ||
+ gotd_proc_id == PROC_SESSION_WRITE) {
+ if (conf_notify_http(new_repo, $2, 1, $5, $7)) {
+ free($2);
+ free($5);
+ free($7);
+ YYERROR;
+ }
+ free($2);
+ free($5);
+ free($7);
+ }
+ }
+ ;
+
repository : REPOSITORY STRING {
struct gotd_repo *repo;
free($2);
}
| protect
+ | notify
;
repoopts2 : repoopts2 repoopts1 nl
{ "branch", BRANCH },
{ "connection", CONNECTION },
{ "deny", DENY },
+ { "diff", DIFF },
+ { "email", EMAIL },
{ "limit", LIMIT },
{ "listen", LISTEN },
{ "namespace", NAMESPACE },
{ "on", ON },
+ { "password", PASSWORD },
{ "path", PATH },
{ "permit", PERMIT },
{ "protect", PROTECT },
+ { "reference", REFERENCE },
{ "repository", REPOSITORY },
{ "request", REQUEST },
{ "ro", RO },
{ "rw", RW },
+ { "shortlog", SHORTLOG },
+ { "summarize", SUMMARIZE },
{ "tag", TAG },
{ "timeout", TIMEOUT },
+ { "url", URL },
{ "user", USER },
+ { "with", WITH }
};
const struct keywords *p;
STAILQ_INIT(&repo->rules);
TAILQ_INIT(&repo->protected_tag_namespaces);
TAILQ_INIT(&repo->protected_branch_namespaces);
+ TAILQ_INIT(&repo->protected_branches);
TAILQ_INIT(&repo->protected_branches);
+ TAILQ_INIT(&repo->notification_refs);
+ TAILQ_INIT(&repo->notification_ref_namespaces);
+ STAILQ_INIT(&repo->notification_targets);
if (strlcpy(repo->name, name, sizeof(repo->name)) >=
sizeof(repo->name))
yyerror("got_pathlist_insert: %s", error->msg);
else
yyerror("duplicate protect branch %s", branchname);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+conf_notify_branch(struct gotd_repo *repo, char *branchname)
+{
+ const struct got_error *error;
+ struct got_pathlist_entry *pe;
+ char *refname;
+
+ if (strncmp(branchname, "refs/heads/", 11) != 0) {
+ if (asprintf(&refname, "refs/heads/%s", branchname) == -1) {
+ yyerror("asprintf: %s", strerror(errno));
+ return -1;
+ }
+ } else {
+ refname = strdup(branchname);
+ if (refname == NULL) {
+ yyerror("strdup: %s", strerror(errno));
+ return -1;
+ }
+ }
+
+ if (!refname_is_valid(refname)) {
+ free(refname);
+ return -1;
+ }
+
+ error = got_pathlist_insert(&pe, &repo->notification_refs,
+ refname, NULL);
+ if (error) {
+ free(refname);
+ yyerror("got_pathlist_insert: %s", error->msg);
+ return -1;
+ }
+ if (pe == NULL)
+ free(refname);
+
+ return 0;
+}
+
+static int
+conf_notify_ref_namespace(struct gotd_repo *repo, char *namespace)
+{
+ const struct got_error *error;
+ struct got_pathlist_entry *pe;
+ char *s;
+
+ got_path_strip_trailing_slashes(namespace);
+ if (!refname_is_valid(namespace))
+ return -1;
+
+ if (asprintf(&s, "%s/", namespace) == -1) {
+ yyerror("asprintf: %s", strerror(errno));
return -1;
}
+ error = got_pathlist_insert(&pe, &repo->notification_ref_namespaces,
+ s, NULL);
+ if (error) {
+ free(s);
+ yyerror("got_pathlist_insert: %s", error->msg);
+ return -1;
+ }
+ if (pe == NULL)
+ free(s);
+
return 0;
}
+static void
+conf_notify_summarize(struct gotd_repo *repo)
+{
+ repo->summarize_notifications = 1;
+}
+
+static int
+conf_notify_email(struct gotd_repo *repo, char *recipient, int shortlog,
+ int with_diff)
+{
+ struct gotd_notification_target *target;
+
+ STAILQ_FOREACH(target, &repo->notification_targets, entry) {
+ if (target->type != GOTD_NOTIFICATION_VIA_EMAIL)
+ continue;
+ if (strcmp(target->conf.email.recipient, recipient) == 0) {
+ yyerror("duplicate email notification for '%s' in "
+ "repository '%s'", recipient, repo->name);
+ return -1;
+ }
+ }
+
+ target = calloc(1, sizeof(*target));
+ if (target == NULL)
+ fatal("calloc");
+ target->type = GOTD_NOTIFICATION_VIA_EMAIL;
+ target->conf.email.recipient = strdup(recipient);
+ if (target->conf.email.recipient == NULL)
+ fatal("calloc");
+ target->conf.email.with_diff = with_diff;
+ target->conf.email.shortlog = shortlog;
+
+ STAILQ_INSERT_TAIL(&repo->notification_targets, target, entry);
+ return 0;
+}
+
+static int
+conf_notify_http(struct gotd_repo *repo, char *url, int shortlog,
+ char *user, char *password)
+{
+ const struct got_error *error;
+ struct gotd_notification_target *target;
+ char *proto, *host, *port, *request_path;
+ int ret = 0;
+
+ error = gotd_parse_url(&proto, &host, &port, &request_path, url);
+ if (error) {
+ yyerror("invalid HTTP notification URL '%s' in "
+ "repository '%s': %s", url, repo->name, error->msg);
+ return -1;
+ }
+
+ if (strcmp(proto, "http") != 0 && strcmp(proto, "https") != 0) {
+ yyerror("invalid protocol '%s' in notification URL '%s' in "
+ "repository '%s", proto, url, repo->name);
+ ret = -1;
+ goto done;
+ }
+
+ if (strcmp(proto, "http") == 0 && (user != NULL || password != NULL)) {
+ log_warnx("%s: WARNING: Using basic authentication over "
+ "plaintext http:// will leak credentials; https:// is "
+ "recommended for URL '%s'", getprogname(), url);
+ }
+
+ STAILQ_FOREACH(target, &repo->notification_targets, entry) {
+ if (target->type != GOTD_NOTIFICATION_VIA_HTTP)
+ continue;
+ if (strcmp(target->conf.http.url, url) == 0) {
+ yyerror("duplicate notification for URL '%s' in "
+ "repository '%s'", url, repo->name);
+ ret = -1;
+ goto done;
+ }
+ }
+
+ target = calloc(1, sizeof(*target));
+ if (target == NULL)
+ fatal("calloc");
+ target->type = GOTD_NOTIFICATION_VIA_HTTP;
+ target->conf.http.url = strdup(url);
+ if (target->conf.http.url == NULL)
+ fatal("calloc");
+ target->conf.http.shortlog = shortlog;
+ if (user) {
+ target->conf.http.user = strdup(user);
+ if (target->conf.http.user == NULL)
+ fatal("calloc");
+ }
+ if (password) {
+ target->conf.http.password = strdup(password);
+ if (target->conf.http.password == NULL)
+ fatal("calloc");
+ }
+
+ STAILQ_INSERT_TAIL(&repo->notification_targets, target, entry);
+done:
+ free(proto);
+ free(host);
+ free(port);
+ free(request_path);
+ return ret;
+}
+
int
symset(const char *nam, const char *val, int persist)
{
return -1;
return 0;
}
+
+const struct got_error *
+gotd_parse_url(char **proto, char **host, char **port,
+ char **request_path, const char *url)
+{
+ const struct got_error *err = NULL;
+ char *s, *p, *q;
+
+ *proto = *host = *port = *request_path = NULL;
+
+ p = strstr(url, "://");
+ if (!p)
+ return got_error(GOT_ERR_PARSE_URI);
+
+ *proto = strndup(url, p - url);
+ if (*proto == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ s = p + 3;
+
+ p = strstr(s, "/");
+ if (p == NULL || strlen(p) == 1) {
+ err = got_error(GOT_ERR_PARSE_URI);
+ goto done;
+ }
+
+ q = memchr(s, ':', p - s);
+ if (q) {
+ *host = strndup(s, q - s);
+ if (*host == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if ((*host)[0] == '\0') {
+ err = got_error(GOT_ERR_PARSE_URI);
+ goto done;
+ }
+ *port = strndup(q + 1, p - (q + 1));
+ if (*port == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if ((*port)[0] == '\0') {
+ err = got_error(GOT_ERR_PARSE_URI);
+ goto done;
+ }
+ } else {
+ *host = strndup(s, p - s);
+ if (*host == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if ((*host)[0] == '\0') {
+ err = got_error(GOT_ERR_PARSE_URI);
+ goto done;
+ }
+ }
+
+ while (p[0] == '/' && p[1] == '/')
+ p++;
+ *request_path = strdup(p);
+ if (*request_path == NULL) {
+ err = got_error_from_errno("strdup");
+ goto done;
+ }
+ got_path_strip_trailing_slashes(*request_path);
+ if ((*request_path)[0] == '\0') {
+ err = got_error(GOT_ERR_PARSE_URI);
+ goto done;
+ }
+done:
+ if (err) {
+ free(*proto);
+ *proto = NULL;
+ free(*host);
+ *host = NULL;
+ free(*port);
+ *port = NULL;
+ free(*request_path);
+ *request_path = NULL;
+ }
+ return err;
+}