Commit Diff


commit - 4de57f7d18150d208973cc4b070938b8117cb022
commit + b3bda0d35a32998d1ceef46bc5b0eb5813e59d9b
blob - 9ea84ce89d0ac18d3086ed46136515c5d7822a0c
blob + 41cf26b122f2b7e4948081a7d3523211aa4cd99f
--- gotsysd/libexec/gotsys-useradd/gotsys-useradd.c
+++ gotsysd/libexec/gotsys-useradd/gotsys-useradd.c
@@ -465,14 +465,24 @@ add_users(void)
 	STAILQ_FOREACH_SAFE(user, &adduser_users, entry, tmp) {
 		uid_t uid;
 
-		err = gotsys_conf_validate_name(user->name, "user");
-		if (err)
-			goto done;
-		if (user->password) {
-			err = gotsys_conf_validate_password(user->name,
-			    user->password);
+		if (strcmp(user->name, "anonymous") == 0) {
+			if (user->password == NULL ||
+			    user->password[0] != '\0') {
+				err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
+				    "the \"anonymous\" user must use an "
+				    "empty password");
+				goto done;
+			}
+		} else {
+			err = gotsys_conf_validate_name(user->name, "user");
 			if (err)
 				goto done;
+			if (user->password) {
+				err = gotsys_conf_validate_password(user->name,
+				    user->password);
+				if (err)
+					goto done;
+			}
 		}
 
 		STAILQ_REMOVE(&adduser_users, user, gotsys_user, entry);
blob - 376e0f47ac088150a0fa8a928f83380dc7736240
blob + 85ffb48b610175195bc6fccdf7c94cba6d458279
--- gotsysd/libexec/gotsys-userhome/gotsys-userhome.c
+++ gotsysd/libexec/gotsys-userhome/gotsys-userhome.c
@@ -167,11 +167,13 @@ create_homedirs(void)
 				if (err->code != GOT_ERR_PARSE_CONFIG)
 					break;
 				/*
-				 * Ignore existing users with invalid names.
+				 * Ignore existing users with invalid names
+				 * except "anonymous".
 				 * Such users were not created by us.
 				 */
 				err = NULL;
-				goto next;
+				if (strcmp(pw->pw_name, "anonymous") != 0)
+					goto next;
 			}
 			/*
 			 * Ignore existing users in our UID range which do
blob - 1d13bf576a50e4592ab5f54dda2741b80d8cead0
blob + eeef346dec6e5b64679f262f7edd5f89fc800fcc
--- gotsysd/sysconf.c
+++ gotsysd/sysconf.c
@@ -74,6 +74,7 @@ static struct gotsysd_sysconf {
 	enum gotsysd_sysconf_state state;
 	uid_t uid_start;
 	uid_t uid_end;
+	int have_anonymous_user;
 } gotsysd_sysconf;
 
 static struct gotsys_conf gotsysconf;
@@ -121,6 +122,26 @@ start_useradd(void)
 	return NULL;
 }
 
+static const struct got_error *
+add_anonymous_user(struct gotsys_userlist *users)
+{
+	const struct got_error *err;
+	struct gotsys_user *user;
+
+	err = gotsys_conf_new_user(&user, "anonymous");
+	if (err)
+		return err;
+	user->password = strdup("");
+	if (user->password == NULL) {
+		err = got_error_from_errno("strdup");
+		gotsys_user_free(user);
+		return err;
+	}
+
+	STAILQ_INSERT_TAIL(&gotsysconf.users, user, entry);
+	return NULL;
+}
+
 static void
 sysconf_dispatch_libexec(int fd, short event, void *arg)
 {
@@ -345,6 +366,13 @@ sysconf_dispatch_libexec(int fd, short event, void *ar
 			    &gotsysconf.users, &gotsysconf.groups);
 			if (err)
 				break;
+			if (!gotsysd_sysconf.have_anonymous_user &&
+			    strcmp(rule->identifier, "anonymous") == 0) {
+				err = add_anonymous_user(&gotsysconf.users);
+				if (err)
+					break;
+				gotsysd_sysconf.have_anonymous_user = 1;
+			}
 			rules = &gotsysd_sysconf.repo_cur->access_rules;
 			STAILQ_INSERT_TAIL(rules, rule, entry);
 			break;
@@ -694,6 +722,8 @@ sysconf_dispatch_priv(int fd, short event, void *arg)
 			gotsysd_sysconf.state =
 			    SYSCONF_STATE_INSTALL_AUTHORIZED_KEYS;
 			user = STAILQ_FIRST(&gotsysconf.users);
+			if (user && strcmp(user->name, "anonymous") == 0)
+				user = STAILQ_NEXT(user, entry);
 			if (user == NULL) {
 				err = got_error_msg(GOT_ERR_PARSE_CONFIG,
 				    "no users defined in configuration file");
@@ -742,6 +772,8 @@ sysconf_dispatch_priv(int fd, short event, void *arg)
 			log_debug("authorized keys installed for user %s",
 			    gotsysd_sysconf.user_cur->name);
 			user = STAILQ_NEXT(gotsysd_sysconf.user_cur, entry);
+			if (user && strcmp(user->name, "anonymous") == 0)
+				user = STAILQ_NEXT(user, entry);
 			if (user) {
 				err = start_userkeys(iev, user);
 				if (err)
blob - 3d8ccf069840ef200e21b99bee10b0677212ddc4
blob + eac2e130716a0c60236d2758d09f5b859e2fac24
--- lib/gotsys_conf.c
+++ lib/gotsys_conf.c
@@ -730,7 +730,14 @@ gotsys_conf_new_access_rule(struct gotsys_access_rule 
 			    "reference to undeclared group '%s' via "
 			    "access rule", name);
 		}
-	} else if (strcmp(name, "anonymous") != 0) {
+	} else if (strcmp(name, "anonymous") == 0) {
+		if (access == GOTSYS_ACCESS_PERMITTED &&
+		    (authorization & GOTSYS_AUTH_WRITE)) {
+			return got_error_msg(GOT_ERR_PARSE_CONFIG,
+			    "the \"anonymous\" user must not have write "
+			    "permission");
+		}
+	} else {
 		struct gotsys_user *user = NULL;
 
 		STAILQ_FOREACH(user, users, entry) {
blob - d0985116bf402e07f13fb2a508a72f4455a47514
blob + 87abeb7c7095519d423cde7b9a91554386478b15
--- lib/gotsys_imsg.c
+++ lib/gotsys_imsg.c
@@ -151,6 +151,7 @@ gotsys_imsg_recv_users(struct imsg *imsg, struct gotsy
 	offset = 0;
 	while (remain > 0) {
 		size_t namelen, pwlen, ulen;
+		int is_anonymous_user = 0;
 
 		if (remain < sizeof(iuser))
 			return got_error(GOT_ERR_PRIVSEP_LEN);
@@ -197,10 +198,13 @@ gotsys_imsg_recv_users(struct imsg *imsg, struct gotsy
 			continue;
 		}
 
-		err = gotsys_conf_validate_name(name, "user");
-		if (err) {
-			free(name);
-			return err;
+		is_anonymous_user = (strcmp(name, "anonymous") == 0);
+		if (!is_anonymous_user) {
+			err = gotsys_conf_validate_name(name, "user");
+			if (err) {
+				free(name);
+				return err;
+			}
 		}
 
 		err = gotsys_conf_new_user(&user, name);
@@ -210,6 +214,13 @@ gotsys_imsg_recv_users(struct imsg *imsg, struct gotsy
 			return err;
 
 		if (pwlen) {
+			if (is_anonymous_user) {
+				err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
+				    "the \"anonymous\" user must use an "
+				    "empty password");
+				gotsys_user_free(user);
+				return err;
+			}
 			user->password = strndup(imsg->data + offset +
 			    sizeof(iuser) + namelen, pwlen);
 			if (user->password == NULL) {
@@ -222,6 +233,13 @@ gotsys_imsg_recv_users(struct imsg *imsg, struct gotsy
 				gotsys_user_free(user);
 				return err;
 			}
+		} else if (is_anonymous_user) {
+			user->password = strdup("");
+			if (user->password == NULL) {
+				err = got_error_from_errno("strdup");
+				gotsys_user_free(user);
+				return err;
+			}
 		}
 #if 0
 		log_debug("user %s: password '%s' ssh key '%s'", user->name,
blob - 09d2632d1b6fdba13d7c7fdc738a5a6330e72fc5
blob + b670e5bee998649e54afac2f9b831ffdd87d57b4
--- regress/gotsysd/test_gotsysd.sh
+++ regress/gotsysd/test_gotsysd.sh
@@ -879,7 +879,24 @@ test_repo_create() {
 		return 1
 	fi
 
-	cat >> ${testroot}/wt/gotsys.conf <<EOF
+	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}
+}
 repository "foo" {
 	permit rw ${GOTSYSD_DEV_USER}
 }
@@ -983,6 +1000,109 @@ EOF
 	test_done "$testroot" "$ret"
 }
 
+test_user_anonymous() {
+	local testroot=`test_init user_anonymous 1`
+
+	# An attempt to grant write permissions to anonymus is an error.
+	cat > ${testroot}/bad-gotsys.conf <<EOF
+repository "gotsys" {
+	permit rw anonymous
+}
+EOF
+	gotsys check ${testroot}/bad-gotsys.conf \
+		> $testroot/stdout  2> $testroot/stderr
+	ret=$?
+	if [ $ret -eq 0 ]; then
+		echo "gotsys check suceeded unexpectedly" >&2
+		test_done "$testroot" 1
+		return 1
+	fi
+
+	echo -n "gotsys: ${testroot}/bad-gotsys.conf: line 2: " \
+		> $testroot/stderr.expected
+	echo "the \"anonymous\" user must not have write permission" \
+		>> $testroot/stderr.expected
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	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}
+}
+repository "foo" {
+	permit rw ${GOTSYSD_DEV_USER}
+	permit ro anonymous
+}
+EOF
+	(cd ${testroot}/wt && got commit -m "add anonymus user" >/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
+
+	# The new repository should be readable anonymously.
+	got clone -q anonymous@${VMIP}:foo.git $testroot/foo-anonclone.git
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "got clone failed unexpectedly" >&2
+		test_done "$testroot" 1
+		return 1
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_user_add
 run_test test_user_mod
@@ -990,3 +1110,4 @@ run_test test_user_del
 run_test test_group_add
 run_test test_group_del
 run_test test_repo_create
+run_test test_user_anonymous