From 2c1b2eaaef8726c71648b4a115fbe628f28d6b4b Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Fri, 7 Oct 2016 01:48:16 +0200 Subject: git-receiver: splitted/reimplemented and tested shell command parser --- src/blogc-git-receiver/shell-command-parser.c | 125 ++++++++++++++++++++++++++ src/blogc-git-receiver/shell-command-parser.h | 15 ++++ src/blogc-git-receiver/shell.c | 97 +++++++++----------- src/blogc-git-receiver/shell.h | 1 + 4 files changed, 182 insertions(+), 56 deletions(-) create mode 100644 src/blogc-git-receiver/shell-command-parser.c create mode 100644 src/blogc-git-receiver/shell-command-parser.h (limited to 'src') diff --git a/src/blogc-git-receiver/shell-command-parser.c b/src/blogc-git-receiver/shell-command-parser.c new file mode 100644 index 0000000..cc8c537 --- /dev/null +++ b/src/blogc-git-receiver/shell-command-parser.c @@ -0,0 +1,125 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#include +#include +#include "../common/utils.h" +#include "shell-command-parser.h" + +typedef enum { + START_COMMAND = 1, + START_REPO, + START_REPO2, + REPO, + START_ESCAPED, +} command_state_t; + + +char* +bgr_shell_command_parse(const char *command) +{ + command_state_t state = START_COMMAND; + size_t start = 0; + size_t command_len = strlen(command); + + bc_string_t *rv = bc_string_new(); + + for (size_t current = 0; current < command_len; current++) { + + char c = command[current]; + + switch (state) { + case START_COMMAND: + if (c == ' ') { + if (((current == 16) && + (0 == strncmp("git-receive-pack", command, 16))) || + ((current == 15) && + (0 == strncmp("git-upload-pack", command, 15))) || + ((current == 18) && + (0 == strncmp("git-upload-archive", command, 18)))) + { + state = START_REPO; + break; + } + goto error; + } + break; + + case START_REPO: + if (c == '\'') { // never saw git using double-quotes + state = START_REPO2; + break; + } + if (c == '\\') { // escaped ! or ' + state = START_ESCAPED; + break; + } + goto error; + + case START_REPO2: + if (c == '\'') { + state = START_REPO; + break; + } + start = current; + if (rv->len == 0 && c == '/') { // no absolute urls + start = current + 1; + } + state = REPO; + break; + + case START_ESCAPED: + if (c == '!' || c == '\'') { + bc_string_append_c(rv, c); + state = START_REPO; + break; + } + goto error; + + case REPO: + if (c == '\'') { + bc_string_append_len(rv, command + start, current - start); + state = START_REPO; + break; + } + break; + } + } + + if (rv->len > 0) + return bc_string_free(rv, false); + +error: + bc_string_free(rv, true); + return NULL; +} + + +char* +bgr_shell_quote(const char *command) +{ + // this does not really belongs here, but function is very small + bc_string_t *rv = bc_string_new(); + bc_string_append_c(rv, '\''); + if (command != NULL) { + for (size_t i = 0; i < strlen(command); i++) { + switch (command[i]) { + case '!': + bc_string_append(rv, "'\\!'"); + break; + case '\'': + bc_string_append(rv, "'\\''"); + break; + default: + bc_string_append_c(rv, command[i]); + } + } + } + bc_string_append_c(rv, '\''); + return bc_string_free(rv, false); +} diff --git a/src/blogc-git-receiver/shell-command-parser.h b/src/blogc-git-receiver/shell-command-parser.h new file mode 100644 index 0000000..47054cb --- /dev/null +++ b/src/blogc-git-receiver/shell-command-parser.h @@ -0,0 +1,15 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifndef _SHELL_COMMAND_PARSER_H +#define _SHELL_COMMAND_PARSER_H + +char* bgr_shell_command_parse(const char *command); +char* bgr_shell_quote(const char *command); + +#endif /* _SHELL_COMMAND_PARSER_H */ diff --git a/src/blogc-git-receiver/shell.c b/src/blogc-git-receiver/shell.c index 290526b..581ec3d 100644 --- a/src/blogc-git-receiver/shell.c +++ b/src/blogc-git-receiver/shell.c @@ -16,12 +16,9 @@ #include #include #include "../common/utils.h" +#include "shell-command-parser.h" #include "shell.h" -#ifndef BUFFER_SIZE -#define BUFFER_SIZE 4096 -#endif - int bgr_shell(int argc, char *argv[]) @@ -29,22 +26,7 @@ bgr_shell(int argc, char *argv[]) int rv = 0; char *repo = NULL; - char *command_orig = NULL; - char *command_name = NULL; - char command_new[BUFFER_SIZE]; - - bool exec_git = false; - - // validate git command - size_t len = strlen(argv[2]); - if (!((len > 17 && (0 == strncmp(argv[2], "git-receive-pack ", 17))) || - (len > 16 && (0 == strncmp(argv[2], "git-upload-pack ", 16))) || - (len > 19 && (0 == strncmp(argv[2], "git-upload-archive ", 19))))) - { - fprintf(stderr, "error: unsupported git command: %s\n", argv[2]); - rv = 1; - goto cleanup; - } + char *quoted_repo = NULL; // get shell path char *self = getenv("SHELL"); @@ -62,23 +44,17 @@ bgr_shell(int argc, char *argv[]) goto cleanup; } - // get git repository - command_orig = bc_strdup(argv[2]); - char *p, *r; - for (p = command_orig; *p != ' ' && *p != '\0'; p++); - if (*p == ' ') - p++; - if (*p == '\'' || *p == '"') - p++; - if (*p == '/') - p++; - for (r = p; *p != '\'' && *p != '"' && *p != '\0'; p++); - if (*p == '\'' || *p == '"') - *p = '\0'; - if (*--p == '/') - *p = '\0'; - - repo = bc_strdup_printf("repos/%s", r); + // validate command and extract git repository + char *tmp_repo = bgr_shell_command_parse(argv[2]); + if (tmp_repo == NULL) { + fprintf(stderr, "error: invalid git-shell command: %s\n", argv[2]); + rv = 1; + goto cleanup; + } + + repo = bc_strdup_printf("repos/%s", tmp_repo); + quoted_repo = bgr_shell_quote(repo); + free(tmp_repo); // check if repository is sane if (0 == strlen(repo)) { @@ -99,7 +75,7 @@ bgr_shell(int argc, char *argv[]) if (0 != access(repo, F_OK)) { char *git_init_cmd = bc_strdup_printf( - "git init --bare \"%s\" > /dev/null", repo); + "git init --bare %s > /dev/null", quoted_repo); if (0 != system(git_init_cmd)) { fprintf(stderr, "error: failed to create git repository: %s\n", repo); @@ -175,32 +151,41 @@ bgr_shell(int argc, char *argv[]) } git_exec: - command_name = bc_strdup(argv[2]); - for (p = command_name; *p != ' ' && *p != '\0'; p++); - if (*p == ' ') - *p = '\0'; - if (BUFFER_SIZE < (strlen(command_name) + strlen(repo) + 4)) { - fprintf(stderr, "error: git-shell command is too big\n"); - rv = 1; - goto cleanup; - } + { + // static allocation instead of bc_strdup_printf to avoid leaks + char buffer[4096]; + char *command = bc_strdup(argv[2]); + char *p; + for (p = command; *p != ' ' && *p != '\0'; p++); + if (*p == ' ') + *p = '\0'; + + if (sizeof(buffer) < (strlen(command) + strlen(quoted_repo) + 2)) { + fprintf(stderr, "error: git-shell command is too big\n"); + rv = 1; + goto cleanup; + } - if (snprintf(command_new, BUFFER_SIZE, "%s '%s'", command_name, repo)) - exec_git = true; + if (0 > snprintf(buffer, sizeof(buffer), "%s %s", command, quoted_repo)) { + fprintf(stderr, "error: failed to generate git-shell command\n"); + rv = 1; + goto cleanup; + } -cleanup: - free(repo); - free(command_orig); - free(command_name); + free(command); + free(repo); + free(quoted_repo); - if (exec_git) { - execlp("git-shell", "git-shell", "-c", command_new, NULL); + execlp("git-shell", "git-shell", "-c", buffer, NULL); // execlp only returns on error, then something bad happened fprintf(stderr, "error: failed to execute git-shell\n"); - rv = 1; + return 1; // avoid freeing repo again } +cleanup: + free(repo); + free(quoted_repo); return rv; } diff --git a/src/blogc-git-receiver/shell.h b/src/blogc-git-receiver/shell.h index c05e4e1..ec77db1 100644 --- a/src/blogc-git-receiver/shell.h +++ b/src/blogc-git-receiver/shell.h @@ -9,6 +9,7 @@ #ifndef _SHELL_H #define _SHELL_H +char* bgr_shell_get_repo(const char *command); int bgr_shell(int argc, char *argv[]); #endif /* _SHELL_H */ -- cgit v1.2.3-18-g5258