(2 months ago)commit c268699: initial release of brickware marrow
#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 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 (0 == strncmp(repo_path, GIT_BASE_PATH, strlen(GIT_BASE_PATH))) {
snprintf(real_path, sizeof(real_path), "%s", repo_path);
} else {
if ('/' == repo_path[0]) {
snprintf(real_path, sizeof(real_path), "%s%s", GIT_BASE_PATH, repo_path);
} else {
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;
}
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 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 (0 == strncmp(repo_path, GIT_BASE_PATH, strlen(GIT_BASE_PATH))) {
snprintf(full_path, sizeof(full_path), "%s", repo_path);
} else {
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) {
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 (EXIT_FAILURE);
}
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;
}