git4pipi/pam_oath_key.c
2021-07-28 22:52:43 +02:00

391 lines
9.4 KiB
C

#include "liboath/oath.h"
#include "libpq-fe.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h> //Pour la conversion bigendian/littleendian
// Qu'es-ce qui est défini
#define PAM_SM_AUTH
#include <syslog.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_modutil.h>
#include <security/pam_ext.h>
// 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;
}