Commit Diff


commit - 71313859002afca93f40060d925f90bf11fb7734
commit + e39d79e3c6d16ad1f982e9c9a8c2f99d368bd15c
blob - 9fb50df7e49e232c3ac7791a729a4e6beefe2509
blob + 3d8275dfeef285fef7ab58e4e4300a6ef6c9887e
--- gotsh/Makefile
+++ gotsh/Makefile
@@ -9,7 +9,8 @@ SRCS=		gotsh.c error.c pkt.c hash.c serve.c path.c git
 
 MAN =		${PROG}.1
 
-CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR}/../gotd
+CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR}/../gotd \
+	-I${.CURDIR}/../gotwebd
 
 .if defined(PROFILE)
 LDADD = -lutil_p -lc_p -levent_p
blob - 6388edcc1cf538c87de0145789b59d6972dec668
blob + 988e06af44aa3e0a9bb6f100ca2d575ea66e5b66
--- gotsh/gotsh.1
+++ gotsh/gotsh.1
@@ -22,6 +22,7 @@
 .Sh SYNOPSIS
 .Nm Fl c Sq Cm git-receive-pack Ar repository-path
 .Nm Fl c Sq Cm git-upload-pack Ar repository-path
+.Nm Fl c Sq Cm weblogin Oo Ar hostname Oc
 .Sh DESCRIPTION
 .Nm
 is the network-facing interface to
@@ -75,6 +76,27 @@ accessing the unix socket of
 via
 .Nm .
 .Pp
+The
+.Cm weblogin
+command provides user authentication for
+.Xr gotwebd 8 .
+.Nm
+will connect to
+.Xr gotwebd 8
+and obtain a login URL which allows browsing private repositories the user
+has been granted read access to in
+.Xr gotwebd.conf 5 .
+If multiple servers are declared in
+.Xr gotwebd.conf 5
+the
+.Ar hostname
+parameter is required and indicates the desired virtual host to use in the URL.
+If no
+.Ar hostname
+is specified and only one server is declared in
+.Xr gotwebd.conf 5
+then the name of this server will be used in the URL.
+.Pp
 It is recommended to restrict
 .Xr ssh 1
 features available to users of
@@ -139,12 +161,48 @@ Match User anonymous
     DisableForwarding yes
     PermitTTY no
 .Ed
+.Pp
+Obtain a
+.Xr gotwebd 8
+login URL for got.example.com:
+.Bd -literal -offset indent
+$ ssh got.example.com weblogin
+.Ed
+.Pp
+If the web server at got.example.com serves virtual hosts then two
+hostnames must be provided.
+One for
+.Xr ssh 1
+to connect to, and another to identify the virtual host served by
+.Xr gotwebd 8 :
+.Bd -literal -offset indent
+$ ssh got.example.com weblogin got.example.com
+.Ed
+.Pp
+In practice both hostnames will often be the same, but this is not guaranteed.
+There is no reliable way determine the desired virtual host automatically.
+An
+.Xr ssh_config 5
+entry like the following can save some typing:
+.Bd -literal -offset indent
+Host weblogin
+	Hostname got.example.com
+	RemoteCommand weblogin %h
+.Ed
+.Pp
+The following command is now equivalent to the above:
+.Bd -literal -offset indent
+$ ssh weblogin
+.Ed
+.Ed
 .Sh SEE ALSO
 .Xr gitwrapper 1 ,
 .Xr got 1 ,
 .Xr ssh 1 ,
 .Xr gotd.conf 5 ,
+.Xr gotwebd.conf 5 ,
 .Xr sshd_config 5 ,
-.Xr gotd 8
+.Xr gotd 8 ,
+.Xr gotwebd 8
 .Sh AUTHORS
 .An Stefan Sperling Aq Mt stsp@openbsd.org
blob - 13623bc34739f85f1e1611827992c048d0747d09
blob + 1772346892c041ad7f5ea59596a15be2077c6044
--- gotsh/gotsh.c
+++ gotsh/gotsh.c
@@ -19,6 +19,7 @@
 #include <sys/socket.h>
 #include <sys/un.h>
 
+#include <ctype.h>
 #include <err.h>
 #include <event.h>
 #include <imsg.h>
@@ -34,18 +35,22 @@
 #include "got_object.h"
 #include "got_serve.h"
 #include "got_path.h"
+#include "got_reference.h"
 
 #include "got_lib_dial.h"
+#include "got_lib_poll.h"
 
 #include "gotd.h"
