brickware/marrow.git - afb08ea

(3 months ago)commit afb08ea: removed Marrow Key

Back

marrow-shell.c

raw

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <errno.h>
#include <stdint.h>

#define MAX_CMD_LEN 4096
#define GIT_BASE_PATH "/srv/git"

typedef enum {
    CMD_UNKNOWN,
    CMD_GIT_UPLOAD_PACK,
    CMD_GIT_RECEIVE_PACK,
    CMD_GIT_UPLOAD_ARCHIVE
} git_command_t;

static const char *
get_git_user(void) {
    return getenv("GIT_USER");
}

static int32_t
is_anonymous(void) {
    return NULL == get_git_user();
}

static git_command_t
parse_command(const char *cmd) {
    if (0 == strncmp(cmd, "git-upload-pack", 15)) {
        return CMD_GIT_UPLOAD_PACK;
    } else if (0 == strncmp(cmd, "git-receive-pack", 16)) {
        return CMD_GIT_RECEIVE_PACK;
    } else if (0 == strncmp(cmd, "git-upload-archive", 18)) {
        return CMD_GIT_UPLOAD_ARCHIVE;
    }
    return CMD_UNKNOWN;
}

static int32_t
is_read_command(git_command_t 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;
}

static char *
extract_repo_path(const char *cmd) {
    char *path = NULL;
    const char *start = strchr(cmd, '\'');
    
    if (start) {
        start++;
        const char *end = strchr(start, '\'');
        if (end) {
            size_t len = end - start;
            path = malloc(len + 1);
            if (path) {
                strncpy(path, start, len);
                path[len] = '\0';
            }
        }
    } else {
        const char *space = strchr(cmd, ' ');
        if (space) {
            space++;
            while (' ' == *space) space++;
            
            size_t len = strlen(space);
            path = malloc(len + 1);
            if (path) {
                strcpy(path, space);
                char *end = strchr(path, ' ');
                if (end) *end = '\0';
            }
        }
    }
    
    return path;
}

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]) {
        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);
        } else {
            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;
}

static int32_t
check_permissions(const char *cmd) {
    git_command_t git_cmd = parse_command(cmd);
    
    if (CMD_UNKNOWN == git_cmd) {
        fputs("Error: Unknown command\n", stderr);
        return 0;
    }
    
    const char *user = get_git_user();
    char *repo_path = extract_repo_path(cmd);
    
    if (!repo_path) {
        fputs("Error: Could not extract repository path\n", stderr);
        return 0;
    }
    
    if (is_anonymous()) {
        if (is_write_command(git_cmd)) {
            fputs("Error: Anonymous users cannot perform write operations\n", stderr);
            free(repo_path);
            return 0;
        }
    } else {
        if (is_write_command(git_cmd)) {
            if (!validate_repo_path(repo_path, user)) {
                fputs("Error: User '", stderr);
                fputs(user, stderr);
                fputs("' can only write to repositories under ", stderr);
                fputs(GIT_BASE_PATH, stderr);
                fputs("/", stderr);
                fputs(user, stderr);
                fputs("/\n", stderr);
                free(repo_path);
                return 0;
            }
        }
    }
    
    if (is_read_command(git_cmd)) {
        if (!validate_repo_path(repo_path, NULL)) {
            fputs("Error: Repository path must be under ", stderr);
            fputs(GIT_BASE_PATH, stderr);
            fputs("/\n", stderr);
            free(repo_path);
            return 0;
        }
    }
    
    free(repo_path);
    return 1;
}

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]) {
        snprintf(full_path, sizeof(full_path), "%s", repo_path);
    } else {
        snprintf(full_path, sizeof(full_path), "%s/%s", GIT_BASE_PATH, repo_path);
    }
    
    switch (git_cmd) {
        case CMD_GIT_UPLOAD_PACK:
            snprintf(command, sizeof(command), "git-upload-pack '%s'", full_path);
            break;
        case CMD_GIT_RECEIVE_PACK:
            snprintf(command, sizeof(command), "git-receive-pack '%s'", full_path);
            break;
        case CMD_GIT_UPLOAD_ARCHIVE:
            snprintf(command, sizeof(command), "git-upload-archive '%s'", full_path);
            break;
        default:
            fputs("Error: Unknown git command\n", stderr);
            free(repo_path);
            exit(1);
    }
    
    free(repo_path);
    
    execl("/bin/sh", "sh", "-c", command, NULL);
    perror("execl");
    exit(1);
}

int32_t
main(int32_t argc, char *argv[]) {
    const char *username = NULL;
    int32_t arg_offset = 0;
    
    /* Check for -u USER flag for authenticated access */
    if (argc > 2 && 0 == strcmp(argv[1], "-u")) {
        username = argv[2];
        arg_offset = 2;
        /* 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;
    
    if (ssh_cmd) {
        cmd = ssh_cmd;
    } else if (argc >= (3 + arg_offset) && 0 == strcmp(argv[1 + arg_offset], "-c")) {
        /* Fallback to command line argument */
        cmd = argv[2 + arg_offset];
    } else {
        fputs("Usage: ", stderr);
        fputs(argv[0], stderr);
        fputs(" [-u USER] -c <command>\n", stderr);
        fputs("This shell is designed to work with git SSH access\n", stderr);
        fputs("Usually invoked via SSH with SSH_ORIGINAL_COMMAND set\n", stderr);
        fputs("Supported commands:\n", stderr);
        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;
    }
    
    const char *user = get_git_user();
    
    if (user) {
        fputs("Authenticated as: ", stderr);
        fputs(user, stderr);
        fputs("\n", stderr);
    } else {
        fputs("Anonymous access\n", stderr);
    }
    
    if (!check_permissions(cmd)) {
        return 1;
    }
    
    execute_command(cmd);
    
    return 0;
}