#include "liboath/oath.h" #include "libpq-fe.h" #include #include #include #include // 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 parse_cfg(int flags, int argc, const char ** argv, struct cfg * cfg) { int i; cfg -> debug = 0; 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; char * oathSecret; /***** 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)); /****** Getting ssh key ******/ /* Retrieve SSH authentication information. */ char * ssh_auth_info_ret = (char *)pam_getenv(pamh, "SSH_AUTH_INFO_0"); char ssh_auth_info [SSH_AUTH_INFO_LEN]; ssh_auth_info_ret = "pubkey ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKXQt1YWjKCsjcsFW7o1hdjAB/qxWBwesAeV0RcBeW0I"; strcpy(ssh_auth_info,ssh_auth_info_ret); if (!ssh_auth_info || !*ssh_auth_info) { DBG(("No SSH auth info, du coup on ignore")); return PAM_IGNORE; } 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-system' user=pipiadmin 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\n", BDD_PASS_FILE)); 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")); PQclear(res); const char *paramValues[2]; paramValues[0] = sshKeyType; paramValues[1] = sshKeyVal; res = PQexecParams(conn, "SELECT \"oathPrivate\" FROM login.git WHERE \"sshKeyType\"=$1 AND \"sshPubKey\"=$2",2,NULL,paramValues,NULL,NULL,1); if (PQresultStatus(res) != PGRES_TUPLES_OK) { DBG(("SET failed: %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; } oathSecret=PQgetvalue(res, 0, 0); DBG(("On a récupéré le code secret : %s",oathSecret)); 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; /****** Terminé ! ******/ done: oath_done(); free(query_prompt); free(onlypasswd); DBG(("Terminé ! [%s]", pam_strerror(pamh, retval))); return retval; } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc ,const char **argv) { return PAM_SUCCESS; }