391 lines
9.4 KiB
C
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;
|
|
}
|
|
|
|
|