(2 months ago)commit c268699: initial release of brickware marrow
| author | Tanner Stenson <tanner@brickware.sh> |
| date | Mon Oct 27 22:38:04 2025 -0400 |
initial release of brickware marrow
commit c26869931a0774874b1d49cf020332511c0b158a
Author: Tanner Stenson <tanner@brickware.sh>
Date: Mon Oct 27 22:38:04 2025 -0400
initial release of brickware marrow
diff --git a/Makefile b/Makefile
index d369580..fe8f120 100644
--- a/Makefile
+++ b/Makefile
@@ -8,11 +8,11 @@ TARGETS = marrow-auth marrow-shell
all: $(TARGETS)
-marrow-auth: marrow-auth.c
- $(CC) $(CFLAGS) -o $@ $<
+marrow-auth: marrow-auth.o slice.o
+ $(CC) $(CFLAGS) -o $@ $>
-marrow-shell: marrow-shell.c
- $(CC) $(CFLAGS) -o $@ $<
+marrow-shell: marrow-shell.o slice.o
+ $(CC) $(CFLAGS) -o $@ $>
install: $(TARGETS)
install -d $(DESTDIR)$(BINDIR)
@@ -21,5 +21,10 @@ install: $(TARGETS)
clean:
rm -f $(TARGETS)
+ rm -f *.o
.PHONY: all install clean
+
+.SUFFIXES : .c .o
+.c.o :
+ $(CC) $(CFLAGS) -fPIC -c $<
diff --git a/marrow-auth.c b/marrow-auth.c
index 7e3fdbd..4f770fa 100644
--- a/marrow-auth.c
+++ b/marrow-auth.c
@@ -8,6 +8,7 @@
#include <fcntl.h>
#include <dirent.h>
+#include "./slice.h"
#define MAX_LINE 4096
#define MAX_USERNAME 256
@@ -17,20 +18,7 @@
#define PATH_MAX 4096
#endif
-struct slice {
- const char *ptr;
- size_t size;
-};
-static struct slice
-_cstrtoslice(const char *s) {
- struct slice result;
-
- result.ptr = s;
- result.size = strlen(s);
-
- return (result);
-}
static int32_t
_openat(const int32_t wfd, const struct slice *path, int32_t flags) {
@@ -136,9 +124,9 @@ main(int32_t argc, char *argv[]) {
}
// TODO: loop over config dir per user
- const struct slice conf_path = _cstrtoslice(argv[1]);
- const struct slice ssh_keytype = _cstrtoslice(argv[2]);
- const struct slice ssh_key = _cstrtoslice(argv[3]);
+ const struct slice conf_path = cstrtoslice(argv[1]);
+ const struct slice ssh_keytype = cstrtoslice(argv[2]);
+ const struct slice ssh_key = cstrtoslice(argv[3]);
fd = _openat(AT_FDCWD, &conf_path, O_RDONLY);
if (0 > fd) {
@@ -190,7 +178,7 @@ main(int32_t argc, char *argv[]) {
if (0 == _slcmp(&ssh_keytype, &origin_keytype) && 0 == _slcmp(&ssh_key, &origin_key)) {
// TODO: if match, return ssh authorized_key format for that user
- printf("command=\"marrow-shell -u %s\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %.*s %.*s %.*s\n",
+ printf("command=\"/usr/local/bin/marrow-shell -u %s\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %.*s %.*s %.*s\n",
dp->d_name,
ssh_keytype.size,
ssh_keytype.ptr,
@@ -214,7 +202,7 @@ main(int32_t argc, char *argv[]) {
// TODO: if no match, return ssh authorized_key format
if (!match) {
- printf("command=\"marrow-shell\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %.*s %.*s anonymous\n",
+ printf("command=\"/usr/local/bin/marrow-shell\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %.*s %.*s anonymous\n",
ssh_keytype.size,
ssh_keytype.ptr,
ssh_key.size,
diff --git a/marrow-shell.c b/marrow-shell.c
index fe2d839..c0cd2f3 100644
--- a/marrow-shell.c
+++ b/marrow-shell.c
@@ -42,12 +42,12 @@ parse_command(const char *cmd) {
static int32_t
is_read_command(git_command_t cmd) {
- return CMD_GIT_UPLOAD_PACK == cmd || CMD_GIT_UPLOAD_ARCHIVE == cmd;
+ return (CMD_GIT_UPLOAD_PACK == cmd) || (CMD_GIT_UPLOAD_ARCHIVE == cmd);
}
static int32_t
is_write_command(git_command_t cmd) {
- return CMD_GIT_RECEIVE_PACK == cmd;
+ return (CMD_GIT_RECEIVE_PACK == cmd);
}
static char *
@@ -82,38 +82,87 @@ extract_repo_path(const char *cmd) {
}
}
- return path;
+ return (path);
+}
+
+static char *
+normalize_path(const char *path) {
+ char *normalized = malloc(PATH_MAX);
+ if (!normalized) {
+ return NULL;
+ }
+
+ char temp[PATH_MAX];
+ strncpy(temp, path, sizeof(temp) - 1);
+ temp[sizeof(temp) - 1] = '\0';
+
+ char *components[PATH_MAX / 2]; /* Max possible components */
+ int32_t comp_count = 0;
+
+ /* Split path into components */
+ char *token = strtok(temp, "/");
+ while (token && comp_count < (PATH_MAX / 2 - 1)) {
+ if (0 == strcmp(token, ".")) {
+ /* Skip current directory */
+ continue;
+ } else if (0 == strcmp(token, "..")) {
+ /* Go up one directory */
+ if (comp_count > 0) {
+ comp_count--;
+ }
+ } else {
+ /* Normal component */
+ components[comp_count++] = token;
+ }
+ token = strtok(NULL, "/");
+ }
+
+ /* Rebuild the path */
+ normalized[0] = '\0';
+ for (int32_t i = 0; i < comp_count; i++) {
+ strcat(normalized, "/");
+ strcat(normalized, components[i]);
+ }
+
+ /* Handle root case */
+ if (0 == strlen(normalized)) {
+ strcpy(normalized, "/");
+ }
+
+ return normalized;
}
static int32_t
validate_repo_path(const char *repo_path, const char *user) {
char real_path[PATH_MAX];
char expected_base[PATH_MAX];
-
- if ('/' == repo_path[0]) {
+
+ if (0 == strncmp(repo_path, GIT_BASE_PATH, strlen(GIT_BASE_PATH))) {
snprintf(real_path, sizeof(real_path), "%s", repo_path);
} else {
- snprintf(real_path, sizeof(real_path), "%s/%s", GIT_BASE_PATH, repo_path);
- }
-
- char *resolved = realpath(real_path, NULL);
- if (!resolved) {
- if (ENOENT == errno) {
- resolved = strdup(real_path);
+ if ('/' == repo_path[0]) {
+ snprintf(real_path, sizeof(real_path), "%s%s", GIT_BASE_PATH, repo_path);
} else {
- return 0;
+ snprintf(real_path, sizeof(real_path), "%s/%s", GIT_BASE_PATH, repo_path);
}
}
-
+
+ /* Always normalize the path to resolve .. components */
+ char *resolved = normalize_path(real_path);
+ if (!resolved) {
+ return 0;
+ }
+
if (!user) {
snprintf(expected_base, sizeof(expected_base), "%s/", GIT_BASE_PATH);
} else {
snprintf(expected_base, sizeof(expected_base), "%s/%s/", GIT_BASE_PATH, user);
}
-
+
int32_t valid = (0 == strncmp(resolved, expected_base, strlen(expected_base))) ||
(0 == strcmp(resolved, GIT_BASE_PATH) && !user);
-
+
+
free(resolved);
return valid;
}
@@ -171,22 +220,68 @@ check_permissions(const char *cmd) {
return 1;
}
+static int32_t
+create_bare_repository(const char *path) {
+ char command[MAX_CMD_LEN];
+ char parent_dir[PATH_MAX];
+
+ /* Extract parent directory */
+ strncpy(parent_dir, path, sizeof(parent_dir) - 1);
+ parent_dir[sizeof(parent_dir) - 1] = '\0';
+ char *last_slash = strrchr(parent_dir, '/');
+ if (last_slash) {
+ *last_slash = '\0';
+
+ /* Create parent directory if it doesn't exist */
+ snprintf(command, sizeof(command), "mkdir -p '%s'", parent_dir);
+ if (0 != system(command)) {
+ return 0;
+ }
+ }
+
+ /* Create bare git repository */
+ snprintf(command, sizeof(command), "git init --bare '%s'", path);
+ return (0 == system(command));
+}
+
static void
execute_command(const char *cmd) {
char command[MAX_CMD_LEN];
git_command_t git_cmd = parse_command(cmd);
char *repo_path = extract_repo_path(cmd);
-
+
if (!repo_path) {
fputs("Error: Invalid command format\n", stderr);
exit(1);
}
-
+
char full_path[PATH_MAX];
- if ('/' == repo_path[0]) {
+ if (0 == strncmp(repo_path, GIT_BASE_PATH, strlen(GIT_BASE_PATH))) {
snprintf(full_path, sizeof(full_path), "%s", repo_path);
} else {
- snprintf(full_path, sizeof(full_path), "%s/%s", GIT_BASE_PATH, repo_path);
+ if ('/' == repo_path[0]) {
+ snprintf(full_path, sizeof(full_path), "%s%s", GIT_BASE_PATH, repo_path);
+ } else {
+ snprintf(full_path, sizeof(full_path), "%s/%s", GIT_BASE_PATH, repo_path);
+ }
+ }
+
+ /* Auto-create repository for authenticated write operations */
+ if (is_write_command(git_cmd) && !is_anonymous()) {
+ struct stat st;
+ if (0 != stat(full_path, &st)) {
+ if (ENOENT == errno) {
+ if (!create_bare_repository(full_path)) {
+ fputs("Error: Failed to create repository\n", stderr);
+ free(repo_path);
+ exit(1);
+ }
+ } else {
+ perror("stat");
+ free(repo_path);
+ exit(1);
+ }
+ }
}
switch (git_cmd) {
@@ -224,7 +319,7 @@ main(int32_t argc, char *argv[]) {
/* Set GIT_USER environment variable */
setenv("GIT_USER", username, 1);
}
-
+
/* Check for SSH_ORIGINAL_COMMAND first (SSH forced command) */
const char *ssh_cmd = getenv("SSH_ORIGINAL_COMMAND");
const char *cmd = NULL;
@@ -244,7 +339,7 @@ main(int32_t argc, char *argv[]) {
fputs(" git-upload-pack <repo> - Clone/fetch repository\n", stderr);
fputs(" git-receive-pack <repo> - Push to repository\n", stderr);
fputs(" git-upload-archive <repo> - Archive repository\n", stderr);
- return 1;
+ return (EXIT_FAILURE);
}
const char *user = get_git_user();
@@ -264,4 +359,4 @@ main(int32_t argc, char *argv[]) {
execute_command(cmd);
return 0;
-}
\ No newline at end of file
+}
diff --git a/path.h b/path.h
new file mode 100644
index 0000000..cba5e3c
--- /dev/null
+++ b/path.h
@@ -0,0 +1,47 @@
+#ifndef __PATH_H
+
+struct path {
+ char **components; // Array of path components
+ int32_t count; // Number of components
+ int32_t capacity; // Allocated capacity
+ int32_t is_absolute; // Whether path starts with /
+};
+
+// Creation/Destruction
+struct path
+path_from_string(const char *s);
+
+struct path
+path_new(void);
+
+void
+path_free(struct path *p);
+
+// Manipulation
+int32_t
+path_join(struct path *p, const char *component);
+
+int32_t
+path_resolve(struct path *p); // Handle .. and .
+
+struct path
+path_parent(const struct path *p);
+
+const char *
+path_basename(const struct path *p);
+
+// Output
+int32_t
+path_to_string(const struct path *p, char *buffer, size_t size);
+
+// Utilities
+int32_t
+path_is_absolute(const struct path *p);
+
+int32_t
+path_starts_with(const struct path *p, const struct path *prefix);
+
+struct path
+path_copy(const struct path *src);
+
+#endif
diff --git a/slice.c b/slice.c
new file mode 100644
index 0000000..c3fcab1
--- /dev/null
+++ b/slice.c
@@ -0,0 +1,14 @@
+#include <unistd.h>
+#include <string.h>
+#include "./slice.h"
+
+
+struct slice
+cstrtoslice(const char *s) {
+ struct slice result;
+
+ result.ptr = s;
+ result.size = strlen(s);
+
+ return (result);
+}
diff --git a/slice.h b/slice.h
new file mode 100644
index 0000000..b064ace
--- /dev/null
+++ b/slice.h
@@ -0,0 +1,15 @@
+#ifndef __SLICE_H
+/*
+ * Slice management
+ */
+
+struct slice {
+ const char *ptr;
+ size_t size;
+};
+
+struct slice
+cstrtoslice(const char *s);
+
+
+#endif