Files
elog/src/auth.cxx
T
2022-02-11 16:41:24 +01:00

639 lines
22 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/********************************************************************\
Name: auth.c
Created by: Stefan Ritt
Copyright 2000 + Stefan Ritt
ELOG 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 of the License, or
(at your option) any later version.
ELOG 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 ELOG. If not, see <http://www.gnu.org/licenses/>.
Contents: Authentication subroutines. Currently supported:
- password file authentication
- kerberos5 password authentication
$Id: elog.c 2350 2010-12-23 10:45:10Z ritt $
\********************************************************************/
#include "elogd.h"
#ifdef HAVE_KRB5
#include <krb5.h>
#endif
#ifdef HAVE_LDAP
#include <ldap.h>
LDAP *ldap_ld;
char ldap_login_attr[64];
char ldap_userbase[256];
char ldap_bindDN[512];
#endif /* HAVE_LDAP */
#ifdef HAVE_PAM
#include <security/pam_appl.h>
#endif /* HAVE_PAM */
extern LOGBOOK *lb_list;
/*==================================================================*/
/*---- Kerberos5 routines ------------------------------------------*/
#ifdef HAVE_KRB5
int auth_verify_password_krb5(LOGBOOK * lbs, const char *user, const char *password, char *error_str,
int error_size)
{
char *princ_name, str[256], realm[256];
krb5_error_code error;
krb5_principal princ;
krb5_context context;
krb5_creds creds;
krb5_get_init_creds_opt options;
if (krb5_init_context(&context) < 0)
return FALSE;
strlcpy(str, user, sizeof(str));
if (getcfg(lbs->name, "Kerberos Realm", realm, sizeof(realm))) {
strlcat(str, "@", sizeof(str));
strlcat(str, realm, sizeof(str));
}
if ((error = krb5_parse_name(context, str, &princ)) != 0) {
strlcpy(error_str, "<b>Kerberos error:</b><br>", error_size);
strlcat(error_str, krb5_get_error_message(context, error), error_size);
strlcat(error_str, ".<br>Please check your Kerberos configuration.", error_size);
return FALSE;
}
error = krb5_unparse_name(context, princ, &princ_name);
if (error) {
strlcpy(error_str, "<b>Kerberos error:</b><br>", error_size);
strlcat(error_str, krb5_get_error_message(context, error), error_size);
strlcat(error_str, ".<br>Please check your Kerberos configuration.", error_size);
return FALSE;
}
sprintf(str, "Using %s as server principal for authentication", princ_name);
write_logfile(lbs, str);
memset(&options, 0, sizeof(options));
krb5_get_init_creds_opt_init(&options);
memset(&creds, 0, sizeof(creds));
error = krb5_get_init_creds_password(context, &creds, princ,
(char *) password, NULL, NULL, 0, NULL, &options);
krb5_free_context(context);
if (error && error != KRB5KDC_ERR_PREAUTH_FAILED && error != KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN) {
sprintf(error_str, "<b>Kerberos error %d:</b><br>", error);
strlcat(error_str, krb5_get_error_message(context, error), error_size);
strlcat(error_str, ".<br>Please check your Kerberos configuration.", error_size);
return FALSE;
}
if (error)
return FALSE;
return TRUE;
}
int auth_change_password_krb5(LOGBOOK * lbs, const char *user, const char *old_pwd, const char *new_pwd,
char *error_str, int error_size)
{
char *princ_name, str[256], realm[256];
int result_code, n;
krb5_error_code error;
krb5_data result_code_string, result_string;
krb5_principal princ;
krb5_context context;
krb5_creds creds;
krb5_get_init_creds_opt options;
if (krb5_init_context(&context) < 0)
return FALSE;
strlcpy(str, user, sizeof(str));
if (getcfg(lbs->name, "Kerberos Realm", realm, sizeof(realm))) {
strlcat(str, "@", sizeof(str));
strlcat(str, realm, sizeof(str));
}
if ((error = krb5_parse_name(context, str, &princ)) != 0) {
strlcpy(error_str, "<b>Kerberos error:</b><br>", error_size);
strlcat(error_str, krb5_get_error_message(context, error), error_size);
strlcat(error_str, ".<br>Please check your Kerberos configuration.", error_size);
return FALSE;
}
error = krb5_unparse_name(context, princ, &princ_name);
sprintf(str, "Using %s as server principal for authentication", princ_name);
write_logfile(lbs, str);
memset(&options, 0, sizeof(options));
krb5_get_init_creds_opt_init(&options);
krb5_get_init_creds_opt_set_tkt_life(&options, 300);
krb5_get_init_creds_opt_set_forwardable(&options, FALSE);
krb5_get_init_creds_opt_set_proxiable(&options, FALSE);
memset(&creds, 0, sizeof(creds));
error = krb5_get_init_creds_password(context, &creds, princ,
(char *) old_pwd, NULL, NULL, 0, "kadmin/changepw", &options);
if (error) {
strlcpy(error_str, "<b>Kerberos error:</b><br>", error_size);
strlcat(error_str, krb5_get_error_message(context, error), error_size);
strlcat(error_str, ".<br>Please check your Kerberos configuration.", error_size);
return FALSE;
}
error = krb5_set_password(context, &creds, (char *) new_pwd, princ,
&result_code, &result_code_string, &result_string);
if (error) {
strlcpy(error_str, "<b>Kerberos error:</b><br>", error_size);
strlcat(error_str, krb5_get_error_message(context, error), error_size);
strlcat(error_str, ".<br>Please check your Kerberos configuration.", error_size);
return FALSE;
}
if (result_code > 0) {
if (result_code_string.length > 0) {
strlcpy(error_str, result_code_string.data, error_size);
if ((int) result_code_string.length < error_size)
error_str[result_code_string.length] = 0;
}
if (result_string.length > 0) {
strlcat(error_str, ": ", error_size);
n = strlen(error_str) + result_string.length;
strlcat(error_str, result_string.data, error_size);
if (n < error_size)
error_str[n] = 0;
}
}
krb5_free_data_contents(context, &result_code_string);
krb5_free_data_contents(context, &result_string);
krb5_free_cred_contents(context, &creds);
krb5_get_init_creds_opt_free(context, &options);
krb5_free_context(context);
if (result_code > 0)
return FALSE;
return TRUE;
}
#endif
/*---- LDAP routines ------------------------------------------*/
#ifdef HAVE_LDAP
int ldap_init(LOGBOOK *lbs, char *error_str, int error_size)
{
char str[512], ldap_server[256];
int ii, version;
// Read Config file
if (getcfg(lbs->name, "LDAP server", ldap_server, sizeof(ldap_server))) {
strlcpy(str, ldap_server, sizeof(str));
}
else {
strlcpy(error_str, "<b>LDAP initialization error</b><br>", error_size);
strlcat(error_str, "<br>Please check your LDAP configuration.", error_size);
strlcat(str, "ERR: Cannot find LDAP server entry!", sizeof(str));
write_logfile(lbs, str);
return FALSE;
}
if (!getcfg(lbs->name, "LDAP userbase", ldap_userbase, sizeof(ldap_userbase))) {
strlcpy(error_str, "<b>LDAP initialization error</b><br>", error_size);
strlcat(error_str, "<br>Please check your LDAP configuration.", error_size);
strlcat(str, ", ERR: Cannot find LDAP userbase (e.g. \'ou=People,dc=example,dc=org\')!", sizeof(str));
write_logfile(lbs, str);
return FALSE;
}
if (!getcfg(lbs->name, "LDAP login attribute", ldap_login_attr, sizeof(ldap_login_attr))) {
strlcpy(error_str, "<b>LDAP initialization error</b><br>", error_size);
strlcat(error_str, "<br>Please check your LDAP configuration.", error_size);
strlcat(str, ", ERR: Cannot find LDAP login attribute (e.g. uid, cn, ...)!", sizeof(str));
write_logfile(lbs, str);
return FALSE;
}
// Initialize/open LDAP connection
if(ldap_initialize( &ldap_ld, ldap_server )) {
perror("ldap_initialize");
strlcpy(error_str, "<b>LDAP initialization error</b><br>", error_size);
strlcat(error_str, "<br>Please check your LDAP configuration.", error_size);
return FALSE;
}
// Use the LDAP_OPT_PROTOCOL_VERSION session preference to specify that the client is LDAPv3 client
version = LDAP_VERSION3;
ldap_set_option(ldap_ld, LDAP_OPT_PROTOCOL_VERSION, &version);
write_logfile(lbs, str);
return TRUE;
}
int auth_verify_password_ldap(LOGBOOK *lbs, const char *user, const char *password, char *error_str,
int error_size)
{ LDAPMessage *result, *err;
int bind=0, ii;
char str[512];
ldap_ld = NULL;
memset(&ldap_bindDN[0], 0, sizeof(ldap_bindDN));
if (!strcmp(password, "")) {
strlcpy(error_str, "<b>LDAP authentication failed, no password provided</b>", error_size);
write_logfile(lbs, "LDAP authentication failed, no password provided");
return FALSE;
}
if (!ldap_init(lbs,error_str, error_size)) {
strlcpy(error_str, "<b>LDAP initialization error</b><br>", error_size);
strlcat(error_str, "<br>Please check your LDAP configuration.", error_size);
return FALSE;
}
// Form LDAP bind DN (distinguished name):
// login_attr=user,ldap_userbase, e.g. uid=tuser,ou=People,dc=example,dc=org
sprintf(ldap_bindDN,"%s=%s,%s",ldap_login_attr,user,ldap_userbase);
strlcat(str, "Connecting as: ", sizeof(str));
strlcat(str, ldap_bindDN, sizeof(str));
write_logfile(lbs, str);
// User authentication (bind)
bind = ldap_simple_bind_s(ldap_ld, ldap_bindDN, password);
if( bind != LDAP_SUCCESS ) {
strlcpy(error_str, "<b>LDAP authentication error:</b><br>", error_size);
strlcat(error_str, ldap_err2string(bind), error_size);
strlcat(error_str, ".<br>Please check your user/password or LDAP configuration.", error_size);
strlcpy(str, "LDAP Authentication: FAILED!", sizeof(str));
write_logfile(lbs, str);
ldap_unbind(ldap_ld);
return FALSE;
}
strlcpy(str, "LDAP Authentication: Success!", sizeof(str));
ldap_unbind(ldap_ld);
write_logfile(lbs, str);
return TRUE;
}
int ldap_adduser_file(LOGBOOK *lbs, const char *user, const char *password, char *error_str,
int error_size)
{ LDAPMessage *result, *entry;
char *attribute, **values;
char str[512], filter[512];
BerElement *ber;
int bind=0, rc=0, i;
char lbs_str[256], user_str[256], user_enc[256], fullname[256], usergn[128], usersn[128], useremail[256];
PMXML_NODE node, npwd;
struct timeval timeOut = {3,0}; // 3 second connection/search timeout
// zerotime.tv_sec = zerotime.tv_usec = 0L;
write_logfile(lbs, "New user: getting userdata from LDAP...");
if(!ldap_init(lbs,error_str,error_size)) {
return FALSE;
}
// User authentication (bind)
bind = ldap_simple_bind_s(ldap_ld, ldap_bindDN, password);
if( bind != LDAP_SUCCESS ) {
strlcpy(error_str, "<b>LDAP authentication error:</b><br>", error_size);
strlcat(error_str, ldap_err2string(bind), error_size);
strlcat(error_str, ".<br>Please check your user/password or LDAP configuration.", error_size);
strlcat(str, "LDAP Authentication: FAILED!", sizeof(str));
write_logfile(lbs, str);
ldap_unbind(ldap_ld);
return FALSE;
}
// form LDAP filter to find the user;
sprintf(filter, "(%s=%s)", ldap_login_attr, user);
// below based on: http://www.djack.com.pl/modules.php?name=FAQ&myfaq=yes&xmyfaq=yes&id_cat=7&id=183 (code errors!)
// AND on: http://www-archive.mozilla.org/directory/csdk-docs/example.htm
// Get user's first name, surname and email from LDAP
rc = ldap_search_ext_s(
ldap_ld, // LDAP session handle
ldap_userbase, // Search Base
LDAP_SCOPE_SUBTREE, // Search Scope everything below o=Acme
filter, // Search Filter only inetOrgPerson objects
NULL, // returnAllAttributes NULL means Yes
0, // attributesOnly False means we want values
NULL, // Server controls There are none
NULL, // Client controls There are none
&timeOut, // search Timeout
LDAP_NO_LIMIT, // no size limit
&result);
if (rc != LDAP_SUCCESS) {
strlcat(str, "LDAP search returned error: ", sizeof(str));
strlcat(str, ldap_err2string(rc), sizeof(str));
write_logfile(lbs, str);
ldap_unbind(ldap_ld);
return FALSE;
}
for(entry = ldap_first_entry(ldap_ld,result);
entry != NULL;
entry = ldap_next_entry(ldap_ld,entry) ) {
for(attribute = ldap_first_attribute(ldap_ld,entry,&ber);
attribute != NULL;
attribute = ldap_next_attribute(ldap_ld,entry,ber) ) {
// For each attribute, print the attribute name and values. //
if((values = ldap_get_values(ldap_ld,entry,attribute)) != NULL ) {
for(i=0; values[i] != NULL; i++) {
if(strcmp(attribute,"givenName")==0 || strcmp(attribute,"gn")==0)
strlcpy(usergn, values[i], sizeof(usergn));
if(strcmp(attribute,"sn")==0 || strcmp(attribute,"surname")==0)
strlcpy(usersn, values[i], sizeof(usersn));
if(strcmp(attribute,"mail")==0 || strcmp(attribute,"rfc822Mailbox")==0)
strlcpy(useremail, values[i], sizeof(useremail));
}
ldap_value_free(values);
}
ldap_memfree(attribute);
}
if(ber != NULL) ber_free(ber,0);
}
ldap_msgfree(result);
ldap_unbind(ldap_ld);
sprintf(fullname, "%s %s", usergn, usersn);
// Add user from LDAP in the local password file
// do not allow HTML in user name
strencode2(user_enc, user, sizeof(user_enc));
sprintf(lbs_str, "/list/user[name=%s]", user_enc);
node = mxml_find_node(lbs->pwd_xml_tree, lbs_str);
node = mxml_find_node(lbs->pwd_xml_tree, "/list");
if (!node) {
show_error(loc("Error accessing password file"));
return 0;
}
node = mxml_add_node(node, "user", NULL);
mxml_add_node(node, "name", user_enc); // add user login from LDAP;
do_crypt(password, user_str, sizeof(str));
npwd = mxml_add_node(node, "password", user_str); // add user password;
if (npwd) mxml_add_attribute(npwd, "encoding", "SHA256"); // user password is encoded;
strencode2(user_str, fullname, sizeof(str)); // add full user name from LDAP;
mxml_add_node(node, "full_name", user_str);
mxml_add_node(node, "last_logout", "0"); // last logout;
mxml_add_node(node, "last_activity", "0"); // last activity;
mxml_add_node(node, "email", useremail); // add user email from LDAP;
mxml_add_node(node, "inactive", "0");
sprintf(str,"New user: %s, %s added", user_enc, useremail);
write_logfile(lbs, str);
return TRUE;
}
/*---- clear ldap_ld and ldap_bindDN ----*/
int ldap_clear ()
{
ldap_ld = NULL;
memset(&ldap_bindDN[0], 0, sizeof(ldap_bindDN));
return TRUE;
}
#endif /* LDAP */
/* PAM authentication routines */
#ifdef HAVE_PAM
/* we need a custom PAM conversation function to handle acquiring the auth
* token (password) from the web formular, and hand it to PAM */
int elog_conv(int num_msg, const struct pam_message **mess, struct pam_response **resp, void *my_data)
{
char *resptok;
/* no PAM message received, this is an error */
if(num_msg <= 0 || num_msg >= PAM_MAX_NUM_MSG) {
*resp = NULL;
return (PAM_CONV_ERR);
}
/* if we do not have enough space to allocate the response, we have an error
* */
if((*resp = calloc(num_msg, sizeof(struct pam_response))) == NULL)
return (PAM_BUF_ERR);
/* this is the password we got through the UI, this is put into the
* response, and given to pam_authenticate */
if(!(resptok = strdup(my_data))) {
free(resp);
return (PAM_BUF_ERR);
}
/* set the response to our auth token (password) */
(*resp)->resp = resptok;
return (PAM_SUCCESS);
}
int auth_verify_password_pam(LOGBOOK *lbs, const char *user, const char *password, char *error_str, int error_size)
{
pam_handle_t *pamh;
int retval;
char str[256];
int verified = 0;
/* use our custom conversation function */
static struct pam_conv elog_pam_conv = {
elog_conv,
NULL
};
/* set conversation application data to our password */
elog_pam_conv.appdata_ptr = (char *)password;
/* start PAM auth procedure */
sprintf(str, "[PAM] Starting authentication for user %s", user);
write_logfile(lbs, str);
retval = pam_start("elogd", user, &elog_pam_conv, &pamh);
/* if we can use PAM, try to authenticate using our conversation method */
if(retval == PAM_SUCCESS) {
retval = pam_authenticate(pamh, 0);
}
/* if the user authenticated, see if the acc is valid */
if(retval == PAM_SUCCESS) {
retval = pam_acct_mgmt(pamh, 0);
}
verified = (retval == PAM_SUCCESS);
if(verified)
sprintf(str, "[PAM] Authentication successful for user %s", user);
else
sprintf(str, "[PAM] Authentication not successful for user %s", user);
write_logfile(lbs, str);
if(pam_end(pamh, retval) != PAM_SUCCESS) {
pamh = NULL;
strlcpy(error_str, "PAM: Error releasing authenticator", error_size);
}
return verified;
}
#endif /* HAVE_PAM */
/*---- local password file routines --------------------------------*/
int auth_verify_password_file(LOGBOOK * lbs, const char *user, const char *password, char *error_str,
int error_size)
{
char upwd[256], enc_pwd[256];
if (error_size)
*error_str = 0;
get_user_line(lbs, (char *) user, upwd, NULL, NULL, NULL, NULL, NULL);
do_crypt(password, enc_pwd, sizeof(enc_pwd));
return strcmp(enc_pwd, upwd) == 0;
}
int auth_change_password_file(LOGBOOK * lbs, const char *user, const char *new_pwd,
char *error_str, int error_size)
{
char str[256], file_name[256], enc_pwd[256];
PMXML_NODE node;
if (error_size)
*error_str = 0;
if (lbs == NULL)
lbs = get_first_lbs_with_global_passwd();
if (!lbs->pwd_xml_tree)
return FALSE;
sprintf(str, "/list/user[name=%s]/password", user);
node = mxml_find_node(lbs->pwd_xml_tree, str);
if (node == NULL)
return FALSE;
do_crypt(new_pwd, enc_pwd, sizeof(enc_pwd));
mxml_replace_node_value(node, enc_pwd);
if (get_password_file(lbs, file_name, sizeof(file_name)))
mxml_write_tree(file_name, lbs->pwd_xml_tree);
return TRUE;
}
/*---- common function entry points --------------------------------*/
int auth_verify_password(LOGBOOK * lbs, const char *user, const char *password, char *error_str,
int error_size)
{
char str[256];
BOOL verified;
error_str[0] = 0;
verified = FALSE;
/* otherwise calls with null lbs which make this procedure crash */
if (lbs == NULL)
lbs = get_first_lbs_with_global_passwd();
if (lbs == NULL)
return FALSE;
getcfg(lbs->name, "Authentication", str, sizeof(str));
#ifdef HAVE_KRB5
if (stristr(str, "Kerberos"))
verified = auth_verify_password_krb5(lbs, user, password, error_str, error_size);
if (verified)
return TRUE;
#endif
#ifdef HAVE_LDAP
if (stristr(str, "LDAP")) {
verified = auth_verify_password_ldap(lbs, user, password, error_str, error_size);
// if user not in password file (external authentication!) and "LDAP register" is allowed (>0),
// obtain user info from LDAP and add locally
if (verified) {
if (get_user_line(lbs, user, NULL, NULL, NULL, NULL, NULL, NULL) == 2) {
if (getcfg(lbs->name, "LDAP register", str, sizeof(str)) && atoi(str) > 0)
ldap_adduser_file(lbs, user, password, error_str, error_size);
}
}
ldap_clear();
}
if (verified)
return TRUE;
#endif
#ifdef HAVE_PAM
if(stristr(str, "PAM"))
verified = auth_verify_password_pam(lbs, user, password, error_str, error_size);
if(verified)
return TRUE;
#endif /* HAVE_PAM */
if (str[0] == 0 || stristr(str, "File"))
verified = auth_verify_password_file(lbs, user, password, error_str, error_size);
return verified;
}
int auth_change_password(LOGBOOK * lbs, const char *user, const char *old_pwd, const char *new_pwd,
char *error_str, int error_size)
{
int status = 0;
char str[256];
error_str[0] = 0;
getcfg(lbs->name, "Authentication", str, sizeof(str));
if (str[0] == 0 || stristr(str, "File"))
status = auth_change_password_file(lbs, user, new_pwd, error_str, error_size);
if (old_pwd) {} // avoid compiler warning
#ifdef HAVE_KRB5
if (stristr(str, "Kerberos"))
status = auth_change_password_krb5(lbs, user, old_pwd, new_pwd, error_str, error_size);
#endif
return status;
}