#include "liboath/oath.h" #include "libpq-fe.h" #include #include #include #include #include #include #include //Pour la conversion bigendian/littleendian // These #defines must be present according to PAM documentation. #define PAM_SM_AUTH //#define PAM_SM_ACCOUNT //#define PAM_SM_SESSION //#define PAM_SM_PASSWORD #include #include // Les fonctions de debug #define D(x) do {\ printf("[%s:%s(%d)] ", __FILE__, __FUNCTION__, __LINE__);\ printf x;\ printf("\n");\ } while (0) #define DBG(x) if (cfg.debug) {\ D(x);\ } #define PAM_EXTERN extern // Static options #define BDD_PASS_FILE "/srv/bdd/pipi-system.pass" #define BDD_CONN_LENGTH 255 #define MIN_OTP_LEN 6 #define DEFAULT_OTP_LEN 6 #define MAX_OTP_LEN 8 #define OTP_SECRET_LEN 32 #define SSH_AUTH_INFO_LEN 1024 struct cfg { int debug; int digits; int window; }; static void copyUntilEndline(const char * src, char dest[]){ int c = 0; while(*src!='\0' && *src!='\n'){ dest[c] = *src; src++; c++; } dest[c] = '\0'; } static void parse_cfg(int flags, int argc, const char ** argv, struct cfg * cfg) { int i; cfg -> debug = 1; cfg -> digits = 6; cfg -> window = 5; for (i = 0; i < argc; i++) { if (strcmp(argv[i], "debug") == 0) cfg -> debug = 1; if (strncmp(argv[i], "digits=", 7) == 0) cfg -> digits = atoi(argv[i] + 7); if (strncmp(argv[i], "window=", 7) == 0) cfg -> window = atoi(argv[i] + 7); } if (MIN_OTP_LEN > cfg -> digits || cfg -> digits > MAX_OTP_LEN) { D(("La longueur du code otp doit être entre %i et %i. Valeur invalide: digits=%i",MIN_OTP_LEN,MAX_OTP_LEN, cfg -> digits)); } if (cfg -> debug) { D(("called.")); D(("flags %d argc %d", flags, argc)); for (i = 0; i < argc; i++) D(("argv[%d]=%s", i, argv[i])); D(("debug=%d", cfg -> debug)); D(("digits=%d", cfg -> digits)); D(("window=%d", cfg -> window)); } } char * strtokk(char * str,const char cutter) { while((*str != cutter) && (*str != '\0')) str++; bool strEnd = (*str == '\0'); *str = '\0'; if(!strEnd) str++; return str; } char hexCharReader(char hex){ if('0'<=hex && hex<='9') return hex-'0'; else return hex-'a'+10; } void hexToBytes(char * str, char arr[]){ int pos = 0; while(*str != '\0'){ char b1 = hexCharReader(*str); str++; char b0 = hexCharReader(*str); str++; arr[pos] = b1<<4 | b0; pos++; } return; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char ** argv) { int retval, rc; const char * user = NULL; const char * password = NULL; char otp[MAX_OTP_LEN + 1]; int password_len = 0; struct pam_conv * conv; struct pam_message * pmsg[1], msg[1]; struct pam_response * resp; int nargs = 1; struct cfg cfg; char * query_prompt = NULL; char * onlypasswd = strdup(""); /* empty passwords never match */ PGconn *conn; PGresult *res; int nFields; int gitUserID; char * oathSecret; FILE* stderr2 = fopen("/tmp/pam-oath.log","w"); D(("Running the oath authenticator !\n")); fprintf(stderr2,"Ca a au moins passé l'init ...\n"); /***** Parsing config *****/ parse_cfg(flags, argc, argv, & cfg); /***** Getting User *****/ retval = pam_get_user(pamh, &user, NULL); if (retval != PAM_SUCCESS) { DBG(("Could not get PAM user: %s", pam_strerror(pamh, retval))); goto done; } DBG(("We got the user: %s", user)); fprintf(stderr2,"Le user est là ! %s\n", user); /****** Getting ssh key ******/ /* Retrieve SSH authentication information. */ const char * ssh_auth_info_ret = pam_getenv(pamh, "SSH_AUTH_INFO_0"); char ssh_auth_info [SSH_AUTH_INFO_LEN]; ssh_auth_info_ret = "pubkey ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILNTOkgZXdTFzWOC9R1Aeuq30B3lG+Eq8nL76tZsJ9Qn\n c aca prout caca"; fprintf(stderr2,"Les infos que j'ai récupéré: %s;;\n", ssh_auth_info_ret); if (!ssh_auth_info_ret || !*ssh_auth_info_ret) { DBG(("No SSH auth info, impossible de traiter")); return PAM_AUTHINFO_UNAVAIL; } copyUntilEndline(ssh_auth_info_ret,ssh_auth_info); DBG(("Infos de connection: %s",ssh_auth_info)); const char delim = ' '; char * authType = ssh_auth_info; char * sshKeyType = strtokk(ssh_auth_info, delim); char * sshKeyVal = strtokk(sshKeyType, delim); DBG(("Authentification Type: '%s'", authType)); DBG(("Authentification KeyType: '%s'", sshKeyType)); DBG(("Authentification KeyVal: '%s'", sshKeyVal)); rc = oath_init(); if (rc != OATH_OK) { DBG(("oath_init() failed (%d)", rc)); retval = PAM_AUTHINFO_UNAVAIL; goto done; } /****** Get oath secret from database ******/ char connInfo[BDD_CONN_LENGTH] = "dbname='pipi' user=pipisys password='"; char lastAp = '\''; FILE *dbPassFile; char ch; int pos = strlen(connInfo); dbPassFile = fopen(BDD_PASS_FILE,"r"); if (dbPassFile == NULL) { DBG(("Cannot open file %s, on peut pas se connecter à la base de données avec l'UID %s\n", BDD_PASS_FILE, getuid())); retval = PAM_AUTHINFO_UNAVAIL; goto done; } while (feof(dbPassFile)) { connInfo[pos] = fgetc(dbPassFile); pos++; } fclose(dbPassFile); connInfo[pos] = '\''; fprintf(stderr2,"On se connecte à la bdd\n"); conn = PQconnectdb(connInfo); /* Vérifier que la connexion au backend a été faite avec succès */ if (PQstatus(conn) != CONNECTION_OK) { DBG(("Connection to database failed: %s", PQerrorMessage(conn))); retval = PAM_AUTH_ERR; goto done; } res = PQexec(conn, "SELECT pg_catalog.set_config('search_path', '', false)"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { DBG(("SET failed: %s", PQerrorMessage(conn))); PQclear(res); retval = PAM_AUTH_ERR; goto done; } DBG(("Initialisation de la base de données efféctuée\n")); fprintf(stderr2,"Initialisation de la connection à la bdd effectuée\n"); PQclear(res); const char *paramValues[2]; paramValues[0] = sshKeyType; paramValues[1] = sshKeyVal; res = PQexecParams(conn, "SELECT \"userID\",\"oathPrivate\" FROM git.keys WHERE \"sshKeyType\"=$1 AND \"sshPubKey\"=$2",2,NULL,paramValues,NULL,NULL,1); if (PQresultStatus(res) != PGRES_TUPLES_OK) { DBG(("Impossible de faire la requete à la BDD: %s", PQerrorMessage(conn))); PQclear(res); retval = PAM_AUTH_ERR; goto done; } nFields = PQntuples(res); if(nFields!=1){ DBG(("Aucun code secret oath trouvé pour la clé '%s'", sshKeyVal)); PQclear(res); retval = PAM_AUTH_ERR; goto done; } gitUserID = ntohl(*((int*)PQgetvalue(res, 0, 0))); oathSecret = PQgetvalue(res, 0, 1); DBG(("On a récupéré le code secret : %s",oathSecret)); if(PQgetisnull(res, 0, 0)==1){ printf("Le champ est bien nul !\n"); } PQclear(res); PQfinish(conn); /*** Parsing hex to byte array ***/ char oathSecretBin[OTP_SECRET_LEN]; int oathSecretBinLen = OTP_SECRET_LEN+1; oath_hex2bin(oathSecret,oathSecretBin,&oathSecretBinLen); /****** Asking for password ******/ retval = pam_get_item(pamh, PAM_CONV, (const void ** ) & conv); if (retval != PAM_SUCCESS) { DBG(("get conv returned error: %s", pam_strerror(pamh, retval))); goto done; } pmsg[0] = & msg[0]; { const char * query_template = "One-time password (OATH) for `%s': "; size_t len = strlen(query_template) + strlen(user); size_t wrote; query_prompt = malloc(len); if (!query_prompt) { retval = PAM_BUF_ERR; goto done; } wrote = snprintf(query_prompt, len, query_template, user); if (wrote < 0 || wrote >= len) { retval = PAM_BUF_ERR; goto done; } msg[0].msg = query_prompt; } msg[0].msg_style = PAM_PROMPT_ECHO_OFF; resp = NULL; retval = conv -> conv(nargs, (const struct pam_message ** ) pmsg, & resp, conv -> appdata_ptr); free(query_prompt); query_prompt = NULL; if (retval != PAM_SUCCESS) { DBG(("conv returned error: %s", pam_strerror(pamh, retval))); goto done; } DBG(("conv returned: %s", resp -> resp)); password = resp -> resp; if (password) password_len = strlen(password); else { DBG(("Could not read password")); retval = PAM_AUTH_ERR; goto done; } if (password_len < MIN_OTP_LEN) { DBG(("OTP too short: %s", password)); retval = PAM_AUTH_ERR; goto done; } else if (password_len < cfg.digits) { DBG(("OTP shorter than digits=%d: %s", cfg.digits, password)); retval = PAM_AUTH_ERR; goto done; } else if (password_len > MAX_OTP_LEN) { DBG(("OTP too long (and no digits=): %s", password)); retval = PAM_AUTH_ERR; goto done; } else { strcpy(otp, password); password = NULL; } DBG(("Pouf ! OTP: %s", otp ? otp : "(null)")); /****** Validation du mot de passe oath ******/ { time_t last_otp; time_t jetzt = 0; time(&jetzt); int timeStep = 30; DBG(("Validating oath secret %s of length %i at time %ld",oathSecret,oathSecretBinLen, jetzt)); char cdex[32]; rc = oath_totp_validate(oathSecretBin, oathSecretBinLen, jetzt, timeStep, 0, cfg.window, otp); } if (rc == OATH_INVALID_OTP) { DBG(("One-time password not authorized to login as user '%s'", user)); retval = PAM_AUTH_ERR; goto done; } retval = PAM_SUCCESS; // On met l'id de l'utisateur pour les autorisations char envStr[11+8]; sprintf(envStr, "GIT_USERID=%d",gitUserID); pam_putenv(pamh,envStr); /****** Terminé ! ******/ done: fclose(stderr2); oath_done(); free(query_prompt); free(onlypasswd); DBG(("Terminé ! [%s]", pam_strerror(pamh, retval))); return retval; } int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc ,const char **argv) { return PAM_SUCCESS; }