commit - 2668ec1c3f803454fa75fdcfd86d3a8e6d9ab069
commit + 46fa6498d5de78cc0dce21a09958993f0ebc274d
blob - 68b3cdf4b5d8d53e812ddef4196adf3ae0ebbf34
blob + fd4db424121e7744a2b14792f89c76873bfde529
--- gotsys/gotsys.conf.5
+++ gotsys/gotsys.conf.5
.Nm .
These namespaces are always protected and even attempts to create new
references in these namespaces will always be denied.
-.\".It Ic notify Brq Ar ...
-.\"The
-.\".Ic notify
-.\"directive enables notifications about new commits or tags
-.\"added to the repository.
-.\".Pp
-.\"The default content of email notifications looks similar to the output of the
-.\".Cm got log -d
-.\"command.
-.\".Pp
-.\"Notifications via HTTP require a HTTP or HTTPS server which is accepting
-.\"POST requests with or without HTTP Basic authentication.
-.\"Depending on the use case a custom server-side CGI script may be required
-.\"for the processing of notifications.
-.\"HTTP notifications can achieve functionality
-.\"similar to Git's server-side post-receive hook script
-.\"by triggering arbitrary post-commit actions via the HTTP server.
-.\".Pp
-.\"The
-.\".Ic notify
-.\"directive expects parameters which must be enclosed in curly braces.
-.\"The available parameters are as follows:
-.\".Bl -tag -width Ds
-.\".It Ic branch Ar name
-.\"Send notifications about commits to the named branch.
-.\"The
-.\".Ar name
-.\"will be looked up in the
-.\".Dq refs/heads/
-.\"reference namespace.
-.\"This directive may be specified multiple times to build a list of
-.\"branches to send notifications for.
-.\"If neither a
-.\".Ic branch
-.\"nor a
-.\".Ic reference namespace
-.\"are specified then changes to any reference will trigger notifications.
-.\".It Ic reference Ic namespace Ar namespace
-.\"Send notifications about commits or tags within a reference namespace.
-.\"This directive may be specified multiple times to build a list of
-.\"namespaces to send notifications for.
-.\"If neither a
-.\".Ic branch
-.\"nor a
-.\".Ic reference namespace
-.\"are specified then changes to any reference will trigger notifications.
-.\".It Ic email Ic to Ar recipient Oo Ic reply to Ar responder Oc
-.\"Send notifications via email to the specified
-.\".Ar recipient .
-.\"This directive may be specified multiple times to build a list of
-.\"recipients to send notifications to.
-.\".Pp
-.\"The
-.\".Ar recipient
-.\"must be an email addresses that accepts mail.
-.\".Pp
-.\"If a
-.\".Ar responder
-.\"is specified via the
-.\".Ic reply to
-.\"directive, the
-.\".Ar responder
-.\"will be used as the Reply-to address.
-.\"Setting the Reply-to header can be useful if replies should go to a
-.\"mailing list instead of the
-.\".Ar sender ,
-.\"for example.
-.\".It Ic url Ar URL Oo Ic user Ar user Ic password Ar password Oo Ic insecure Oc Oc Oo Ic hmac Ar secret Oc
-.\"Send notifications via HTTP.
-.\"This directive may be specified multiple times to build a list of
-.\"HTTP servers to send notifications to.
-.\".Pp
-.\"The notification will be sent as a POST request to the given
-.\".Ar URL ,
-.\"which must be a valid HTTP URL and begin with either
-.\".Dq http://
-.\"or
-.\".Dq https:// .
-.\"If HTTPS is used, sending of notifications will only succeed if
-.\"no TLS errors occur.
-.\".Pp
-.\"The optional
-.\".Ic user
-.\"and
-.\".Ic password
-.\"directives enable HTTP Basic authentication.
-.\"If used, both a
-.\".Ar user
-.\"and a
-.\".Ar password
-.\"must be specified.
-.\"The
-.\".Ar password
-.\"must not be an empty string.
-.\"Unless the
-.\".Ic insecure
-.\"option is specified the notification target
-.\".Ar URL
-.\"must be a
-.\".Dq https://
-.\"URL to avoid leaking of authentication credentials.
-.\".Pp
-.\"If a
-.\".Ic hmac
-.\".Ar secret
-.\"is provided, the request body will be signed using HMAC, allowing the
-.\"receiver to verify the notification message's authenticity and integrity.
-.\"The signature uses HMAC-SHA256 and will be sent in the HTTP header
-.\".Dq X-Gotd-Signature .
-.\"Suitable secrets can be generated with
-.\".Xr openssl 1
-.\"as follows:
-.\".Pp
-.\".Dl $ openssl rand -base64 32
-.\".Pp
-.\"The request body contains a JSON object with a
-.\".Dq notifications
-.\"property containing an array of notification objects.
-.\"This JSON format is documented in
-.\".Xr gotd 8 .
-.\".El
+.It Ic notify Brq Ar ...
+The
+.Ic notify
+directive enables notifications about new commits or tags
+added to the repository.
+.Pp
+The default content of email notifications looks similar to the output of the
+.Cm got log -d
+command.
+.Pp
+Notifications via HTTP require a HTTP or HTTPS server which is accepting
+POST requests with or without HTTP Basic authentication.
+Depending on the use case a custom server-side CGI script may be required
+for the processing of notifications.
+HTTP notifications can achieve functionality
+similar to Git's server-side post-receive hook script
+by triggering arbitrary post-commit actions via the HTTP server.
+.Pp
+The
+.Ic notify
+directive expects parameters which must be enclosed in curly braces.
+The available parameters are as follows:
+.Bl -tag -width Ds
+.It Ic branch Ar name
+Send notifications about commits to the named branch.
+The
+.Ar name
+will be looked up in the
+.Dq refs/heads/
+reference namespace.
+This directive may be specified multiple times to build a list of
+branches to send notifications for.
+If neither a
+.Ic branch
+nor a
+.Ic reference namespace
+are specified then changes to any reference will trigger notifications.
+.It Ic reference Ic namespace Ar namespace
+Send notifications about commits or tags within a reference namespace.
+This directive may be specified multiple times to build a list of
+namespaces to send notifications for.
+If neither a
+.Ic branch
+nor a
+.Ic reference namespace
+are specified then changes to any reference will trigger notifications.
+.It Ic email Ic to Ar recipient Oo Ic reply to Ar responder Oc
+Send notifications via email to the specified
+.Ar recipient .
+This directive may be specified multiple times to build a list of
+recipients to send notifications to.
+.Pp
+The
+.Ar recipient
+must be an email address that accepts mail.
+.Pp
+If a
+.Ar responder
+is specified via the
+.Ic reply to
+directive, the
+.Ar responder
+will be used as the Reply-to address.
+Setting the Reply-to header can be useful if replies should go to a
+mailing list, for example.
+.It Ic url Ar URL Oo Ic user Ar user Ic password Ar password Oo Ic insecure Oc Oc Oo Ic hmac Ar secret Oc
+Send notifications via HTTP.
+This directive may be specified multiple times to build a list of
+HTTP servers to send notifications to.
+.Pp
+The notification will be sent as a POST request to the given
+.Ar URL ,
+which must be a valid HTTP URL and begin with either
+.Dq http://
+or
+.Dq https:// .
+If HTTPS is used, sending of notifications will only succeed if
+no TLS errors occur.
+.Pp
+The optional
+.Ic user
+and
+.Ic password
+directives enable HTTP Basic authentication.
+If used, both a
+.Ar user
+and a
+.Ar password
+must be specified.
+The
+.Ar password
+must not be an empty string.
+Unless the
+.Ic insecure
+option is specified the notification target
+.Ar URL
+must be a
+.Dq https://
+URL to avoid leaking of authentication credentials.
+.Pp
+If a
+.Ic hmac
+.Ar secret
+is provided, the request body will be signed using HMAC, allowing the
+receiver to verify the notification message's authenticity and integrity.
+The signature uses HMAC-SHA256 and will be sent in the HTTP header
+.Dq X-Gotd-Signature .
+Suitable secrets can be generated with
+.Xr openssl 1
+as follows:
+.Pp
+.Dl $ openssl rand -base64 32
+.Pp
+The request body contains a JSON object with a
+.Dq notifications
+property containing an array of notification objects.
+The following notification object properties are always present:
+.Bl -tag -width authenticated_user
+.It Dv repo
+The repository name as a string.
+.It Dv authenticated_user
+The committer's user account as authenticated by
+.Xr gotd 8
+as a string.
+.It Dv type
+The notification object type as a string.
.El
+.Pp
+Each notification object carries additional type-specific properties.
+The types and their type-specific properties are:
+.Bl -tag -width Ds
+.It Dv commit
+The commit notification object has the following fields.
+Except where noted, all are optional.
+.Bl -tag -width Ds
+.It Dv short
+Boolean, indicates whether the object has all the fields set.
+When several commits are batched in a single send operation, not all of
+the fields are available for each commit object.
+.It Dv id
+The commit ID as string, may be abbreviated.
+.It Dv committer
+An object with the committer information with the following fields:
+.Pp
+.Bl -tag -compact -width Ds
+.It Dv full
+Committer's full name.
+.It Dv name
+Committer's name.
+.It Dv mail
+Committer's mail address.
+.It Dv user
+Committer's username.
+This is the only field guaranteed to be set.
+.El
+.It Dv author
+An object with the author information.
+Has the same fields as the
+.Sq committer
+but may be unset.
+.It Dv date
+Number, representing the number of seconds since the Epoch in UTC.
+.It Dv short_message
+The first line of the commit message.
+This field is always set.
+.It Dv message
+The complete commit message, may be unset.
+.It Dv diffstat
+An object with the summarized changes, may be unset.
+Contains a
+.Sq files
+field with an array of objects describing the changes per-file and a
+.Sq total
+field with the cumulative changes.
+The changes per-file contains the following fields:
+.Pp
+.Bl -tag -compact -width removed
+.It Dv action
+A string describing the action, can be
+.Dq added ,
+.Dq deleted ,
+.Dq modified ,
+.Dq mode changed ,
+or
+.Dq unknown .
+.It Dv file
+The file path.
+.It Dv added
+The number of lines added.
+.It Dv removed
+The number of lines removed.
+.El
+.Pp
+The
+.Sq total
+object contains two fields:
+.Sq added
+and
+.Sq removed
+which are the number of added and removed lines respectively.
+.El
+.It Dv branch-deleted
+The branch deleted notifications has the following fields, all guaranteed
+to be set:
+.Bl -tag -width Ds
+.It Dv ref
+The removed branch reference.
+.It Dv id
+The hash of the commit pointed by the deleted branch.
+.El
+.It Dv tag
+The tag notification has the following fields, all guaranteed to be set:
+.Bl -tag -width Ds
+.It tag
+The tag reference.
+.It tagger
+The user information, with the same format of the
+.Sq committer
+field for the
+.Sq commit
+notification but with all the field guaranteed to be set.
+.It Dv date
+Number, representing the number of seconds since the Epoch in UTC.
+.It Dv object
+The object being tagged.
+It contains the fields
+.Sq type
+with the object type and
+.Sq id
+with the object id being tagged.
+.It Dv message
+The tag message.
+.El
+.El
+.El
+.El
.Sh EXAMPLES
.Bd -literal -offset indent
group developers
branch "main"
tag namespace "refs/tags/"
}
-.\"
-.\" notify {
-.\" branch "main"
-.\" reference namespace "refs/tags/"
-.\" email to openbsd-ports-changes@example.com
-.\" }
+
+ notify {
+ branch "main"
+ reference namespace "refs/tags/"
+ email to openbsd-ports-changes@example.com
+ }
}
repository "secret" {
blob - d6c4206f6fde168542a00fd487891e6fa296621a
blob + ba7ff338e3c04fc7b3d068b9db8f76aa2af69e17
--- gotsys/gotsys.h
+++ gotsys/gotsys.h
size_t nprotected_branches;
struct got_pathlist_head notification_refs;
+ size_t num_notification_refs;
struct got_pathlist_head notification_ref_namespaces;
+ size_t num_notification_ref_namespaces;
struct gotsys_notification_targets notification_targets;
};
TAILQ_HEAD(gotsys_repolist, gotsys_repo);
blob - b39d9501ba290cd72e72747e8f31970ce5e09ec4
blob + 97555fb391c138b895b056ac33aa9ae9bf247c3d
--- gotsys/parse.y
+++ gotsys/parse.y
}
if (pe == NULL)
free(refname);
+ else
+ repo->num_notification_refs++;
return 0;
}
}
if (pe == NULL)
free(s);
+ else
+ repo->num_notification_ref_namespaces++;
return 0;
}
static int
+email_address_is_valid(const char *s)
+{
+ const char allowed[] = {
+ '!', '%', '&', '*', '+', '-', '/', '?',
+ '^', '_', '`', '.', '{', '|', '}', '~'
+ };
+ size_t i, j, len = strlen(s);
+ char *at;
+ ptrdiff_t local_len;
+ size_t domain_len;
+
+ if (s[0] == '\0' || s[0] == '.')
+ return 0;
+
+ at = strchr(s, '@');
+ if (at == NULL)
+ return 0;
+
+ local_len = at - s;
+ if (local_len == 0 || local_len > 64)
+ return 0;
+
+ for (i = 0; i < local_len; i++) {
+ if (isalnum(s[i]))
+ continue;
+
+ for (j = 0; j < nitems(allowed); j++) {
+ if (s[i] == allowed[j])
+ break;
+ }
+ if (j < nitems(allowed))
+ continue;
+
+ return 0;
+ }
+
+ if (s[local_len - 1] == '.')
+ return 0;
+
+ if (s[local_len + 1] == '-' || s[len - 1] == '-')
+ return 0;
+
+ domain_len = len - local_len;
+ if (domain_len == 0 || domain_len > 255)
+ return 0;
+
+ for (i = local_len + 1; i < domain_len; i++) {
+ if (isalnum(s[i]) || s[i] == '.' || s[i] == '-')
+ continue;
+
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
conf_notify_email(struct gotsys_repo *repo, char *sender, char *recipient,
char *responder, char *hostname, char *port)
{
}
target->type = GOTSYS_NOTIFICATION_VIA_EMAIL;
if (sender) {
+ if (!email_address_is_valid(sender)) {
+ yyerror("invalid email address: %s", sender);
+ goto free_target;
+ }
target->conf.email.sender = strdup(sender);
if (target->conf.email.sender == NULL) {
yyerror("strdup: %s", strerror(errno));
goto free_target;
}
}
+
+ if (!email_address_is_valid(recipient)) {
+ yyerror("invalid email address: %s", recipient);
+ goto free_target;
+ }
target->conf.email.recipient = strdup(recipient);
if (target->conf.email.recipient == NULL) {
yyerror("strdup: %s", strerror(errno));
goto free_target;
}
if (responder) {
+ if (!email_address_is_valid(responder)) {
+ yyerror("invalid email address: %s", responder);
+ goto free_target;
+ }
target->conf.email.responder = strdup(responder);
if (target->conf.email.responder == NULL) {
yyerror("strdup: %s", strerror(errno));
return -1;
}
+static inline int
+should_urlencode(int c)
+{
+ if (c <= ' ' || c >= 127)
+ return 1;
+
+ switch (c) {
+ /* gen-delim */
+ case ':':
+ case '/':
+ case '?':
+ case '#':
+ case '[':
+ case ']':
+ case '@':
+ /* sub-delims */
+ case '!':
+ case '$':
+ case '&':
+ case '\'':
+ case '(':
+ case ')':
+ case '*':
+ case '+':
+ case ',':
+ case ';':
+ case '=':
+ /* needed because the URLs are embedded into gotd.conf */
+ case '\"':
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static char *
+urlencode(const char *str)
+{
+ const char *s;
+ char *escaped;
+ size_t i, len;
+ int a, b;
+
+ len = 0;
+ for (s = str; *s; ++s) {
+ len++;
+ if (len == 1 && *s == '/')
+ continue;
+ if (should_urlencode(*s))
+ len += 2;
+ }
+
+ escaped = calloc(1, len + 1);
+ if (escaped == NULL)
+ return NULL;
+
+ i = 0;
+ for (s = str; *s; ++s) {
+ if (i == 0 && *s == '/') {
+ escaped[i++] = *s;
+ continue;
+ }
+ if (should_urlencode(*s)) {
+ a = (*s & 0xF0) >> 4;
+ b = (*s & 0x0F);
+
+ escaped[i++] = '%';
+ escaped[i++] = a <= 9 ? ('0' + a) : ('7' + a);
+ escaped[i++] = b <= 9 ? ('0' + b) : ('7' + b);
+ } else
+ escaped[i++] = *s;
+ }
+
+ return escaped;
+}
+
static const struct got_error *
parse_url(char **proto, char **host, char **port,
char **request_path, const char *url)
{
const struct got_error *err = NULL;
char *s, *p, *q;
+ size_t i, host_len;
*proto = *host = *port = *request_path = NULL;
p = strstr(url, "://");
- if (!p)
- return got_error(GOT_ERR_PARSE_URI);
+ if (!p) {
+ return got_error_msg(GOT_ERR_PARSE_URI,
+ "no protocol specified");
+ }
*proto = strndup(url, p - url);
if (*proto == NULL) {
s = p + 3;
p = strstr(s, "/");
- if (p == NULL) {
- err = got_error(GOT_ERR_PARSE_URI);
- goto done;
- }
+ if (p == NULL)
+ p = (char *)&url[strlen(url) - 1];
q = memchr(s, ':', p - s);
if (q) {
err = got_error(GOT_ERR_PARSE_URI);
goto done;
}
+ if (strcmp(*port, "http") != 0 &&
+ strcmp(*port, "https") != 0) {
+ const char *errstr;
+
+ (void)strtonum(*port, 1, USHRT_MAX, &errstr);
+ if (errstr != NULL) {
+ err = got_error_fmt(GOT_ERR_PARSE_URI,
+ "port number '%s' is %s", *port, errstr);
+ goto done;
+ }
+ }
} else {
*host = strndup(s, p - s);
if (*host == NULL) {
goto done;
}
if ((*host)[0] == '\0') {
- err = got_error(GOT_ERR_PARSE_URI);
+ err = got_error_msg(GOT_ERR_PARSE_URI,
+ "hostname cannot be empty");
goto done;
}
}
+ host_len = strlen(*host);
+ for (i = 0; i < host_len; i++) {
+ if (isalnum((*host)[i]) ||
+ (*host)[i] == '.' || (*host)[i] == '-')
+ continue;
+ err = got_error_fmt(GOT_ERR_PARSE_URI,
+ "invalid hostname: %s", *host);
+ goto done;
+
+ }
+
while (p[0] == '/' && p[1] == '/')
p++;
- *request_path = strdup(p);
- if (*request_path == NULL) {
- err = got_error_from_errno("strdup");
- goto done;
+ if (p[0] == '\0') {
+ *request_path = strdup("/");
+ if (*request_path == NULL) {
+ err = got_error_from_errno("strdup");
+ }
+ } else {
+ *request_path = urlencode(p);
+ if (*request_path == NULL)
+ err = got_error_from_errno("calloc");
}
- if ((*request_path)[0] == '\0') {
- err = got_error(GOT_ERR_PARSE_URI);
- goto done;
- }
done:
if (err) {
free(*proto);
}
static int
+basic_auth_user_is_valid(const char *s)
+{
+ size_t i, len;
+
+ if (s[0] == '\0')
+ return 0;
+
+ len = strlen(s);
+ for (i = 0; i < len; i++) {
+ if (s[i] & 0x80)
+ return 0;
+
+ if (isalnum(s[i]) ||
+ (i > 0 && s[i] == '-') ||
+ (i > 0 && s[i] == '_') ||
+ (i > 0 && s[i] == '.'))
+ continue;
+
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+basic_auth_password_is_valid(const char *s)
+{
+ size_t i, len;
+
+ if (s[0] == '\0')
+ return 0;
+
+ len = strlen(s);
+ for (i = 0; i < len; i++) {
+ if (s[i] & 0x80)
+ return 0;
+ if (iscntrl(s[i]))
+ return 0;
+ if (s[i] == '"')
+ return 0;
+
+ }
+
+ return 1;
+}
+
+static int
+validate_hmac_secret(const char *s, size_t len)
+{
+ static const u_int8_t base64chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ if (strchr(base64chars, s[i]) == NULL)
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
conf_notify_http(struct gotsys_repo *repo, char *url, char *user,
char *password, int insecure, char *hmac_secret)
{
path = NULL;
if (user) {
+ if (user[0] == '\0') {
+ yyerror("%s: basic auth user names cannot be empty",
+ url);
+ goto done;
+ }
+ if (!basic_auth_user_is_valid(user)) {
+ yyerror("%s: basic auth user names may only "
+ "contain alphabetic ASCII characters, "
+ "non-leading digits, non-leading hyphens, "
+ "non-leading underscores, or non-leading "
+ "periods", url);
+ goto done;
+ }
target->conf.http.user = strdup(user);
if (target->conf.http.user == NULL) {
yyerror("strdup: %s", strerror(errno));
goto done;
}
+ if (password[0] == '\0') {
+ yyerror("%s: basic auth passwords cannot be empty",
+ url);
+ goto done;
+ }
+ if (!basic_auth_password_is_valid(user)) {
+ yyerror("%s: passwords for basic auth may only "
+ "contain ASCII characters, excluding control "
+ "characters and the \" (double quote) character",
+ url);
+ goto done;
+ }
target->conf.http.password = strdup(password);
if (target->conf.http.password == NULL) {
yyerror("strdup: %s", strerror(errno));
}
if (hmac_secret) {
+ if (hmac_secret[9] == '\0') {
+ yyerror("hmac secrets cannot be empty");
+ goto done;
+ }
+ if (!validate_hmac_secret(hmac_secret, strlen(hmac_secret))) {
+ yyerror("hmac secrets must be base64-encoded; use "
+ "'openssl rand -base64 32' output instead of: %s",
+ hmac_secret);
+ goto done;
+ }
target->conf.http.hmac_secret = strdup(hmac_secret);
if (target->conf.http.hmac_secret == NULL) {
yyerror("strdup: %s", strerror(errno));
blob - f732d4ca5c76b8dbe25a819d920f14dfc615b1cb
blob + 200118a1594f49d882d2f0fb2c642bac8efd8d55
--- gotsysd/gotsysd.h
+++ gotsysd/gotsysd.h
#define GOTD_CONF_PATH "/etc/gotd.conf"
#endif
+#ifndef GOTD_SECRETS_PATH
+#define GOTD_SECRETS_PATH "/etc/gotd-secrets.conf"
+#endif
+
#ifndef GOTSYSD_PATH_GOTSH
#define GOTSYSD_PATH_GOTSH "/usr/local/bin/gotsh"
#endif
GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES,
GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM,
GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_ELEM,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_DONE,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_ELEM,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_DONE,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_EMAIL,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_HTTP,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGETS_DONE,
GOTSYSD_IMSG_SYSCONF_PARSE_DONE,
/* Addition of users and groups. */
/* Followed by path_len bytes. */
/* Followed by data_len bytes. */
+};
+
+/* Structure for GOTSYSD_IMSG_NOTIFICATION_TARGET_EMAIL. */
+struct gotsysd_imsg_notitfication_target_email {
+ size_t sender_len;
+ size_t recipient_len;
+ size_t responder_len;
+ size_t hostname_len;
+ size_t port_len;
+ size_t repo_name_len;
+
+ /*
+ * Followed by sender_len + responder_len + responder_len +
+ * hostname_len + port_len + repo_name_len bytes.
+ */
};
+/* Structure for GOTD_IMSG_NOTIFICATION_TARGET_HTTP. */
+struct gotsysd_imsg_notitfication_target_http {
+ int tls;
+ size_t hostname_len;
+ size_t port_len;
+ size_t path_len;
+ size_t user_len;
+ size_t password_len;
+ size_t hmac_len;
+ size_t repo_name_len;
+
+ /*
+ * Followed by hostname_len + port_len + path_len + user_len + password_len +
+ * hmac_len + repo_name_len bytes.
+ */
+};
+
#ifndef GOT_LIBEXECDIR
#define GOT_LIBEXECDIR /usr/libexec
#endif
struct gotsys_repolist;
struct gotsys_repo;
struct gotsys_access_rule;
+struct gotsys_notification_target;
struct got_pathlist_head;
const struct got_error *gotsys_imsg_send_users(struct gotsysd_imsgev *,
const struct got_error *gotsys_imsg_recv_pathlist(size_t *, struct imsg *);
const struct got_error *gotsys_imsg_recv_pathlist_elem(struct imsg *,
struct got_pathlist_head *);
+const struct got_error *gotsys_imsg_recv_notification_target_email(char **,
+ struct gotsys_notification_target **, struct imsg *);
+const struct got_error *gotsys_imsg_recv_notification_target_http(char **,
+ struct gotsys_notification_target **, struct imsg *);
struct gotsys_uidset_element;
struct gotsys_uidset;
blob - 7e3a2d02aa64a6ad196c47fb3ce747ed6294522b
blob + 92e28da9546ddf1755fbec09972f0a3fa16394b8
--- gotsysd/helpers.c
+++ gotsysd/helpers.c
case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES:
case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM:
case GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_ELEM:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_DONE:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_ELEM:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_DONE:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_EMAIL:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_HTTP:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGETS_DONE:
case GOTSYSD_IMSG_SYSCONF_PARSE_DONE:
if (proc->type != GOTSYSD_IMSG_START_PROG_READ_CONF) {
err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES:
case GOTSYSD_IMSG_SYSCONF_PROTECTED_BRANCHES_ELEM:
case GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_ELEM:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_DONE:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_ELEM:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_DONE:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_EMAIL:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_HTTP:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGETS_DONE:
case GOTSYSD_IMSG_SYSCONF_REPOS_DONE:
proc = find_proc(GOTSYSD_IMSG_START_PROG_WRITE_CONF,
1);
blob - de3b5c530209dfd7fd4ee70ed891e96ad029fc17
blob + 12ca1fecf1f999c56f4cc86fe5638973e2a976e2
--- gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c
+++ gotsysd/libexec/gotsys-write-conf/gotsys-write-conf.c
static size_t nprotected_refs_received;
static int gotd_conf_tmpfd = -1;
static char *gotd_conf_tmppath;
+static int gotd_secrets_tmpfd = -1;
+static char *gotd_secrets_tmppath;
static struct gotsys_access_rule_list global_repo_access_rules;
+static struct got_pathlist_head *notif_refs_cur;
+static size_t *num_notif_refs_cur;
+static size_t num_notif_refs_needed;
+static size_t num_notif_refs_received;
enum writeconf_state {
WRITECONF_STATE_EXPECT_USERS,
}
RB_FOREACH(pe, got_pathlist_head, &repo->protected_branches) {
+ err = refname_is_valid(pe->path);
+ if (err)
+ return err;
+ ret = dprintf(gotd_conf_tmpfd, "\t\tbranch \"%s\"\n", pe->path);
+ if (ret == -1) {
+ return got_error_from_errno2("dprintf",
+ gotd_conf_tmppath);
+ }
+ if (ret != 12 + strlen(pe->path))
+ return got_error_fmt(GOT_ERR_IO, "short write to %s",
+ gotd_conf_tmppath);
+ }
+
+ ret = dprintf(gotd_conf_tmpfd, "\t%s\n", closing);
+ if (ret == -1)
+ return got_error_from_errno2("dprintf", gotd_conf_tmppath);
+ if (ret != 2 + strlen(closing))
+ return got_error_fmt(GOT_ERR_IO, "short write to %s",
+ gotd_conf_tmppath);
+done:
+ free(namespace);
+ return NULL;
+}
+
+static const struct got_error *
+write_notification_target_email(struct gotsys_notification_target *target)
+{
+ char sender[128];
+ char recipient[128];
+ char responder[128];
+ int ret = 0;
+
+ if (target->conf.email.sender) {
+ ret = snprintf(sender, sizeof(sender), " from \"%s\"",
+ target->conf.email.sender);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(sender)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "notification email sender too long");
+ }
+ } else
+ sender[0] = '\0';
+
+ ret = snprintf(recipient, sizeof(recipient), " to \"%s\"",
+ target->conf.email.recipient);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(recipient)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "notification email recipient too long");
+ }
+
+ if (target->conf.email.responder) {
+ ret = snprintf(responder, sizeof(responder), " reply to \"%s\"",
+ target->conf.email.responder);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(responder)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "notification email responder too long");
+ }
+ } else
+ responder[0] = '\0';
+
+ ret = dprintf(gotd_conf_tmpfd, "\t\temail%s%s%s\n",
+ sender, recipient, responder);
+ if (ret == -1)
+ return got_error_from_errno2("dprintf", gotd_conf_tmppath);
+ if (ret != 8 + strlen(sender) + strlen(recipient) + strlen(responder)) {
+ return got_error_fmt(GOT_ERR_IO, "short write to %s",
+ gotd_conf_tmppath);
+ }
+
+ return NULL;
+}
+
+static const struct got_error *
+write_notification_target_http(struct gotsys_notification_target *target,
+ int idx)
+{
+ char proto[16];
+ char port[16];
+ char label[16];
+ char auth[128];
+ char insecure[16];
+ char hmac[128];
+ int ret = 0;
+
+ insecure[0] = '\0';
+
+ if (target->conf.http.tls) {
+ if (strlcpy(proto, "https://", sizeof(proto)) >=
+ sizeof(proto)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "http notification protocol too long");
+ }
+ } else {
+ if (strlcpy(proto, "http://", sizeof(proto)) >=
+ sizeof(proto)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "http notification protocol too long");
+ }
+
+ if (target->conf.http.user && target->conf.http.password) {
+ if (strlcpy(insecure, " insecure", sizeof(insecure)) >=
+ sizeof(insecure)) {
+ return got_error_msg(GOT_ERR_NO_SPACE, "http "
+ "notification insecure keyword too long");
+ }
+ }
+ }
+
+ if (target->conf.http.port) {
+ ret = snprintf(port, sizeof(port), ":%s",
+ target->conf.http.port);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(port)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "notification http port too long");
+ }
+ } else
+ port[0] = '\0';
+
+ if (target->conf.http.user && target->conf.http.password) {
+ ret = snprintf(label, sizeof(label), "basic%d", idx);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(label)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "basic auth label too long");
+ }
+
+ ret = snprintf(auth, sizeof(auth), " auth %s", label);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(label)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "http notification auth too long");
+ }
+ } else
+ auth[0] = '\0';
+
+ if (target->conf.http.hmac_secret) {
+ ret = snprintf(label, sizeof(label), "hmac%d", idx);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(label)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "notification http hmac label too long");
+ }
+
+ ret = snprintf(hmac, sizeof(hmac), " hmac %s", label);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(label)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "http notification hmac too long");
+ }
+ } else
+ hmac[0] = '\0';
+
+ ret = dprintf(gotd_conf_tmpfd, "\t\turl \"%s%s%s/%s\"%s%s%s\n",
+ proto, target->conf.http.hostname, port,
+ target->conf.http.path, auth, insecure, hmac);
+ if (ret == -1)
+ return got_error_from_errno2("dprintf", gotd_conf_tmppath);
+ if (ret != 10 + strlen(proto) + strlen(target->conf.http.hostname) +
+ strlen(port) + strlen(target->conf.http.path) + strlen(auth) +
+ strlen(insecure) + strlen(hmac)) {
+ return got_error_fmt(GOT_ERR_IO, "short write to %s",
+ gotd_conf_tmppath);
+ }
+
+ return NULL;
+}
+
+static const struct got_error *
+write_notification_targets(struct gotsys_repo *repo)
+{
+ const struct got_error *err = NULL;
+ struct got_pathlist_entry *pe;
+ struct gotsys_notification_target *target;
+ const char *opening = "notify {";
+ const char *closing = "}";
+ char *namespace = NULL;
+ int ret = 0, i;
+
+ if (STAILQ_EMPTY(&repo->notification_targets))
+ return NULL;
+
+ ret = dprintf(gotd_conf_tmpfd, "\t%s\n", opening);
+ if (ret == -1)
+ return got_error_from_errno2("dprintf", gotd_conf_tmppath);
+ if (ret != 2 + strlen(opening))
+ return got_error_fmt(GOT_ERR_IO, "short write to %s",
+ gotd_conf_tmppath);
+
+ RB_FOREACH(pe, got_pathlist_head, &repo->notification_refs) {
err = refname_is_valid(pe->path);
if (err)
return err;
gotd_conf_tmppath);
}
+ RB_FOREACH(pe, got_pathlist_head, &repo->notification_ref_namespaces) {
+ namespace = strdup(pe->path);
+ if (namespace == NULL)
+ return got_error_from_errno("strdup");
+
+ got_path_strip_trailing_slashes(namespace);
+ err = refname_is_valid(namespace);
+ if (err)
+ goto done;
+
+ ret = dprintf(gotd_conf_tmpfd,
+ "\t\treference namespace \"%s\"\n", namespace);
+ if (ret == -1) {
+ err = got_error_from_errno2("dprintf",
+ gotd_conf_tmppath);
+ goto done;
+ }
+ if (ret != 25 + strlen(namespace)) {
+ err = got_error_fmt(GOT_ERR_IO, "short write to %s",
+ gotd_conf_tmppath);
+ goto done;
+ }
+ free(namespace);
+ namespace = NULL;
+ }
+
+ i = 0;
+ STAILQ_FOREACH(target, &repo->notification_targets, entry) {
+ i++;
+ switch (target->type) {
+ case GOTSYS_NOTIFICATION_VIA_EMAIL:
+ err = write_notification_target_email(target);
+ break;
+ case GOTSYS_NOTIFICATION_VIA_HTTP:
+ err = write_notification_target_http(target, i);
+ break;
+ default:
+ break;
+ }
+ }
+
ret = dprintf(gotd_conf_tmpfd, "\t%s\n", closing);
if (ret == -1)
return got_error_from_errno2("dprintf", gotd_conf_tmppath);
gotd_conf_tmppath);
done:
free(namespace);
+ return err;
+}
+
+static const struct got_error *
+write_repo_secrets(off_t *written, struct gotsys_repo *repo)
+{
+ struct gotsys_notification_target *target;
+ char label[32];
+ int ret = 0, i = 0;
+ size_t len;
+
+ STAILQ_FOREACH(target, &repo->notification_targets, entry) {
+ if (target->type != GOTSYS_NOTIFICATION_VIA_HTTP)
+ continue;
+
+ if (target->conf.http.user == NULL &&
+ target->conf.http.password == NULL &&
+ target->conf.http.hmac_secret == NULL)
+ continue;
+
+ i++;
+
+ if (target->conf.http.user && target->conf.http.password) {
+ ret = snprintf(label, sizeof(label), "basic%d", i);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(label)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "basic auth label too long");
+ }
+
+ ret = dprintf(gotd_secrets_tmpfd,
+ "auth %s user \"%s\" password \"%s\"\n", label,
+ target->conf.http.user, target->conf.http.password);
+ if (ret == -1)
+ return got_error_from_errno2("dprintf",
+ gotd_secrets_tmppath);
+ len = strlen(label) +
+ strlen(target->conf.http.user) +
+ strlen(target->conf.http.password);
+ if (ret != 26 + len) {
+ return got_error_fmt(GOT_ERR_IO,
+ "short write to %s", gotd_secrets_tmppath);
+ }
+ *written += ret;
+ }
+
+ if (target->conf.http.hmac_secret) {
+ ret = snprintf(label, sizeof(label), "hmac%d", i);
+ if (ret == -1)
+ return got_error_from_errno("snprintf");
+ if ((size_t)ret >= sizeof(label)) {
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "hmac secret label too long");
+ }
+ ret = dprintf(gotd_secrets_tmpfd, "hmac %s \"%s\"\n",
+ label, target->conf.http.hmac_secret);
+ if (ret == -1)
+ return got_error_from_errno2("dprintf",
+ gotd_secrets_tmppath);
+ len = strlen(label) +
+ strlen(target->conf.http.hmac_secret);
+ if (ret != 9 + len) {
+ return got_error_fmt(GOT_ERR_IO,
+ "short write to %s", gotd_secrets_tmppath);
+ }
+ *written += ret;
+ }
+ }
+
return NULL;
}
static const struct got_error *
+prepare_gotd_secrets(void)
+{
+ const struct got_error *err = NULL;
+ struct gotsys_repo *repo;
+ off_t written = 0;
+
+ if (ftruncate(gotd_secrets_tmpfd, 0) == -1)
+ return got_error_from_errno("ftruncate");
+
+ TAILQ_FOREACH(repo, &gotsysconf.repos, entry) {
+ err = write_repo_secrets(&written, repo);
+ if (err)
+ return err;
+ }
+
+ if (written == 0) {
+ if (unlink(gotd_secrets_tmppath) == -1) {
+ return got_error_from_errno2("unlink",
+ gotd_secrets_tmppath);
+ }
+ free(gotd_secrets_tmppath);
+ gotd_secrets_tmppath = NULL;
+
+ if (close(gotd_secrets_tmpfd) == -1)
+ return got_error_from_errno("close");
+ gotd_secrets_tmpfd = -1;
+ }
+
+ return NULL;
+}
+
+static const struct got_error *
write_gotd_conf(void)
{
const struct got_error *err = NULL;
if (err)
return err;
+ err = write_notification_targets(repo);
+ if (err)
+ return err;
+
ret = dprintf(gotd_conf_tmpfd, "}\n");
if (ret == -1)
return got_error_from_errno2("dprintf",
}
}
+ if (gotd_secrets_tmppath != NULL && gotd_secrets_tmpfd != -1) {
+ if (fchmod(gotd_secrets_tmpfd, 0600) == -1) {
+ return got_error_from_errno_fmt("chmod 0600 %s",
+ gotd_secrets_tmppath);
+ }
+
+ if (rename(gotd_secrets_tmppath, GOTD_SECRETS_PATH) == -1) {
+ return got_error_from_errno_fmt("rename %s to %s",
+ gotd_conf_tmppath, GOTD_SECRETS_PATH);
+ }
+
+ free(gotd_secrets_tmppath);
+ gotd_secrets_tmppath = NULL;
+ }
+
if (fchmod(gotd_conf_tmpfd, 0644) == -1) {
return got_error_from_errno_fmt("chmod 0644 %s",
gotd_conf_tmppath);
writeconf_state);
break;
}
- repo_cur = NULL;
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS:
+ if (repo_cur == NULL ||
+ notif_refs_cur != NULL ||
+ num_notif_refs_needed != 0 ||
+ writeconf_state != WRITECONF_STATE_EXPECT_REPOS) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ break;
+ }
+ err = gotsys_imsg_recv_pathlist(&npaths, &imsg);
+ if (err)
+ break;
+ notif_refs_cur = &repo_cur->notification_refs;
+ num_notif_refs_cur = &repo_cur->num_notification_refs;
+ num_notif_refs_needed = npaths;
+ num_notif_refs_received = 0;
break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES:
+ if (repo_cur == NULL ||
+ notif_refs_cur != NULL ||
+ num_notif_refs_needed != 0 ||
+ writeconf_state != WRITECONF_STATE_EXPECT_REPOS) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ break;
+ }
+ err = gotsys_imsg_recv_pathlist(&npaths, &imsg);
+ if (err)
+ break;
+ notif_refs_cur =
+ &repo_cur->notification_ref_namespaces;
+ num_notif_refs_cur =
+ &repo_cur->num_notification_ref_namespaces;
+ num_notif_refs_needed = npaths;
+ num_notif_refs_received = 0;
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_ELEM:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_ELEM:
+ if (notif_refs_cur == NULL ||
+ num_notif_refs_cur == NULL ||
+ num_notif_refs_needed == 0 ||
+ num_notif_refs_received >=
+ num_notif_refs_needed ||
+ writeconf_state != WRITECONF_STATE_EXPECT_REPOS) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ break;
+ }
+ err = gotsys_imsg_recv_pathlist_elem(&imsg,
+ notif_refs_cur);
+ if (err)
+ break;
+ if (++num_notif_refs_received >=
+ num_notif_refs_needed) {
+ notif_refs_cur = NULL;
+ *num_notif_refs_cur = num_notif_refs_received;
+ num_notif_refs_needed = 0;
+ num_notif_refs_received = 0;
+ }
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_DONE:
+ if (repo_cur == NULL ||
+ num_notif_refs_needed != 0 ||
+ notif_refs_cur != NULL ||
+ writeconf_state != WRITECONF_STATE_EXPECT_REPOS) {
+ err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
+ "received unexpected imsg %d while in "
+ "state %d\n", imsg.hdr.type,
+ writeconf_state);
+ break;
+ }
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_DONE:
+ if (repo_cur == NULL ||
+ num_notif_refs_needed != 0 ||
+ notif_refs_cur != NULL ||
+ writeconf_state != WRITECONF_STATE_EXPECT_REPOS) {
+ err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
+ "received unexpected imsg %d while in "
+ "state %d\n", imsg.hdr.type,
+ writeconf_state);
+ break;
+ }
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_EMAIL: {
+ struct gotsys_notification_target *target;
+
+ if (repo_cur == NULL ||
+ num_notif_refs_needed != 0 ||
+ notif_refs_cur != NULL ||
+ writeconf_state != WRITECONF_STATE_EXPECT_REPOS) {
+ err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
+ "received unexpected imsg %d while in "
+ "state %d\n", imsg.hdr.type,
+ writeconf_state);
+ break;
+ }
+
+ err = gotsys_imsg_recv_notification_target_email(NULL,
+ &target, &imsg);
+ if (err)
+ break;
+ STAILQ_INSERT_TAIL(&repo_cur->notification_targets,
+ target, entry);
+ break;
+ }
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_HTTP: {
+ struct gotsys_notification_target *target;
+
+ if (repo_cur == NULL ||
+ num_notif_refs_needed != 0 ||
+ notif_refs_cur != NULL ||
+ writeconf_state != WRITECONF_STATE_EXPECT_REPOS) {
+ err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
+ "received unexpected imsg %d while in "
+ "state %d\n", imsg.hdr.type,
+ writeconf_state);
+ break;
+ }
+
+ err = gotsys_imsg_recv_notification_target_http(NULL,
+ &target, &imsg);
+ if (err)
+ break;
+ STAILQ_INSERT_TAIL(&repo_cur->notification_targets,
+ target, entry);
+ break;
+ }
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGETS_DONE:
+ break;
case GOTSYSD_IMSG_SYSCONF_REPOS_DONE:
if (writeconf_state != WRITECONF_STATE_EXPECT_REPOS) {
err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
}
repo_cur = NULL;
writeconf_state = WRITECONF_STATE_WRITE_CONF;
+ err = prepare_gotd_secrets();
+ if (err)
+ break;
err = write_gotd_conf();
if (err)
break;
GOTD_CONF_PATH, "");
if (err)
goto done;
+ err = got_opentemp_named_fd(&gotd_secrets_tmppath, &gotd_secrets_tmpfd,
+ GOTD_CONF_PATH, "");
+ if (err)
+ goto done;
#ifndef PROFILE
if (pledge("stdio rpath wpath cpath fattr chown unveil", NULL) == -1) {
err = got_error_from_errno("pledge");
goto done;
}
+ if (unveil(gotd_secrets_tmppath, "rwc") == -1) {
+ err = got_error_from_errno2("unveil rwc", gotd_secrets_tmppath);
+ goto done;
+ }
+
if (unveil(GOTD_CONF_PATH, "rwc") == -1) {
err = got_error_from_errno2("unveil rwc", GOTD_CONF_PATH);
goto done;
}
+ if (unveil(GOTD_SECRETS_PATH, "rwc") == -1) {
+ err = got_error_from_errno2("unveil rwc", GOTD_SECRETS_PATH);
+ goto done;
+ }
+
if (unveil(NULL, NULL) == -1) {
err = got_error_from_errno("unveil");
goto done;
if (gotd_conf_tmppath && unlink(gotd_conf_tmppath) == -1 && err == NULL)
err = got_error_from_errno2("unlink", gotd_conf_tmppath);
free(gotd_conf_tmppath);
+ if (gotd_secrets_tmppath && unlink(gotd_secrets_tmppath) == -1 &&
+ err == NULL)
+ err = got_error_from_errno2("unlink", gotd_secrets_tmppath);
+ free(gotd_secrets_tmppath);
if (close(GOTSYSD_FILENO_MSG_PIPE) == -1 && err == NULL)
err = got_error_from_errno("close");
if (gotd_conf_tmpfd != -1 && close(gotd_conf_tmpfd) == -1 &&
err == NULL)
err = got_error_from_errno("close");
+ if (gotd_secrets_tmpfd != -1 && close(gotd_secrets_tmpfd) == -1 &&
+ err == NULL)
+ err = got_error_from_errno("close");
if (err)
gotsysd_imsg_send_error(&iev.ibuf, 0, 0, err);
imsgbuf_clear(&iev.ibuf);
blob - 8d18a175cf0653c1c2bc59bfdeb57c4fc6a92728
blob + 0c5a12d1a927caa5c343c9f24680f5e65d37be18
--- gotsysd/sysconf.c
+++ gotsysd/sysconf.c
size_t nprotected_refs_needed;
size_t nprotected_refs_received;
struct gotsys_access_rule_list *global_repo_access_rules;
+ struct got_pathlist_head *notif_refs_cur;
+ size_t *num_notif_refs_cur;
+ size_t num_notif_refs_needed;
+ size_t num_notif_refs_received;
} gotsysd_sysconf;
static struct gotsys_conf gotsysconf;
break;
}
log_debug("done receiving protected refs");
- gotsysd_sysconf.repo_cur = NULL;
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS:
+ if (gotsysd_sysconf.repo_cur == NULL ||
+ gotsysd_sysconf.notif_refs_cur != NULL ||
+ gotsysd_sysconf.num_notif_refs_needed != 0 ||
+ gotsysd_sysconf.state !=
+ SYSCONF_STATE_EXPECT_REPOS) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ break;
+ }
+ err = gotsys_imsg_recv_pathlist(&npaths, &imsg);
+ if (err)
+ break;
+ gotsysd_sysconf.notif_refs_cur =
+ &gotsysd_sysconf.repo_cur->notification_refs;
+ gotsysd_sysconf.num_notif_refs_cur =
+ &gotsysd_sysconf.repo_cur->num_notification_refs;
+ gotsysd_sysconf.num_notif_refs_needed = npaths;
+ gotsysd_sysconf.num_notif_refs_received = 0;
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES:
+ if (gotsysd_sysconf.repo_cur == NULL ||
+ gotsysd_sysconf.notif_refs_cur != NULL ||
+ gotsysd_sysconf.num_notif_refs_needed != 0 ||
+ gotsysd_sysconf.state !=
+ SYSCONF_STATE_EXPECT_REPOS) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ break;
+ }
+ err = gotsys_imsg_recv_pathlist(&npaths, &imsg);
+ if (err)
+ break;
+ gotsysd_sysconf.notif_refs_cur =
+ &gotsysd_sysconf.repo_cur->notification_ref_namespaces;
+ gotsysd_sysconf.num_notif_refs_cur =
+ &gotsysd_sysconf.repo_cur->num_notification_ref_namespaces;
+ gotsysd_sysconf.num_notif_refs_needed = npaths;
+ gotsysd_sysconf.num_notif_refs_received = 0;
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_ELEM:
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_ELEM:
+ if (gotsysd_sysconf.notif_refs_cur == NULL ||
+ gotsysd_sysconf.num_notif_refs_cur == NULL ||
+ gotsysd_sysconf.num_notif_refs_needed == 0 ||
+ gotsysd_sysconf.num_notif_refs_received >=
+ gotsysd_sysconf.num_notif_refs_needed ||
+ gotsysd_sysconf.state !=
+ SYSCONF_STATE_EXPECT_REPOS) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ break;
+ }
+ err = gotsys_imsg_recv_pathlist_elem(&imsg,
+ gotsysd_sysconf.notif_refs_cur);
+ if (err)
+ break;
+ if (++gotsysd_sysconf.num_notif_refs_received >=
+ gotsysd_sysconf.num_notif_refs_needed) {
+ gotsysd_sysconf.notif_refs_cur = NULL;
+ *gotsysd_sysconf.num_notif_refs_cur =
+ gotsysd_sysconf.num_notif_refs_received;
+ gotsysd_sysconf.num_notif_refs_needed = 0;
+ gotsysd_sysconf.num_notif_refs_received = 0;
+ }
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_DONE:
+ if (gotsysd_sysconf.repo_cur == NULL ||
+ gotsysd_sysconf.num_notif_refs_needed != 0 ||
+ gotsysd_sysconf.notif_refs_cur != NULL ||
+ gotsysd_sysconf.state !=
+ SYSCONF_STATE_EXPECT_REPOS) {
+ err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
+ "received unexpected imsg %d while in "
+ "state %d\n", imsg.hdr.type,
+ gotsysd_sysconf.state);
+ break;
+ }
+ log_debug("done receiving notification refs");
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_DONE:
+ if (gotsysd_sysconf.repo_cur == NULL ||
+ gotsysd_sysconf.num_notif_refs_needed != 0 ||
+ gotsysd_sysconf.notif_refs_cur != NULL ||
+ gotsysd_sysconf.state !=
+ SYSCONF_STATE_EXPECT_REPOS) {
+ err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
+ "received unexpected imsg %d while in "
+ "state %d\n", imsg.hdr.type,
+ gotsysd_sysconf.state);
+ break;
+ }
+ log_debug("done receiving notification ref namespaces");
+ break;
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_EMAIL: {
+ struct gotsys_notification_target *target;
+
+ if (gotsysd_sysconf.repo_cur == NULL ||
+ gotsysd_sysconf.num_notif_refs_needed != 0 ||
+ gotsysd_sysconf.notif_refs_cur != NULL ||
+ gotsysd_sysconf.state !=
+ SYSCONF_STATE_EXPECT_REPOS) {
+ err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
+ "received unexpected imsg %d while in "
+ "state %d\n", imsg.hdr.type,
+ gotsysd_sysconf.state);
+ break;
+ }
+
+ err = gotsys_imsg_recv_notification_target_email(NULL,
+ &target, &imsg);
+ if (err)
+ break;
+ STAILQ_INSERT_TAIL(
+ &gotsysd_sysconf.repo_cur->notification_targets,
+ target, entry);
break;
+ }
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_HTTP: {
+ struct gotsys_notification_target *target;
+
+ if (gotsysd_sysconf.repo_cur == NULL ||
+ gotsysd_sysconf.num_notif_refs_needed != 0 ||
+ gotsysd_sysconf.notif_refs_cur != NULL ||
+ gotsysd_sysconf.state !=
+ SYSCONF_STATE_EXPECT_REPOS) {
+ err = got_error_fmt(GOT_ERR_PRIVSEP_MSG,
+ "received unexpected imsg %d while in "
+ "state %d\n", imsg.hdr.type,
+ gotsysd_sysconf.state);
+ break;
+ }
+
+ err = gotsys_imsg_recv_notification_target_http(NULL,
+ &target, &imsg);
+ if (err)
+ break;
+ STAILQ_INSERT_TAIL(
+ &gotsysd_sysconf.repo_cur->notification_targets,
+ target, entry);
+ break;
+ }
+ case GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGETS_DONE:
+ break;
case GOTSYSD_IMSG_SYSCONF_REPOS_DONE:
if (gotsysd_sysconf.state !=
SYSCONF_STATE_EXPECT_REPOS) {
blob - 58e4ea0fdbf1305b478ed52f32a261460972f9d7
blob + 08719ff3c688ae2778ff81d0668fb2e6efdb4d77
--- lib/gotsys_imsg.c
+++ lib/gotsys_imsg.c
if (gotsysd_imsg_compose_event(iev,
GOTSYSD_IMSG_SYSCONF_PROTECTED_REFS_DONE, 0, -1, NULL, 0) == -1)
return got_error_from_errno("gotsysd_imsg_compose_event");
+
+ return NULL;
+}
+
+static const struct got_error *
+send_notification_target_email(struct gotsysd_imsgev *iev,
+ const char *repo_name, struct gotsys_notification_target *target)
+{
+ struct gotsysd_imsg_notitfication_target_email itarget;
+ struct ibuf *wbuf = NULL;
+
+ memset(&itarget, 0, sizeof(itarget));
+
+ if (target->conf.email.sender)
+ itarget.sender_len = strlen(target->conf.email.sender);
+ if (target->conf.email.recipient)
+ itarget.recipient_len = strlen(target->conf.email.recipient);
+ if (target->conf.email.responder)
+ itarget.responder_len = strlen(target->conf.email.responder);
+ if (target->conf.email.hostname)
+ itarget.hostname_len = strlen(target->conf.email.hostname);
+ if (target->conf.email.port)
+ itarget.port_len = strlen(target->conf.email.port);
+ itarget.repo_name_len = strlen(repo_name);
+
+ wbuf = imsg_create(&iev->ibuf,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_EMAIL,
+ 0, 0, sizeof(itarget) + itarget.sender_len + itarget.recipient_len +
+ itarget.responder_len + itarget.hostname_len + itarget.port_len +
+ itarget.repo_name_len);
+ if (wbuf == NULL) {
+ return got_error_from_errno("imsg_create "
+ "NOTIFICATION_TARGET_EMAIL");
+ }
+
+ if (imsg_add(wbuf, &itarget, sizeof(itarget)) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_EMAIL");
+ }
+ if (target->conf.email.sender) {
+ if (imsg_add(wbuf, target->conf.email.sender,
+ itarget.sender_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_EMAIL");
+ }
+ }
+
+ if (target->conf.email.recipient) {
+ if (imsg_add(wbuf, target->conf.email.recipient,
+ itarget.recipient_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_EMAIL");
+ }
+ }
+ if (target->conf.email.responder) {
+ if (imsg_add(wbuf, target->conf.email.responder,
+ itarget.responder_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_EMAIL");
+ }
+ }
+ if (target->conf.email.hostname) {
+ if (imsg_add(wbuf, target->conf.email.hostname,
+ itarget.hostname_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_EMAIL");
+ }
+ }
+ if (target->conf.email.port) {
+ if (imsg_add(wbuf, target->conf.email.port,
+ itarget.port_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_EMAIL");
+ }
+ }
+ if (imsg_add(wbuf, repo_name, itarget.repo_name_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_EMAIL");
+ }
+ imsg_close(&iev->ibuf, wbuf);
+ gotsysd_imsg_event_add(iev);
return NULL;
}
static const struct got_error *
+send_notification_target_http(struct gotsysd_imsgev *iev, const char *repo_name,
+ struct gotsys_notification_target *target)
+{
+ struct gotsysd_imsg_notitfication_target_http itarget;
+ struct ibuf *wbuf = NULL;
+
+ memset(&itarget, 0, sizeof(itarget));
+
+ itarget.tls = target->conf.http.tls;
+ itarget.hostname_len = strlen(target->conf.http.hostname);
+ itarget.port_len = strlen(target->conf.http.port);
+ itarget.path_len = strlen(target->conf.http.path);
+ if (target->conf.http.user)
+ itarget.user_len = strlen(target->conf.http.user);
+ if (target->conf.http.password)
+ itarget.password_len = strlen(target->conf.http.password);
+ if (target->conf.http.hmac_secret)
+ itarget.hmac_len = strlen(target->conf.http.hmac_secret);
+ itarget.repo_name_len = strlen(repo_name);
+
+ wbuf = imsg_create(&iev->ibuf,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGET_HTTP,
+ 0, 0, sizeof(itarget) + itarget.hostname_len + itarget.port_len +
+ itarget.path_len + itarget.user_len + itarget.password_len +
+ itarget.hmac_len + itarget.repo_name_len);
+ if (wbuf == NULL) {
+ return got_error_from_errno("imsg_create "
+ "NOTIFICATION_TARGET_HTTP");
+ }
+
+ if (imsg_add(wbuf, &itarget, sizeof(itarget)) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_HTTP");
+ }
+ if (imsg_add(wbuf, target->conf.http.hostname,
+ itarget.hostname_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_HTTP");
+ }
+ if (imsg_add(wbuf, target->conf.http.port,
+ itarget.port_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_HTTP");
+ }
+ if (imsg_add(wbuf, target->conf.http.path,
+ itarget.path_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_HTTP");
+ }
+
+ if (target->conf.http.user) {
+ if (imsg_add(wbuf, target->conf.http.user, itarget.user_len) == -1)
+ return got_error_from_errno("imsg_add NOTIFICATION_TARGET_HTTP");
+ }
+ if (target->conf.http.password) {
+ if (imsg_add(wbuf, target->conf.http.password,
+ itarget.password_len) == -1)
+ return got_error_from_errno("imsg_add NOTIFICATION_TARGET_HTTP");
+ }
+ if (target->conf.http.hmac_secret) {
+ if (imsg_add(wbuf, target->conf.http.hmac_secret,
+ itarget.hmac_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_HTTP");
+ }
+ }
+ if (imsg_add(wbuf, repo_name, itarget.repo_name_len) == -1) {
+ return got_error_from_errno("imsg_add "
+ "NOTIFICATION_TARGET_HTTP");
+ }
+
+ imsg_close(&iev->ibuf, wbuf);
+ gotsysd_imsg_event_add(iev);
+ return NULL;
+}
+
+static const struct got_error *
+send_notification_target(struct gotsysd_imsgev *iev, const char *repo_name,
+ struct gotsys_notification_target *target)
+{
+ const struct got_error *err = NULL;
+
+ switch (target->type) {
+ case GOTSYS_NOTIFICATION_VIA_EMAIL:
+ err = send_notification_target_email(iev, repo_name, target);
+ break;
+ case GOTSYS_NOTIFICATION_VIA_HTTP:
+ err = send_notification_target_http(iev, repo_name, target);
+ break;
+ default:
+ break;
+ }
+
+ return err;
+}
+
+static const struct got_error *
+send_notification_targets(struct gotsysd_imsgev *iev, const char *repo_name,
+ struct gotsys_notification_targets *targets)
+{
+ const struct got_error *err = NULL;
+ struct gotsys_notification_target *target;
+
+ STAILQ_FOREACH(target, targets, entry) {
+ err = send_notification_target(iev, repo_name, target);
+ if (err)
+ return err;
+ }
+
+ if (gotsysd_imsg_compose_event(iev,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_TARGETS_DONE, 0, -1, NULL, 0) == -1)
+ return got_error_from_errno("gotsysd_imsg_compose_event");
+
+ return NULL;
+}
+
+static const struct got_error *
+send_notification_config(struct gotsysd_imsgev *iev, struct gotsys_repo *repo)
+{
+ const struct got_error *err = NULL;
+ struct got_pathlist_entry *pe;
+ struct gotsysd_imsg_pathlist ilist;
+
+ memset(&ilist, 0, sizeof(ilist));
+
+ ilist.nelem = repo->num_notification_refs;
+ if (ilist.nelem > 0) {
+ if (gotsysd_imsg_compose_event(iev,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS, 0, -1,
+ &ilist, sizeof(ilist)) == -1) {
+ return got_error_from_errno("imsg compose "
+ "NOTIFICATION_REFS");
+ }
+
+ RB_FOREACH(pe, got_pathlist_head, &repo->notification_refs) {
+ err = send_pathlist_elem(iev, pe->path,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_ELEM);
+ if (err)
+ return err;
+ }
+ }
+
+ if (gotsysd_imsg_compose_event(iev,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REFS_DONE, 0, -1, NULL, 0) == -1)
+ return got_error_from_errno("gotsysd_imsg_compose_event");
+
+ ilist.nelem = repo->num_notification_ref_namespaces;
+ if (ilist.nelem > 0) {
+ if (gotsysd_imsg_compose_event(iev,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES, 0, -1,
+ &ilist, sizeof(ilist)) == -1) {
+ return got_error_from_errno("imsg compose "
+ "NOTIFICATION_REF_NAMESPACES");
+ }
+
+ RB_FOREACH(pe, got_pathlist_head,
+ &repo->notification_ref_namespaces) {
+ err = send_pathlist_elem(iev, pe->path,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_ELEM);
+ if (err)
+ return err;
+ }
+ }
+
+ if (gotsysd_imsg_compose_event(iev,
+ GOTSYSD_IMSG_SYSCONF_NOTIFICATION_REF_NAMESPACES_DONE, 0, -1, NULL, 0) == -1)
+ return got_error_from_errno("gotsysd_imsg_compose_event");
+
+ return send_notification_targets(iev, repo->name, &repo->notification_targets);
+}
+
+static const struct got_error *
send_repo(struct gotsysd_imsgev *iev, struct gotsys_repo *repo)
{
const struct got_error *err;
if (err)
return err;
- /* TODO: send notification config */
+ err = send_notification_config(iev, repo);
+ if (err)
+ return err;
return NULL;
}
free(path);
return err;
}
+
+const struct got_error *
+gotsys_imsg_recv_notification_target_email(char **repo_name,
+ struct gotsys_notification_target **new_target, struct imsg *imsg)
+{
+ const struct got_error *err = NULL;
+ struct gotsysd_imsg_notitfication_target_email itarget;
+ struct gotsys_notification_target *target;
+ size_t datalen;
+
+ if (repo_name)
+ *repo_name = NULL;
+ *new_target = NULL;
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen < sizeof(itarget))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&itarget, imsg->data, sizeof(itarget));
+
+ if (datalen != sizeof(itarget) + itarget.sender_len +
+ itarget.recipient_len + itarget.responder_len +
+ itarget.hostname_len + itarget.port_len + itarget.repo_name_len)
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ if (itarget.recipient_len == 0 || itarget.repo_name_len == 0)
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+
+ target = calloc(1, sizeof(*target));
+ if (target == NULL)
+ return got_error_from_errno("calloc");
+
+ target->type = GOTSYS_NOTIFICATION_VIA_EMAIL;
+
+ if (itarget.sender_len) {
+ target->conf.email.sender = strndup(imsg->data +
+ sizeof(itarget), itarget.sender_len);
+ if (target->conf.email.sender == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.email.sender) != itarget.sender_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+ }
+
+ target->conf.email.recipient = strndup(imsg->data + sizeof(itarget) +
+ itarget.sender_len, itarget.recipient_len);
+ if (target->conf.email.recipient == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.email.recipient) != itarget.recipient_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+
+ if (itarget.responder_len) {
+ target->conf.email.responder = strndup(imsg->data +
+ sizeof(itarget) + itarget.sender_len +
+ itarget.recipient_len, itarget.responder_len);
+ if (target->conf.email.responder == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.email.responder) !=
+ itarget.responder_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+ }
+
+ if (itarget.hostname_len) {
+ target->conf.email.hostname = strndup(imsg->data +
+ sizeof(itarget) + itarget.sender_len +
+ itarget.recipient_len + itarget.responder_len,
+ itarget.hostname_len);
+ if (target->conf.email.hostname == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.email.hostname) !=
+ itarget.hostname_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+ }
+
+ if (itarget.port_len) {
+ target->conf.email.port = strndup(imsg->data +
+ sizeof(itarget) + itarget.sender_len +
+ itarget.recipient_len + itarget.responder_len +
+ itarget.hostname_len, itarget.port_len);
+ if (target->conf.email.port == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.email.port) != itarget.port_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+ }
+
+ if (repo_name) {
+ *repo_name = strndup(imsg->data +
+ sizeof(itarget) + itarget.sender_len +
+ itarget.recipient_len + itarget.responder_len +
+ itarget.hostname_len + itarget.port_len,
+ itarget.repo_name_len);
+ if (*repo_name == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(*repo_name) != itarget.repo_name_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ free(*repo_name);
+ *repo_name = NULL;
+ goto done;
+ }
+ }
+
+ *new_target = target;
+done:
+ if (err)
+ gotsys_notification_target_free(target);
+ return err;
+}
+
+const struct got_error *
+gotsys_imsg_recv_notification_target_http(char **repo_name,
+ struct gotsys_notification_target **new_target, struct imsg *imsg)
+{
+ const struct got_error *err = NULL;
+ struct gotsysd_imsg_notitfication_target_http itarget;
+ struct gotsys_notification_target *target;
+ size_t datalen;
+
+ if (repo_name)
+ *repo_name = NULL;
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen < sizeof(itarget))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&itarget, imsg->data, sizeof(itarget));
+
+ if (datalen != sizeof(itarget) + itarget.hostname_len +
+ itarget.port_len + itarget.path_len + itarget.user_len +
+ itarget.password_len + itarget.hmac_len + itarget.repo_name_len)
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+
+ if (itarget.hostname_len == 0 || itarget.port_len == 0 ||
+ itarget.path_len == 0 || itarget.repo_name_len == 0)
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+
+ target = calloc(1, sizeof(*target));
+ if (target == NULL)
+ return got_error_from_errno("calloc");
+
+ target->type = GOTSYS_NOTIFICATION_VIA_HTTP;
+
+ target->conf.http.tls = itarget.tls;
+
+ target->conf.http.hostname = strndup(imsg->data +
+ sizeof(itarget), itarget.hostname_len);
+ if (target->conf.http.hostname == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.http.hostname) != itarget.hostname_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+
+ target->conf.http.port = strndup(imsg->data + sizeof(itarget) +
+ itarget.hostname_len, itarget.port_len);
+ if (target->conf.http.port == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.http.port) != itarget.port_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+
+ target->conf.http.path = strndup(imsg->data +
+ sizeof(itarget) + itarget.hostname_len + itarget.port_len,
+ itarget.path_len);
+ if (target->conf.http.path == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.http.path) != itarget.path_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+
+ if (itarget.user_len) {
+ target->conf.http.user = strndup(imsg->data +
+ sizeof(itarget) + itarget.hostname_len +
+ itarget.port_len + itarget.path_len,
+ itarget.user_len);
+ if (target->conf.http.user == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.http.user) != itarget.user_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+ }
+
+ if (itarget.password_len) {
+ target->conf.http.password = strndup(imsg->data +
+ sizeof(itarget) + itarget.hostname_len +
+ itarget.port_len + itarget.path_len + itarget.user_len,
+ itarget.password_len);
+ if (target->conf.http.password == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.http.password) !=
+ itarget.password_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+ }
+
+ if (itarget.hmac_len) {
+ target->conf.http.hmac_secret = strndup(imsg->data +
+ sizeof(itarget) + itarget.hostname_len +
+ itarget.port_len + itarget.path_len +
+ itarget.user_len + itarget.password_len,
+ itarget.hmac_len);
+ if (target->conf.http.hmac_secret == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(target->conf.http.hmac_secret) != itarget.hmac_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ goto done;
+ }
+ }
+
+ if (repo_name) {
+ *repo_name = strndup(imsg->data +
+ sizeof(itarget) + itarget.hostname_len +
+ itarget.port_len + itarget.path_len +
+ itarget.user_len + itarget.password_len +
+ itarget.hmac_len, itarget.repo_name_len);
+ if (*repo_name == NULL) {
+ err = got_error_from_errno("strndup");
+ goto done;
+ }
+ if (strlen(*repo_name) != itarget.repo_name_len) {
+ err = got_error(GOT_ERR_PRIVSEP_LEN);
+ free(*repo_name);
+ *repo_name = NULL;
+ goto done;
+ }
+ }
+
+ *new_target = target;
+done:
+ if (err)
+ gotsys_notification_target_free(target);
+ return err;
+}
blob - 78e4fcdc0abf1630c98b1706dda411909c331c5f
blob + 2f11f478c0f33499ceb77104e13e6862d885be25
--- regress/gotsysd/Makefile
+++ regress/gotsysd/Makefile
GOTSYSD_VM_PASSWD_FILE=gotsysd_vm_passwd
GOTD_CONF=gotd.conf
GOTD_UID=501 # /usr/ports/infrastructure/db/user.list
+GOTD_USER=_gotd
GOTSYSD_CONF=gotsysd.conf
GOTSYSD_UID=600 # /usr/ports/infrastructure/db/user.list
GOTSYS_CONF=gotsys.conf
GOT_CONF=got.conf
GOTSYS_REPO=gotsys.git
GOTWEBD_UID=593 # /usr/ports/infrastructure/db/user.list
+GOTSYSD_TEST_SMTP_PORT=2525
+GOTSYSD_TEST_HTTP_PORT=8000
+GOTSYSD_TEST_HMAC_SECRET!=openssl rand -base64 32
GOTSYSD_TEST_USER?=${DOAS_USER}
.if empty(GOTSYSD_TEST_USER)
GOTSYSD_SSH_PUBKEY=${GOTSYSD_SSH_PUBKEY} \
GOTSYS_REPO=${GOTSYS_REPO} \
HOME=$(GOTSYSD_TEST_USER_HOME) \
- PATH=$(GOTSYSD_TEST_USER_HOME)/bin:$(PATH)
+ PATH=$(GOTSYSD_TEST_USER_HOME)/bin:$(PATH) \
+ GOTD_USER=${GOTD_USER} \
+ GOTSYSD_TEST_SMTP_PORT=${GOTSYSD_TEST_SMTP_PORT} \
+ GOTSYSD_TEST_HTTP_PORT=${GOTSYSD_TEST_HTTP_PORT} \
+ GOTSYSD_TEST_HMAC_SECRET=${GOTSYSD_TEST_HMAC_SECRET}
UNPRIV=su -m ${GOTSYSD_TEST_USER} -c
VMID=`vmctl status ${GOTSYSD_VM_NAME} | tail -n1 | \
awk '{print $$1}'`; \
VMIP="100.64.$$VMID.3"; \
- ${UNPRIV} "env ${GOTSYSD_TEST_ENV} VMIP=$${VMIP} sh ./test_gotsysd.sh"
+ GWIP="100.64.$$VMID.2"; \
+ ${UNPRIV} "env ${GOTSYSD_TEST_ENV} VMIP=$${VMIP} GWIP=$${GWIP} \
+ sh ./test_gotsysd.sh"
.include <bsd.regress.mk>
blob - d8a6a0669ccae57aaf4c5fa85f67acd017b0f6be
blob + dece9a715ab4b1fb2ed8745339a3cf7f77e76655
--- regress/gotsysd/test_gotsysd.sh
+++ regress/gotsysd/test_gotsysd.sh
return 1
fi
done
+
+ # Undo gotsys.conf override
+ ssh -q -i ${GOTSYSD_SSH_KEY} root@${VMIP} 'rm -f /etc/gotsysd.conf'
+
+ # Restart gotsysd (XXX need a better way to do this...)
+ ssh -q -i ${GOTSYSD_SSH_KEY} root@${VMIP} 'pkill -xf /usr/local/sbin/gotsysd'
+ sleep 1
+ ssh -q -i ${GOTSYSD_SSH_KEY} root@${VMIP} '/usr/local/sbin/gotsysd -vvv'
+ sleep 1
+ ssh -q -i ${GOTSYSD_SSH_KEY} root@${VMIP} 'gotsys apply -w' > /dev/null
test_done "$testroot" "$ret"
}
+# flan:password encoded in base64
+AUTH="ZmxhbjpwYXNzd29yZA=="
+
+test_http_notification() {
+ local testroot=`test_init http_notification 1`
+
+ got checkout -q $testroot/${GOTSYS_REPO} $testroot/wt >/dev/null
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got checkout failed unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ crypted_vm_pw=`echo ${GOTSYSD_VM_PASSWORD} | encrypt | tr -d '\n'`
+ crypted_pw=`echo ${GOTSYSD_DEV_PASSWORD} | encrypt | tr -d '\n'`
+ sshkey=`cat ${GOTSYSD_SSH_PUBKEY}`
+ cat > ${testroot}/wt/gotsys.conf <<EOF
+group slackers
+
+user ${GOTSYSD_TEST_USER} {
+ password "${crypted_vm_pw}"
+ authorized key ${sshkey}
+}
+user ${GOTSYSD_DEV_USER} {
+ password "${crypted_pw}"
+ authorized key ${sshkey}
+}
+repository gotsys.git {
+ permit rw ${GOTSYSD_TEST_USER}
+ permit rw ${GOTSYSD_DEV_USER}
+
+ notify url "http://${GWIP}:${GOTSYSD_TEST_HTTP_PORT}/" user flan password "password" insecure
+}
+EOF
+ (cd ${testroot}/wt && got commit -m "send http notifications" \
+ >/dev/null)
+ local commit_id=`git_show_head $testroot/${GOTSYS_REPO}`
+
+ got send -q -i ${GOTSYSD_SSH_KEY} -r ${testroot}/${GOTSYS_REPO}
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got send failed unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ # Wait for gotsysd to apply the new configuration.
+ echo "$commit_id" > $testroot/stdout.expected
+ for i in 1 2 3 4 5; do
+ sleep 1
+ ssh -i ${GOTSYSD_SSH_KEY} root@${VMIP} \
+ cat /var/db/gotsysd/commit > $testroot/stdout
+ if cmp -s $testroot/stdout.expected $testroot/stdout; then
+ break;
+ fi
+ done
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "gotsysd failed to apply configuration" >&2
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ cat > ${testroot}/wt/gotsys.conf <<EOF
+group slackers
+
+user ${GOTSYSD_TEST_USER} {
+ password "${crypted_vm_pw}"
+ authorized key ${sshkey}
+}
+
+user ${GOTSYSD_DEV_USER} {
+ password "${crypted_pw}"
+ authorized key ${sshkey}
+}
+
+repository gotsys.git {
+ permit rw ${GOTSYSD_TEST_USER}
+ permit rw ${GOTSYSD_DEV_USER}
+
+ notify url "http://${GWIP}:${GOTSYSD_TEST_HTTP_PORT}/" user flan password "password" insecure
+}
+EOF
+
+ (cd ${testroot}/wt && got commit -m "whitespace changes" >/dev/null)
+ local commit_id=`git_show_head $testroot/${GOTSYS_REPO}`
+ local author_time=`git_show_author_time $testroot/${GOTSYS_REPO}`
+
+ timeout 5 ./http-server -a $AUTH -l "$GWIP" \
+ -p "$GOTSYSD_TEST_HTTP_PORT" > $testroot/stdout &
+
+ sleep 1 # server starts up
+
+ got send -q -i ${GOTSYSD_SSH_KEY} -r ${testroot}/${GOTSYS_REPO}
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got send failed unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ wait %1 # wait for the http "server"
+
+ echo -n > "$testroot/stdout.expected"
+ ed -s "$testroot/stdout.expected" <<-EOF
+ a
+ {"notifications":[{
+ "type":"commit",
+ "short":false,
+ "repo":"gotsys.git",
+ "authenticated_user":"${GOTSYSD_TEST_USER}",
+ "id":"$commit_id",
+ "author":{
+ "full":"$GOT_AUTHOR",
+ "name":"$GIT_AUTHOR_NAME",
+ "mail":"$GIT_AUTHOR_EMAIL",
+ "user":"$GOT_AUTHOR_11"
+ },
+ "committer":{
+ "full":"$GOT_AUTHOR",
+ "name":"$GIT_AUTHOR_NAME",
+ "mail":"$GIT_AUTHOR_EMAIL",
+ "user":"$GOT_AUTHOR_11"
+ },
+ "date":$author_time,
+ "short_message":"whitespace changes",
+ "message":"whitespace changes\n",
+ "diffstat":{
+ "files":[{
+ "action":"modified",
+ "file":"gotsys.conf",
+ "added":2,
+ "removed":0
+ }],
+ "total":{
+ "added":1,
+ "removed":2
+ }
+ }
+ }]}
+ .
+ ,j
+ w
+ EOF
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ test_done "$testroot" "$ret"
+}
+
+test_http_notification_hmac() {
+ local testroot=`test_init http_notification_hmac 1`
+
+ got checkout -q $testroot/${GOTSYS_REPO} $testroot/wt >/dev/null
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got checkout failed unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ crypted_vm_pw=`echo ${GOTSYSD_VM_PASSWORD} | encrypt | tr -d '\n'`
+ crypted_pw=`echo ${GOTSYSD_DEV_PASSWORD} | encrypt | tr -d '\n'`
+ sshkey=`cat ${GOTSYSD_SSH_PUBKEY}`
+ cat > ${testroot}/wt/gotsys.conf <<EOF
+group slackers
+
+user ${GOTSYSD_TEST_USER} {
+ password "${crypted_vm_pw}"
+ authorized key ${sshkey}
+}
+
+user ${GOTSYSD_DEV_USER} {
+ password "${crypted_pw}"
+ authorized key ${sshkey}
+}
+
+repository gotsys.git {
+ permit rw ${GOTSYSD_TEST_USER}
+ permit rw ${GOTSYSD_DEV_USER}
+
+ notify url "http://${GWIP}:${GOTSYSD_TEST_HTTP_PORT}/" user flan password "password" insecure hmac "${GOTSYSD_TEST_HMAC_SECRET}"
+}
+EOF
+
+ (cd ${testroot}/wt && got commit -m "add hmac" >/dev/null)
+ local commit_id=`git_show_head $testroot/${GOTSYS_REPO}`
+
+ got send -q -i ${GOTSYSD_SSH_KEY} -r ${testroot}/${GOTSYS_REPO}
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got send failed unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ # Wait for gotsysd to apply the new configuration.
+ echo "$commit_id" > $testroot/stdout.expected
+ for i in 1 2 3 4 5; do
+ sleep 1
+ ssh -i ${GOTSYSD_SSH_KEY} root@${VMIP} \
+ cat /var/db/gotsysd/commit > $testroot/stdout
+ if cmp -s $testroot/stdout.expected $testroot/stdout; then
+ break;
+ fi
+ done
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "gotsysd failed to apply configuration" >&2
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ cat > ${testroot}/wt/gotsys.conf <<EOF
+group slackers
+
+user ${GOTSYSD_TEST_USER} {
+ password "${crypted_vm_pw}"
+ authorized key ${sshkey}
+}
+user ${GOTSYSD_DEV_USER} {
+ password "${crypted_pw}"
+ authorized key ${sshkey}
+}
+repository gotsys.git {
+ permit rw ${GOTSYSD_TEST_USER}
+ permit rw ${GOTSYSD_DEV_USER}
+ notify url "http://${GWIP}:${GOTSYSD_TEST_HTTP_PORT}/" user flan password "password" insecure hmac "${GOTSYSD_TEST_HMAC_SECRET}"
+}
+EOF
+
+ (cd ${testroot}/wt && got commit -m "whitespace changes" >/dev/null)
+ local commit_id=`git_show_head $testroot/${GOTSYS_REPO}`
+ local author_time=`git_show_author_time $testroot/${GOTSYS_REPO}`
+
+ timeout 5 ./http-server -a $AUTH -l "$GWIP" \
+ -p "$GOTSYSD_TEST_HTTP_PORT" -s "$GOTSYSD_TEST_HMAC_SECRET" \
+ > $testroot/stdout &
+
+ sleep 1 # server starts up
+
+ got send -q -i ${GOTSYSD_SSH_KEY} -r ${testroot}/${GOTSYS_REPO}
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got send failed unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ wait %1 # wait for the http "server"
+
+ echo -n > "$testroot/stdout.expected"
+ ed -s "$testroot/stdout.expected" <<-EOF
+ a
+ {"notifications":[{
+ "type":"commit",
+ "short":false,
+ "repo":"gotsys.git",
+ "authenticated_user":"${GOTSYSD_TEST_USER}",
+ "id":"$commit_id",
+ "author":{
+ "full":"$GOT_AUTHOR",
+ "name":"$GIT_AUTHOR_NAME",
+ "mail":"$GIT_AUTHOR_EMAIL",
+ "user":"$GOT_AUTHOR_11"
+ },
+ "committer":{
+ "full":"$GOT_AUTHOR",
+ "name":"$GIT_AUTHOR_NAME",
+ "mail":"$GIT_AUTHOR_EMAIL",
+ "user":"$GOT_AUTHOR_11"
+ },
+ "date":$author_time,
+ "short_message":"whitespace changes",
+ "message":"whitespace changes\n",
+ "diffstat":{
+ "files":[{
+ "action":"modified",
+ "file":"gotsys.conf",
+ "added":0,
+ "removed":3
+ }],
+ "total":{
+ "added":1,
+ "removed":0
+ }
+ }
+ }]}
+ .
+ ,j
+ w
+ EOF
+
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ test_done "$testroot" "$ret"
+}
+
+test_email_notification() {
+ local testroot=`test_init email_notification 1`
+
+ # Need to smtpd in the test VM since we will be using port 25
+ ssh -i ${GOTSYSD_SSH_KEY} root@${VMIP} \
+ '/etc/rc.d/smtpd stop' > /dev/null
+
+ got checkout -q $testroot/${GOTSYS_REPO} $testroot/wt >/dev/null
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got checkout failed unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ crypted_vm_pw=`echo ${GOTSYSD_VM_PASSWORD} | encrypt | tr -d '\n'`
+ crypted_pw=`echo ${GOTSYSD_DEV_PASSWORD} | encrypt | tr -d '\n'`
+ sshkey=`cat ${GOTSYSD_SSH_PUBKEY}`
+ cat > ${testroot}/wt/gotsys.conf <<EOF
+group slackers
+
+user ${GOTSYSD_TEST_USER} {
+ password "${crypted_vm_pw}"
+ authorized key ${sshkey}
+}
+user ${GOTSYSD_DEV_USER} {
+ password "${crypted_pw}"
+ authorized key ${sshkey}
+}
+repository gotsys.git {
+ permit rw ${GOTSYSD_TEST_USER}
+ permit rw ${GOTSYSD_DEV_USER}
+ notify email to "${GOTSYSD_TEST_USER}@example.com"
+}
+EOF
+ (cd ${testroot}/wt && got commit -m "send email notifications" \
+ >/dev/null)
+ local commit_id=`git_show_head $testroot/${GOTSYS_REPO}`
+
+ got send -q -i ${GOTSYSD_SSH_KEY} -r ${testroot}/${GOTSYS_REPO}
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got send failed unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ # Wait for gotsysd to apply the new configuration.
+ echo "$commit_id" > $testroot/stdout.expected
+ for i in 1 2 3 4 5; do
+ sleep 1
+ ssh -i ${GOTSYSD_SSH_KEY} root@${VMIP} \
+ cat /var/db/gotsysd/commit > $testroot/stdout
+ if cmp -s $testroot/stdout.expected $testroot/stdout; then
+ break;
+ fi
+ done
+ cmp -s $testroot/stdout.expected $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "gotsysd failed to apply configuration" >&2
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ cat > ${testroot}/wt/gotsys.conf <<EOF
+group slackers
+
+user ${GOTSYSD_TEST_USER} {
+ password "${crypted_vm_pw}"
+ authorized key ${sshkey}
+}
+
+user ${GOTSYSD_DEV_USER} {
+ password "${crypted_pw}"
+ authorized key ${sshkey}
+}
+
+repository gotsys.git {
+ permit rw ${GOTSYSD_TEST_USER}
+ permit rw ${GOTSYSD_DEV_USER}
+
+ notify email to "${GOTSYSD_TEST_USER}@example.com"
+}
+EOF
+ (cd ${testroot}/wt && got commit -m "whitespace changes" >/dev/null)
+ local commit_id=`git_show_head $testroot/${GOTSYS_REPO}`
+ local author_time=`git_show_author_time $testroot/${GOTSYS_REPO}`
+
+ ssh -i ${GOTSYSD_SSH_KEY} root@${VMIP} \
+ 'printf "220\r\n250\r\n250\r\n250\r\n354\r\n250\r\n221\r\n" \
+ | timeout 5 nc -l 25' > $testroot/stdout &
+
+ sleep 1 # server starts up
+
+ got send -q -i ${GOTSYSD_SSH_KEY} -r ${testroot}/${GOTSYS_REPO}
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "got send failed unexpectedly" >&2
+ test_done "$testroot" 1
+ return 1
+ fi
+
+ wait %1 # wait for ssh / nc -l
+
+ short_commit_id=`trim_obj_id 12 $commit_id`
+ HOSTNAME=`ssh -i ${GOTSYSD_SSH_KEY} root@${VMIP} hostname`
+ printf "HELO localhost\r\n" > $testroot/stdout.expected
+ printf "MAIL FROM:<${GOTD_USER}@${HOSTNAME}>\r\n" \
+ >> $testroot/stdout.expected
+ printf "RCPT TO:<${GOTSYSD_TEST_USER}@example.com>\r\n" \
+ >> $testroot/stdout.expected
+ printf "DATA\r\n" >> $testroot/stdout.expected
+ printf "From: ${GOTD_USER}@${HOSTNAME}\r\n" >> $testroot/stdout.expected
+ printf "To: ${GOTSYSD_TEST_USER}@example.com\r\n" \
+ >> $testroot/stdout.expected
+ printf "Subject: $GOTSYS_REPO: " >> $testroot/stdout.expected
+ printf "${GOTSYSD_TEST_USER} changed refs/heads/main: $short_commit_id\r\n" \
+ >> $testroot/stdout.expected
+ printf "\r\n" >> $testroot/stdout.expected
+ printf "commit $commit_id\n" >> $testroot/stdout.expected
+ printf "from: $GOT_AUTHOR\n" >> $testroot/stdout.expected
+ d=`date -u -r $author_time +"%a %b %e %X %Y UTC"`
+ printf "date: $d\n" >> $testroot/stdout.expected
+ printf "messagelen: 20\n" >> $testroot/stdout.expected
+ printf " \n" >> $testroot/stdout.expected
+ printf " whitespace changes\n \n" >> $testroot/stdout.expected
+ printf " M gotsys.conf | 3+ 0-\n\n" >> $testroot/stdout.expected
+ printf "1 file changed, 3 insertions(+), 0 deletions(-)\n\n" \
+ >> $testroot/stdout.expected
+ printf "\r\n" >> $testroot/stdout.expected
+ printf ".\r\n" >> $testroot/stdout.expected
+ printf "QUIT\r\n" >> $testroot/stdout.expected
+
+ grep -v ^Date $testroot/stdout > $testroot/stdout.filtered
+ cmp -s $testroot/stdout.expected $testroot/stdout.filtered
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout.filtered
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # Restart smtpd
+ ssh -i ${GOTSYSD_SSH_KEY} root@${VMIP} \
+ '/etc/rc.d/smtpd start' > /dev/null
+
+ test_done "$testroot" "$ret"
+}
+
test_parseargs "$@"
run_test test_user_add
run_test test_user_mod
run_test test_deny_access
run_test test_override_access_rules
run_test test_override_all_user_access
+run_test test_http_notification
+run_test test_http_notification_hmac
+run_test test_email_notification
blob - /dev/null
blob + b3f5e0351cc4e811b08b7d4b7850133a15cfc4ae (mode 755)
--- /dev/null
+++ regress/gotsysd/http-server
+#!/usr/bin/env perl
+#
+# 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.
+
+use v5.36;
+use IPC::Open2;
+use Getopt::Long qw(:config bundling no_getopt_compat);
+use Digest;
+use Digest::HMAC;
+
+my $auth;
+my $address;
+my $port = 8000;
+my $hmac_secret;
+my $hmac_signature;
+my $hmac;
+
+GetOptions("a:s" => \$auth, "l:s" => \$address, "p:i" => \$port, "s:s" => \$hmac_secret)
+ or die("usage: $0 [-a auth] [-l address] [-p port] [-s hmac_secret]\n");
+
+my $pid = open2(my $out, my $in, 'nc', '-l', $address, $port);
+
+my $clen;
+while (<$out>) {
+ local $/ = "\r\n";
+ chomp;
+
+ last if /^$/;
+
+ if (m/^POST/) {
+ die "bad http request" unless m,^POST / HTTP/1.1$,;
+ next;
+ }
+
+ if (m/^Host:/) {
+ die "bad Host header" unless /^Host: $address:$port$/;
+ next;
+ }
+
+ if (m/^Content-Type/) {
+ die "bad content-type header"
+ unless m,Content-Type: application/json$,;
+ next;
+ }
+
+ if (m/^Content-Length/) {
+ die "double content-length" if defined $clen;
+ die "bad content-length header"
+ unless m/Content-Length: (\d+)$/;
+ $clen = $1;
+ next;
+ }
+
+ if (m/Connection/) {
+ die "bad connection header"
+ unless m/Connection: close$/;
+ next;
+ }
+
+ if (m/Authorization/) {
+ die "bad authorization header"
+ unless m/Authorization: basic (.*)$/;
+ my $t = $1;
+ die "wrong authorization; got $t want $auth"
+ if not defined($auth) or $auth ne $t;
+ next;
+ }
+
+ if (m/X-Gotd-Signature/) {
+ die "bad hmac signature header"
+ unless m/X-Gotd-Signature: sha256=(.*)$/;
+ $hmac_signature = $1;
+ next;
+ }
+}
+
+die "no Content-Length header" unless defined $clen;
+
+if (defined $hmac_signature) {
+ die "no Hmac secret provided" unless defined $hmac_secret;
+ my $sha256 = Digest->new("SHA-256");
+ $hmac = Digest::HMAC->new($hmac_secret, $sha256);
+}
+
+while ($clen != 0) {
+ my $len = $clen;
+ $len = 512 if $clen > 512;
+
+ my $r = read($out, my $buf, $len);
+ $clen -= $r;
+
+ if (defined $hmac) {
+ $hmac->add($buf);
+ }
+
+ print $buf;
+}
+say "";
+
+if (defined $hmac) {
+ my $digest = $hmac->hexdigest;
+ if ($digest ne $hmac_signature) {
+ print "bad hmac signature: expected: $hmac_signature, actual: $digest";
+ die
+ }
+}
+
+print $in "HTTP/1.1 200 OK\r\n";
+print $in "Content-Length: 0\r\n";
+print $in "Connection: close\r\n";
+print $in "\r\n";
+
+close $in;
+close $out;
+
+waitpid($pid, 0);
+exit $? >> 8;