diff --git a/Makefile b/Makefile index 8b1b7b4..4c38b79 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ SHARE := -g $(PIC) pam_client: pam_client.c $(CC) $(CFLAGS) $(SHARE) -c pam_client.c -o pam_client.o - $(LD_D) -o pam_client.so pam_client.o -lc -lpam + $(LD_D) -o pam_client.so pam_client.o -lc -lpam -lldap clean: pam_client.so pam_client.o $(RM) pam_client.o diff --git a/pam_client.c b/pam_client.c index f6efc63..e7f1b39 100644 --- a/pam_client.c +++ b/pam_client.c @@ -22,14 +22,222 @@ #include #include #include +#include +#include #include - +#define PAM_LDAP_PATH_CONF "/etc/ldap.conf" #define PAM_SM_AUTH #define MAX_V 30 #define WAITTIME 30 +typedef struct pam_config +{ + /* file name read from */ + char *configFile; + /* space delimited list of servers */ + char *host; + /* port, expected to be common to all servers */ + int port; + int version; + /* bind dn/pw for "anonymous" authentication */ + char *binddn; + char *bindpw; + +} pam_config_t; + +static int +_alloc_config (pam_config_t ** presult) +{ + pam_config_t *result; + + if (*presult == NULL) + { + *presult = (pam_config_t *) calloc (1, sizeof (*result)); + if (*presult == NULL) + return PAM_BUF_ERR; + } + + result = *presult; + result->configFile = NULL; + result->host = NULL; + result->port = 0; + result->binddn = NULL; + result->bindpw = NULL; + result->version = LDAP_VERSION3; + return PAM_SUCCESS; +} + +static int +_read_config (const char *configFile, pam_config_t ** presult) +{ + /* this is the same configuration file as nss_ldap */ + FILE *fp; + char b[BUFSIZ]; + pam_config_t *result; + + if (_alloc_config (presult) != PAM_SUCCESS) + { + return PAM_BUF_ERR; + } + + result = *presult; + + /* configuration file location is configurable; default /etc/ldap.conf */ + if (configFile == NULL) + { + configFile = PAM_LDAP_PATH_CONF; + result->configFile = NULL; + } + else + { + result->configFile = strdup (configFile); + if (result->configFile == NULL) + return PAM_BUF_ERR; + } + + fp = fopen (configFile, "r"); + + if (fp == NULL) + { + return PAM_SERVICE_ERR; + } + + + while (fgets (b, sizeof (b), fp) != NULL) + { + char *k, *v; + int len; + + if (*b == '\n' || *b == '#') + continue; + + k = b; + v = k; + while (*v != '\0' && *v != ' ' && *v != '\t') + v++; + + if (*v == '\0') + continue; + + *(v++) = '\0'; + + /* skip all whitespaces between keyword and value */ + while (*v == ' ' || *v == '\t') + v++; + + /* kick off all whitespaces and newline at the end of value */ + len = strlen (v) - 1; + while (v[len] == ' ' || v[len] == '\t' || v[len] == '\n') + --len; + v[len + 1] = '\0'; + + if (!strcasecmp (k, "host")) + { + result->host = strdup (v); + } + else if (!strcasecmp (k, "binddn")) + { + result->binddn = strdup (v); + } + else if (!strcasecmp (k, "bindpw")) + { + result->bindpw = strdup (v); + } + else if (!strcasecmp (k, "port")) + { + result->port = atoi (v); + } + else if (!strcasecmp (k, "ldap_version")) + { + result->version = atoi (v); + } + } + + if (result->host == NULL || result->binddn == NULL || + result->bindpw == NULL) + { + /* + * According to PAM Documentation, such an error in a config file + * SHOULD be logged at LOG_ALERT level + */ + syslog (LOG_ALERT, "pam_ldap: missing \"host\" in file \"%s\"", + configFile); + return PAM_SERVICE_ERR; + } + + if (result->port == 0) + { + result->port = LDAP_PORT; + } + + fclose (fp); + + /* can't use _pam_overwrite because it only goes to end of string, + * not the buffer + */ + memset (b, 0, BUFSIZ); + return PAM_SUCCESS; +} + +static void +_release_config (pam_config_t ** pconfig) +{ + pam_config_t *c; + c = *pconfig; + if (c == NULL) + return; + + if (c->configFile != NULL) + free (c->configFile); + + if (c->host != NULL) + free (c->host); + + if (c->binddn != NULL) + free (c->binddn); + if (c->bindpw != NULL) + free (c->bindpw); +} + +static int +_check_ldap (int retry_count) +{ + int result; + struct berval userpw; + struct berval *servcred; + char buf[BUFSIZ]; + LDAP *ld; + pam_config_t * config= NULL; + if(_read_config(NULL,&config) != PAM_SUCCESS) { + if(config) + _release_config(&config); + return PAM_SERVICE_ERR; + } + + snprintf(buf,BUFSIZ,"ldap://%s:%d",config->host,config->port); + if(ldap_initialize(&ld,buf) == LDAP_SUCCESS && + ldap_set_option(ld,LDAP_OPT_PROTOCOL_VERSION, + &config->version) == LDAP_SUCCESS ) { + userpw.bv_val = config->bindpw; + userpw.bv_len = (userpw.bv_val != 0) ? strlen (userpw.bv_val) : 0; + for(;retry_count;retry_count--) { + if (ldap_sasl_bind_s(ld,config->binddn,LDAP_SASL_SIMPLE, + &userpw, NULL, NULL,&servcred) == LDAP_SUCCESS) { + result = PAM_SUCCESS; + break; + } + else { + result = PAM_TRY_AGAIN; + } + sleep(1); + } + } + _release_config(&config); + return result; +} + int file_exists(const char *fname) { return access(fname, 0) != -1; } @@ -47,13 +255,15 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags // wait for client daemon if (file_exists(boot_client) || file_exists(default_client)) { - for(i=0;i