/* This file is part of GNU Pies.
Copyright (C) 2015-2023 Sergey Poznyakoff
GNU Pies is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Pies is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Pies. If not, see . */
#include "ident.h"
#include
#include
struct pam_identity_provider_data
{
char *service;
};
struct pam_cred
{
const char *user;
const char *pass;
};
#define COPY_STRING(s) (s) ? strdup(s) : NULL
#define overwrite_and_free(ptr) \
do { \
char *s = ptr; \
while (*s) \
*s++ = 0; \
free (ptr); \
} while (0)
#ifndef PAM_AUTHTOK_RECOVER_ERR
# define PAM_AUTHTOK_RECOVER_ERR PAM_CONV_ERR
#endif
static int
pies_conv (int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
int status = PAM_SUCCESS;
int i;
struct pam_response *reply = NULL;
struct pam_cred *cred = appdata_ptr;
reply = calloc (num_msg, sizeof (*reply));
if (!reply)
return PAM_CONV_ERR;
for (i = 0; i < num_msg && status == PAM_SUCCESS; i++)
{
switch (msg[i]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
reply[i].resp_retcode = PAM_SUCCESS;
reply[i].resp = COPY_STRING (cred->user);
/* PAM frees resp */
break;
case PAM_PROMPT_ECHO_OFF:
if (cred->pass)
{
reply[i].resp_retcode = PAM_SUCCESS;
reply[i].resp = COPY_STRING (cred->pass);
/* PAM frees resp */
} else
status = PAM_AUTHTOK_RECOVER_ERR;
break;
case PAM_TEXT_INFO:
case PAM_ERROR_MSG:
reply[i].resp_retcode = PAM_SUCCESS;
reply[i].resp = NULL;
break;
default:
status = PAM_CONV_ERR;
}
}
if (status != PAM_SUCCESS)
{
for (i = 0; i < num_msg; i++)
if (reply[i].resp)
{
switch (msg[i]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
case PAM_PROMPT_ECHO_OFF:
overwrite_and_free (reply[i].resp);
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
free (reply[i].resp);
}
}
free (reply);
} else
*resp = reply;
return status;
}
static int
authenticate (pies_identity_provider_t provider,
pies_identity_t id, char const *passwd)
{
struct pam_identity_provider_data *pamdata = provider->data;
pam_handle_t *pamh;
int pamerror;
struct pam_cred cred = { id->username, passwd };
struct pam_conv pam_conv = { &pies_conv, &cred };
char const *service = pamdata->service ? pamdata->service : "pies";
do
{
pamerror = pam_start (service, id->username, &pam_conv, &pamh);
if (pamerror != PAM_SUCCESS)
break;
pamerror = pam_authenticate (pamh, 0);
if (pamerror != PAM_SUCCESS)
break;
pamerror = pam_acct_mgmt (pamh, 0);
if (pamerror != PAM_SUCCESS)
break;
pamerror = pam_setcred (pamh, PAM_ESTABLISH_CRED);
}
while (0);
pam_end (pamh, PAM_SUCCESS);
switch (pamerror)
{
case PAM_SUCCESS:
return 0;
case PAM_AUTH_ERR:
return -1;
}
//FIXME _log(L_ERR, 0, "PAM authentication error");
return -1;
}
static int
is_group_member (pies_identity_provider_t p,
pies_identity_t id, char * const * groups)
{
struct group *gr;
int result = 0;
setgrent ();
while (!result && (gr = getgrent ()))
{
if (is_array_member (groups, gr->gr_name))
result = is_array_member (gr->gr_mem, id->username);
}
endgrent ();
return result;
}
static struct grecs_keyword pam_kw[] = {
{ "type", "'pam", "Set mechanism type", grecs_type_null },
{ "service", NULL, "Service name",
grecs_type_string, GRECS_DFLT, NULL,
offsetof(struct pam_identity_provider_data, service) },
{ NULL }
};
static int
configure (struct grecs_node *node, pies_identity_provider_t provider)
{
int i;
struct pam_identity_provider_data *data = grecs_calloc (1, sizeof (*data));
provider->data = data;
for (i = 0; pam_kw[i].ident; i++)
pam_kw[i].varptr = data;
if (grecs_tree_process (node->down, pam_kw))
{
//FIXME: memory leak
return -1;
}
provider->locus = node->locus;
return 0;
}
static void
confhelp (void)
{
static struct grecs_keyword top[] = {
{ "identity-provider", "name: string",
"Configuration for system identity provider",
grecs_type_section, GRECS_INAC, NULL, 0, NULL, NULL,
pam_kw },
{ NULL }
};
grecs_print_statement_array (top, 1, 0, stdout);
}
struct pies_identity_mechanism pam_identity_mechanism = {
"pam",
authenticate,
is_group_member,
NULL,
configure,
confhelp
};