+#include "gotwebd.h"
 
 static int chattygot;
 
 __dead static void
 usage(void)
 {
-	fprintf(stderr, "usage: %s -c '%s|%s repository-path'\n",
+	fprintf(stderr, "usage: %s -c '%s|%s repository-path]'\n",
 	    getprogname(), GOT_DIAL_CMD_SEND, GOT_DIAL_CMD_FETCH);
+	fprintf(stderr, "       %s -c 'weblogin [hostname]'\n'", getprogname());
 	exit(1);
 }
 
@@ -65,46 +70,265 @@ apply_unveil(const char *unix_socket_path)
 	return NULL;
 }
 
+/* Read session URL from gotwebd's auth socket and send it to the client. */
+static const struct got_error *
+weblogin(int outfd, int sock, const char *hostname)
+{
+	const struct got_error *err = NULL;
+	char *url = NULL, *greeting = NULL;
+	char buf[_POSIX_HOST_NAME_MAX + 2];
+	size_t size, remain;
+	int ret;
+
+	if (hostname) {
+		/*
+		 * Send the desired hostname to gotwebd.
+		 * gotwebd reads up to _POSIX_HOST_NAME_MAX + 1 bytes, terminated
+		 * by a linefeed, \n.
+		 */
+		ret = snprintf(buf, sizeof(buf), "login %s\n", hostname);
+	} else
+		ret = snprintf(buf, sizeof(buf), "login\n");
+	if (ret == -1)
+		return got_error_from_errno("snprintf");
+	if ((size_t)ret >= sizeof(buf))
+		return got_error(GOT_ERR_NO_SPACE);
+
+	err = got_poll_write_full(sock, buf, ret);
+	if (err)
+		return err;
+
+	/*
+	 * gotwebd will return "ok URL", likewise terminated by \n,
+	 * or might return an arbitrary error message + \n.
+	 * We don't know how long this line will be, so keep reading
+	 * in chunks until we have read all of it.
+	 * For forward compatibilty, ignore any trailing lines received.
+	 */
+	url = calloc(1, sizeof(buf));
+	if (url == NULL)
+		return got_error_from_errno("calloc");
+
+	size = sizeof(buf);
+	remain = size;
+
+	memset(buf, 0, sizeof(buf)); /* reusing buf with strlcat below */
+
+	for (;;) {
+		size_t len;
+		char *eol;
+
+		err = got_poll_read_full(sock, &len, buf, sizeof(buf) - 1, 1);
+		if (err)
+			goto done;
+
+		if (len == 0) {
+			err = got_error(GOT_ERR_EOF);
+			goto done;
+		} else if (len >= remain) { /* should not happen */
+			err = got_error(GOT_ERR_NO_SPACE);
+			goto done;
+		}
+
+		buf[len] = '\0';
+
+		eol = memchr(buf, '\n', len);
+		if (eol)
+			*eol = '\0';
+
+		if (strlcat(url, buf, size) >= size) {
+			err = got_error(GOT_ERR_NO_SPACE);
+			goto done;
+		}
+
+		if (eol)
+			break;
+
+		remain -= len;
+		if (remain < sizeof(buf)) {
+			size_t newsize = size + sizeof(buf);
+			char *p;
+
+			p = realloc(url, newsize);
+			if (p == NULL) {
+				err = got_error_from_errno("realloc");
+				goto done;
+			}
+			url = p;
+			size = newsize;
+			remain += sizeof(buf);
+		}
+	}
+
+	if (strncmp(url, "ok ", 3) == 0) {
+		if (asprintf(&greeting, "Login successful. Please visit the "
+		    "following URL within the next %d minutes: ",
+		    GOTWEBD_AUTH_TIMEOUT / 60) == -1) {
+			err = got_error_from_errno("asprintf");
+			goto done;
+		}
+		err = got_poll_write_full(outfd, greeting, strlen(greeting));
+		if (err)
+			goto done;
+		err = got_poll_write_full(outfd, url + 3, strlen(url) - 3);
+	} else
+		err = got_poll_write_full(outfd, url, strlen(url));
+done:
+	if (err && err->code != GOT_ERR_EOF) {
+		const struct got_error *err2;
+
+		err2 = got_error_fmt(err->code, "%s", getprogname());
+		got_poll_write_full(outfd, err2->msg, strlen(err2->msg));
+	}
+	free(url);
+	free(greeting);
+	return err;
+}
+
+
+static const struct got_error *
+parse_weblogin_command(char **hostname, char *cmd)
+{
+	size_t len, cmdlen;
+
+	*hostname = NULL;
+
+	len = strlen(cmd);
+
+	while (len > 0 && isspace(cmd[len - 1]))
+		cmd[--len] = '\0';
+
+	if (len == 0)
+		return got_error(GOT_ERR_BAD_PACKET);
+
+	if (len >= strlen(GOTWEBD_AUTH_CMD) &&
+	    strncmp(cmd, GOTWEBD_AUTH_CMD, strlen(GOTWEBD_AUTH_CMD)) == 0)
+		cmdlen = strlen(GOTWEBD_AUTH_CMD);
+	else
+		return got_error(GOT_ERR_BAD_PACKET);
+
+	/* The hostname parameter is optional. */
+	if (len == cmdlen)
+		return NULL;
+
+	if (len <= cmdlen + 1 || cmd[cmdlen] != ' ')
+		return got_error(GOT_ERR_BAD_PACKET);
+
+	if (memchr(&cmd[cmdlen + 1], '\0', len - cmdlen) == NULL)
+		return got_error(GOT_ERR_BAD_PACKET);
+
+	/* Forbid linefeeds in hostnames. We use \n as internal terminator. */
+	if (memchr(&cmd[cmdlen + 1], '\n', len - cmdlen) != NULL)
+		return got_error(GOT_ERR_BAD_PACKET);
+
+	*hostname = strdup(&cmd[cmdlen + 1]);
+	if (*hostname == NULL)
+		return got_error_from_errno("strdup");
+
+	/* Deny an empty hostname. */
+	if ((*hostname)[0] == '\0') {
+		free(*hostname);
+		*hostname = NULL;
+		return got_error(GOT_ERR_BAD_PACKET);
+	}
+
+	/* Deny overlong hostnames ,*/
+	if (len - cmdlen > _POSIX_HOST_NAME_MAX)
+		return got_error_fmt(GOT_ERR_NO_SPACE,
+		    "hostname length exceeds %d bytes", _POSIX_HOST_NAME_MAX);
+
+	/*
+	 * TODO: More hostname verification? In any case, the provided
+	 * value will have to match a string obtained from gotwebd.conf.
+	 */
+
+	return NULL;
+}
+
 int
 main(int argc, char *argv[])
 {
 	const struct got_error *error;
 	const char *unix_socket_path;
-	int gotd_sock = -1;
+	int sock = -1;
 	struct sockaddr_un	 sun;
 	char *gitcmd = NULL, *command = NULL, *repo_path = NULL;
+	char *hostname = NULL;
+	int do_weblogin = 0;
 
 #ifndef PROFILE
 	if (pledge("stdio recvfd unix unveil", NULL) == -1)
 		err(1, "pledge");
 #endif
-
-	unix_socket_path = getenv("GOTD_UNIX_SOCKET");
-	if (unix_socket_path == NULL)
-		unix_socket_path = GOTD_UNIX_SOCKET;
-
-	error = apply_unveil(unix_socket_path);
-	if (error)
-		goto done;
-
-	if (strcmp(argv[0], GOT_DIAL_CMD_SEND) == 0 ||
+	if (strcmp(argv[0], GOTWEBD_AUTH_CMD) == 0) {
+		if (argc != 1 && argc != 2)
+			usage();
+		unix_socket_path = getenv("GOTWEBD_AUTH_SOCKET");
+		if (unix_socket_path == NULL)
+			unix_socket_path = GOTWEBD_AUTH_SOCKET;
+		error = apply_unveil(unix_socket_path);
+		if (error)
+			goto done;
+		if (argc == 2) {
+			hostname = strdup(argv[1]);
+			if (hostname == NULL) {
+				error = got_error_from_errno("strdup");
+				goto done;
+			}
+		}
+		do_weblogin = 1;
+	} else if (strcmp(argv[0], GOT_DIAL_CMD_SEND) == 0 ||
 	    strcmp(argv[0], GOT_DIAL_CMD_FETCH) == 0) {
 		if (argc != 2)
 			usage();
+		unix_socket_path = getenv("GOTD_UNIX_SOCKET");
+		if (unix_socket_path == NULL)
+			unix_socket_path = GOTD_UNIX_SOCKET;
+		error = apply_unveil(unix_socket_path);
+		if (error)
+			goto done;
 		if (asprintf(&gitcmd, "%s %s", argv[0], argv[1]) == -1)
 			err(1, "asprintf");
 		error = got_dial_parse_command(&command, &repo_path, gitcmd);
-	} else {
-		if (argc != 3 || strcmp(argv[1], "-c") != 0)
-			usage();
-		error = got_dial_parse_command(&command, &repo_path, argv[2]);
-	}
-	if (error && error->code == GOT_ERR_BAD_PACKET)
+		if (error) {
+			if (error->code == GOT_ERR_BAD_PACKET)
+				usage();
+			goto done;
+		}
+	} else if (argc == 3 && strcmp(argv[1], "-c") == 0) {
+		if (strcmp(argv[2], GOTWEBD_AUTH_CMD) == 0) {
+			unix_socket_path = getenv("GOTWEBD_AUTH_SOCKET");
+			if (unix_socket_path == NULL)
+				unix_socket_path = GOTWEBD_AUTH_SOCKET;
+			error = apply_unveil(unix_socket_path);
+			if (error)
+				goto done;
+			error = parse_weblogin_command(&hostname, argv[2]);
+			if (error) {
+				if (error->code == GOT_ERR_BAD_PACKET)
+					usage();
+				goto done;
+			}
+			do_weblogin = 1;
+		} else {
+			unix_socket_path = getenv("GOTD_UNIX_SOCKET");
+			if (unix_socket_path == NULL)
+				unix_socket_path = GOTD_UNIX_SOCKET;
+			error = apply_unveil(unix_socket_path);
+			if (error)
+				goto done;
+			error = got_dial_parse_command(&command, &repo_path,
+			    argv[2]);
+			if (error) {
+				if (error->code == GOT_ERR_BAD_PACKET)
+					usage();
+				goto done;
+			}
+		}
+	} else
 		usage();
-	if (error)
-		goto done;
 
-	if ((gotd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+	if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
 		err(1, "socket");
 
 	memset(&sun, 0, sizeof(sun));
@@ -112,21 +336,30 @@ main(int argc, char *argv[])
 	if (strlcpy(sun.sun_path, unix_socket_path,
 	    sizeof(sun.sun_path)) >= sizeof(sun.sun_path))
 		errx(1, "gotd socket path too long");
-	if (connect(gotd_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+	if (connect(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
 		err(1, "connect: %s", unix_socket_path);
 
+	if (do_weblogin) {
 #ifndef PROFILE
-	if (pledge("stdio recvfd", NULL) == -1)
-		err(1, "pledge");
+		if (pledge("stdio", NULL) == -1)
+			err(1, "pledge");
 #endif
-	error = got_serve(STDIN_FILENO, STDOUT_FILENO, command, repo_path,
-	    gotd_sock, chattygot);
+		error = weblogin(STDOUT_FILENO, sock, hostname);
+	} else {
+#ifndef PROFILE
+		if (pledge("stdio recvfd", NULL) == -1)
+			err(1, "pledge");
+#endif
+		error = got_serve(STDIN_FILENO, STDOUT_FILENO, command,
+		    repo_path, sock, chattygot);
+	}
 done:
 	free(gitcmd);
 	free(command);
 	free(repo_path);
-	if (gotd_sock != -1)
-		close(gotd_sock);
+	free(hostname);
+	if (sock != -1)
+		close(sock);
 	if (error) {
 		fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
 		return 1;
blob - 1973199772dbd6e269f7b884a5607f7e7d7f0414
blob + 432a79667a3d6deab012c3bd82c9d857be510a0f
--- gotwebd/gotwebd.h
+++ gotwebd/gotwebd.h
@@ -39,6 +39,10 @@
 #define GOTWEBD_WWW_USER	 "www"
 #endif
 
+#define GOTWEBD_AUTH_CMD	"weblogin"
+#define GOTWEBD_AUTH_SOCKET	 "/var/run/gotweb-auth.sock"
+#define GOTWEBD_AUTH_TIMEOUT	 300 /* in seconds */
+
 #define GOTWEBD_MAXDESCRSZ	 1024
 #define GOTWEBD_MAXCLONEURLSZ	 1024
 #define GOTWEBD_CACHESIZE	 1024
@@ -538,3 +542,6 @@ int config_setfd(struct gotwebd *);
 int config_getfd(struct gotwebd *, struct imsg *);
 int config_getcfg(struct gotwebd *, struct imsg *);
 int config_init(struct gotwebd *);
+
+/* ../lib/gotweb_auth.c */
+const struct got_error * gotweb_auth(int, int, int);