summaryrefslogtreecommitdiff
path: root/src/main.c
blob: 9480cd12f518c13124dc26bfef06ed63a516fb8e (plain)
    1 /**
    2  * Gitaccess implements basic access controls for git servers.
    3  * Copyright (C) 2021 Aaron Ball <nullspoon@oper.io>
    4  * 
    5  * This program is free software: you can redistribute it and/or modify
    6  * it under the terms of the GNU General Public License as published by
    7  * the Free Software Foundation, either version 3 of the License, or
    8  * (at your option) any later version.
    9  * 
   10  * This program is distributed in the hope that it will be useful,
   11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13  * GNU General Public License for more details.
   14  * 
   15  * You should have received a copy of the GNU General Public License
   16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   17  */
   18 
   19 /**
   20  * Description
   21  * -----------
   22  * 
   23  * This program provides basic access controls to git repos. For this program
   24  * to work, it requires each repository to have a 'users' file. That file is
   25  * read to determine if the user associated with the logged in ssh key has
   26  * access to that repo.
   27  * 
   28  * This program also supports the interactive git shell, interactive shell
   29  * rejection via the no-interactive-shell script, and any other scripts that
   30  * are placed inside the ~/git-shell-commands directory.
   31  * 
   32  * To use this script for a specified ssh key, call it using the command
   33  * directive in the ~/.ssh/authorized keys file using the following syntax
   34  * 
   35  *     # Key for user <username>
   36  *     command="gitaccess <username>" ssh-ed25519 AAAAE2v....
   37  */
   38 #include <stdio.h>
   39 #include <stdlib.h>
   40 #include <string.h>
   41 #include <unistd.h>
   42 #include <time.h>
   43 
   44 extern char **environ;
   45 
   46 /**
   47  * logmsg:
   48  * Writes specified message to log file at ${HOME}/git.log
   49  *
   50  * @msg  Message to write to log file
   51  */
   52 void logmsg(char* msg) {
   53   char log[512];
   54   // Get the current timestamp
   55   char dstr[100];
   56   time_t now = time(NULL);
   57   struct tm* t = localtime(&now);
   58 
   59   // Generate date string
   60   strftime(dstr, sizeof(dstr)-1, "%Y-%m-%d  %H:%M:%S", t);
   61 
   62   sprintf(log, "%s/%s", getenv("HOME"), "git.log");
   63 
   64   FILE* fd = fopen(log, "a");
   65   fprintf(fd, "%s %s\n", dstr, msg);
   66   fclose(fd);
   67 }
   68 
   69 /**
   70  * trim:
   71  * Function to trim all leading and trailing whitespace. Note that this mutates
   72  * the source string by writing a null byte over the first trailing whitespace.
   73  *
   74  * @str     String to trim
   75  *
   76  * @return  Pointer to the first non-whitespace character in str
   77  */
   78 char* trim(char* str) {
   79   int i = 0;
   80   char* start;
   81 
   82   // Move the cursor forward
   83   while(str[i] == ' ' || str[i] == '\t')
   84     i++;
   85   start = &str[i];
   86 
   87   // Reset i to end of string
   88   i = strlen(str) - 1;
   89   while(str[i] == ' ' || str[i] == '\t' || str[i] == '\n')
   90     i--;
   91 
   92   if(str[i] != '\0')
   93     str[i + 1] = '\0';
   94   return start;
   95 }
   96 
   97 /**
   98  * line_in_file:
   99  * Checks if the specified line is in the specified file. Note that this
  100  * exactly matches the two lines, so partial matches will still fail.
  101  *
  102  * @path    String path to the file to search
  103  * @line    Line to check if present in file
  104  *
  105  * @return  1 if present, 0 if not, -1 if file could not be accessed
  106  */
  107 int line_in_file(char* path, char* line) {
  108   FILE* fd = fopen(path, "r");
  109   char buf[256];
  110   int retval = 0;
  111 
  112   if(!fd) {
  113     fprintf(stderr, "Could not access %s\n", path);
  114     return -1;
  115   }
  116 
  117   while(fgets(buf, 256, fd)) {
  118     strcpy(buf, trim(buf));
  119     if(strcmp(buf, line) == 0) {
  120       retval = 1;
  121       break;
  122     }
  123   }
  124   fclose(fd);
  125   return retval;
  126 }
  127 
  128 
  129 /**
  130  * is_git_cmd:
  131  * Checks if input string is a valid git server command.
  132  *
  133  * @str     String to check if it is a valid git server command
  134  *
  135  * @return  1 if is a git command, 0 if not
  136  */
  137 int is_git_cmd(char* str) {
  138   if(strcmp(str, "git-upload-pack") == 0
  139     || strcmp(str, "git-upload-archive") == 0
  140     || strcmp(str, "git-receive-pack") == 0)
  141     return 1;
  142   return 0;
  143 }
  144 
  145 
  146 /**
  147  * is_allowed_cmd:
  148  * Checks the git-shell-commands directory for a filename matching the input
  149  * string str. Matching file must be executable to return a positive.
  150  *
  151  * @str     Command name
  152  *
  153  * @return  1 if allowed, 0 if not
  154  */
  155 int is_allowed_cmd(char* str) {
  156   char path[256];
  157   sprintf(path, "%s/git-shell-commands/%s", getenv("HOME"), str);
  158   if(access(path, F_OK) != -1 && access(path, X_OK) != -1)
  159     return 1;
  160   return 0;
  161 }
  162 
  163 
  164 /**
  165  * validate_git:
  166  * Validates the specified user's acccess to the git repo pointed to in the
  167  * SSH_ORIGINAL_COMMAND environment variable.
  168  *
  169  * @user    Name of the user for whom to check permission
  170  *
  171  * @return  1 if permitted, 0 if not
  172  */
  173 int validate_git(char *user, char *repo) {
  174   char userspath[256]; // Path to the repo's users file (if one is specified)
  175 
  176   if(strcmp(&repo[strlen(repo) - 4], ".git") == 0)
  177     sprintf(userspath, "%s/users", repo);
  178   else
  179     sprintf(userspath, "%s.git/users", repo);
  180 
  181   if(access(userspath, F_OK) == -1) {
  182     fprintf(stderr, "Repo %s does not exist or is misconfigured.\n", repo);
  183     return 0;
  184   }
  185   if(line_in_file(userspath, user) != 1) {
  186     fprintf(stderr, "User %s does not have permission to access repo %s\n", user, repo);
  187     return 0;
  188   }
  189   return 1;
  190 }
  191 
  192 
  193 int main(int argc, char* argv[]) {
  194   char cmd[128];   // Buffer for the first cmd in SSH_ORIGINAL_COMMAND
  195   char repo[128];  // Buffer for the repo path (from SSH_ORIGINAL_COMMAND)
  196   char gitsh[512]; // Buffer for the git-shell cmd (from SSH_ORIGINAL_COMMAND)
  197   char* user;
  198   char msg[512];
  199 
  200   // Ensure username is specified
  201   if(argc == 1) {
  202     printf("ERROR: Username no specified in authorized_keys\n");
  203     return 1;
  204   }
  205   user = argv[1];
  206   // Set the USERNAME environment variable
  207   setenv("USERNAME", user, 1);
  208 
  209   // Ensure a command was specified
  210   if(! getenv("SSH_ORIGINAL_COMMAND")) {
  211     sprintf(msg, "[%s] logged in without specifying a command", user);
  212     logmsg(msg);
  213     fprintf(stderr, "No soup for you!\n");
  214     return 1;
  215   }
  216 
  217   // Read the first command in the ssh
  218   sscanf(getenv("SSH_ORIGINAL_COMMAND"), "%128s '%128[^']'[^\n]", cmd, repo);
  219 
  220   if(is_git_cmd(cmd)) {
  221     // Read the repo path (command argument)
  222     if(!validate_git(user, repo)) {
  223       sprintf(msg, "[%s][%s] attempted invalid git command \"%s\"", \
  224           user, repo, cmd);
  225       logmsg(msg);
  226       return 1;
  227     }
  228   } else if(! is_allowed_cmd(cmd)) {
  229     sprintf(msg, "[%s][%s] attempted disallowed command \"%s\"", \
  230         user, repo, cmd);
  231     logmsg(msg);
  232     fprintf(stderr, "Command '%s' is not allowed\n", cmd);
  233     return 1;
  234   }
  235 
  236   sprintf(msg, "[%s][%s] executed \"%s\"", user, repo, cmd);
  237   logmsg(msg);
  238   sprintf(gitsh, "/usr/bin/env git-shell -c \"%s '%s'\"", cmd, repo);
  239   system(gitsh);
  240 
  241   return 0;
  242 }

Generated by cgit