#include #include #include #include #include "nargv/nargv.h" #include "libpq-fe.h" #include /* for fork */ #include /* for pid_t */ #include /* for wait */ #define ANSI_COLOR_GREEN "\x1b[32m" #define ANSI_COLOR_RESET "\x1b[0m" #define AUTHORIZED_SHELL_COMMAND "bash-gitonly" #define AUTHORIZED_SHELL_FLAG "-c" #define AUTHORIZED_COMMANDS "git-receive-pack","git-upload-pack","git-upload-archive" #define AUTHORIZED_COMMANDS_COUNT 3 #define COMMANDS_PATH "/bin/" #define MAX_FULL_EXECFILENAME_LENGTH 24 #define BDD_PASS_FILE "/pipi-system.pass" #define BDD_CONN_LENGTH 255 #define ERR(...) \ fprintf(stderr, ANSI_COLOR_GREEN);\ fprintf(stderr, ##__VA_ARGS__);\ fprintf(stderr,ANSI_COLOR_RESET "\n"); int strArrMem(char * el, const char * arr[]){ int i = 0; while(arr[i]!=NULL){ if(strcmp(el,arr[i]) == 0) return 1; i++; } return 0; } int main(int argc, char **argv, char **envp){ const char const * authorizedCommands[] = {AUTHORIZED_COMMANDS}; NARGV* gitargv; char* repoName; char connInfo[BDD_CONN_LENGTH] = "dbname='pipi' user=pipisys password='"; FILE *dbPassFile; int pos = strlen(connInfo); char * userID; PGconn *conn; PGresult *res; int nFields; const char *accessTypeRequestValues[2]; char* niveauAutorisation; char execFilename[MAX_FULL_EXECFILENAME_LENGTH] = COMMANDS_PATH; pid_t gitPid; // Ce bout de code est ultra-restrictif et risque de planter si SSH change. Le message sera alors reconaissable. if(argc !=3 || (strcmp(argv[0],AUTHORIZED_SHELL_COMMAND)!=0 && strcmp(argv[0],COMMANDS_PATH AUTHORIZED_SHELL_COMMAND)) || strcmp(argv[1],AUTHORIZED_SHELL_FLAG)!=0){ ERR("Pas de bash ici, il vous faut un «vrai» accès SSH"); return 1; } // Parsing de la commande git gitargv = nargv_parse(argv[2]); if (gitargv->error_code) { ERR("Impossible de comprendre la commande, erreur nargv %i: %s: at input column %i", gitargv->error_code, gitargv->error_message, gitargv->error_index); return 1; } // Verification que la commande envoyée est bien une commande git if(strArrMem(gitargv->argv[0],authorizedCommands)==0){ ERR("Wow ! ce canal est réservé aux commandes de données git, pour le reste, utilisez un «vrai» accès SSH"); nargv_free(gitargv); return 1; } // Récupération de l'utilisateur userID = getenv("GIT_USERID"); if(userID==NULL){ ERR("Impossible de récupérer votre identifiant utilisateur ..."); nargv_free(gitargv); return 1; } // Récupération du repo et du droit d'accès demandés // Normalement, la commande n'a que deux éléments: repoName = gitargv->argv[1]; dbPassFile = fopen(BDD_PASS_FILE,"r"); if (dbPassFile == NULL) { ERR("Cannot open file %s, on peut pas se connecter à la base de données en tant que l'uid %d", BDD_PASS_FILE, geteuid()); nargv_free(gitargv); return 1; } while (feof(dbPassFile)) { connInfo[pos] = fgetc(dbPassFile); pos++; } fclose(dbPassFile); connInfo[pos] = '\''; conn = PQconnectdb(connInfo); /* Vérifier que la connexion au backend a été faite avec succès */ if (PQstatus(conn) != CONNECTION_OK) { ERR("Connection to database failed: %s",PQerrorMessage(conn)); nargv_free(gitargv); return 1; } res = PQexec(conn, "SELECT pg_catalog.set_config('search_path', '', false)"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { ERR("SET failed: %s", PQerrorMessage(conn)); PQclear(res); nargv_free(gitargv); return 1; } PQclear(res); // Récupération de l'autorisation auprès du serveur Pgsql accessTypeRequestValues[0] = userID; accessTypeRequestValues[1] = repoName; res = PQexecParams(conn, "SELECT git.\"AccessType\"($1,$2)",2,NULL,accessTypeRequestValues,NULL,NULL,1); if (PQresultStatus(res) != PGRES_TUPLES_OK) { ERR("Impossible de lancer la requête SQL pour les autorisations: %s",PQerrorMessage(conn)); PQclear(res); nargv_free(gitargv); PQfinish(conn); return 1; } nFields = PQntuples(res); // Il y a toujours une seule valeur retournée, renvoie NULL si il n'y a rien niveauAutorisation = PQgetvalue(res, 0, 0); PQclear(res); PQfinish(conn); //ERR("Autorisation: sur %s pour l'id %s -> %s",repoName,userID,niveauAutorisation)); if(niveauAutorisation[0]=='\0'){// If the string is empty i.e. NULL authorisations ERR("Vous n'avez pas le droit d'accéder à ce repo. Il n'existe peut-être même pas ..."); nargv_free(gitargv); return 1; } // Déjà on a la lecture if(strcmp(gitargv->argv[0],"git-receive-pack")==0){ // Il nous faut aussi le droit d'écriture if(strcmp(niveauAutorisation,"WRITE")!=0){ ERR("Vous n'avez pas le droit d'écrire dans ce repo."); nargv_free(gitargv); return 1; } } // On effectue la commande. // Pour l'instant execFilename = COMMANDS_PATH; strcat(execFilename, gitargv->argv[0]); gitPid=fork(); if (gitPid==0) { /* child process */ execve(execFilename, gitargv->argv, (char *const []){NULL}); // Pas d'environement exit(127); /* only if execv fails */ } else { /* pid!=0; parent process */ waitpid(gitPid,0,0); /* wait for child to exit */ } nargv_free(gitargv); return 1; }