#include "liboath/oath.h" #include "libpq-fe.h" #include #include #include #include #include #include #include //Pour la conversion bigendian/littleendian // Qu'es-ce qui est défini #define PAM_SM_AUTH #include #include #include #include #include // Les fonctions de debug #define D(...) \ pam_syslog(pamh, LOG_NOTICE, ##__VA_ARGS__); #define DBG(...) if(cfg.debug) {\ pam_syslog(pamh, LOG_NOTICE, ##__VA_ARGS__);\ } #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(pam_handle_t * pamh, 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++; int 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 * 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; const char * ssh_auth_info_ret; char ssh_auth_info [SSH_AUTH_INFO_LEN]; int gitUserID; char * oathSecret; char * authType; char * sshKeyType; char * sshKeyVal; char connInfo[BDD_CONN_LENGTH] = "dbname='pipi' user=pipisys password='"; FILE *dbPassFile; int pos = strlen(connInfo); const char *paramValues[2]; char oathSecretBin[OTP_SECRET_LEN]; int oathSecretBinLen = OTP_SECRET_LEN+1; //GIT_USERID=######## char envStr[11+8]; D("Running the oath authenticator !\n"); D("Environement récupéré:\n"); char** envp = pam_getenvlist(pamh); for (char **env = envp; *env != 0; env++) { char *thisEnv = *env; printf("%s\n", thisEnv); } /***** Parsing config *****/ parse_cfg(pamh, flags, argc, argv, &cfg); /****** Retrieve SSH authentication information. ******/ ssh_auth_info_ret = pam_getenv(pamh, "SSH_AUTH_INFO_0"); if (!ssh_auth_info_ret || !*ssh_auth_info_ret) { D("Impossible de récupérer les données de connection SSH, impossible de traiter"); return PAM_AUTHINFO_UNAVAIL; } copyUntilEndline(ssh_auth_info_ret,ssh_auth_info); DBG("Infos de connection: %s",ssh_auth_info); authType = ssh_auth_info; sshKeyType = strtokk(ssh_auth_info, ' '); sshKeyVal = strtokk(sshKeyType, ' '); 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 ******/ 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] = '\''; 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"); PQclear(res); 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); PQclear(res); PQfinish(conn); /*** Parsing hex to byte array ***/ 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]; msg[0].msg = "Votre mot de passe OTP: "; 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 trop court: %s", password); retval = PAM_AUTH_ERR; goto done; } else if (password_len < cfg.digits) { DBG("OTP trop court, on a demandé plus de chiffres: %s", cfg.digits, password); retval = PAM_AUTH_ERR; goto done; } else if (password_len > MAX_OTP_LEN) { DBG("OTP trop long: %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; int timeStep = 30; time(&jetzt); DBG("Validation du code secret %s, longueur %i, temps %ld",oathSecret,oathSecretBinLen,jetzt); rc = oath_totp_validate(oathSecretBin, oathSecretBinLen, jetzt, timeStep, 0, cfg.window, otp); } if (rc == OATH_INVALID_OTP) { DBG("Mot de passe OTP invalide."); retval = PAM_AUTH_ERR; goto done; } retval = PAM_SUCCESS; /****** On met l'id de l'utisateur pour les autorisations ******/ sprintf(envStr, "GIT_USERID=%d",gitUserID); pam_putenv(pamh,envStr); /****** Terminé ! ******/ done: 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; }