commit - 71313859002afca93f40060d925f90bf11fb7734
commit + e39d79e3c6d16ad1f982e9c9a8c2f99d368bd15c
blob - 9fb50df7e49e232c3ac7791a729a4e6beefe2509
blob + 3d8275dfeef285fef7ab58e4e4300a6ef6c9887e
--- gotsh/Makefile
+++ gotsh/Makefile
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
.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
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
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
#include <sys/socket.h>
#include <sys/un.h>
+#include <ctype.h>
#include <err.h>
#include <event.h>
#include <imsg.h>
#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);
}
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));
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
#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
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);