git / brickware / marrow.git - c268699

(2 months ago)commit c268699: initial release of brickware marrow

Summary | History | Files

commit c268699

authorTanner Stenson <tanner@brickware.sh>
dateMon Oct 27 22:38:04 2025 -0400

message

initial release of brickware marrow

diff

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