Logo Search packages:      
Sourcecode: cyrus-sasl2 version File versions  Download package

lak.c

/* COPYRIGHT
 * Copyright (c) 2002-2003 Igor Brezac
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY IGOR BREZAC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL IGOR BREZAC OR
 * ITS EMPLOYEES OR AGENTS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 * END COPYRIGHT */

#ifndef AUTH_LDAP
      #include "mechanisms.h"
      #include "utils.h"
#endif

#ifdef AUTH_LDAP

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <ctype.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif

#ifdef HAVE_OPENSSL
#ifndef OPENSSL_DISABLE_OLD_DES_SUPPORT
#define OPENSSL_DISABLE_OLD_DES_SUPPORT
#endif
#include <openssl/evp.h>
#include <openssl/des.h>
#endif

#include <ldap.h>
#include <lber.h>
#include <sasl.h>
#include "lak.h"

typedef struct lak_auth_method {
      int method;
      int (*check) (LAK *lak, const char *user, const char *service, const char *realm, const char *password) ;
} LAK_AUTH_METHOD;

typedef struct lak_hash_rock {
      const char *mda;
      int salted;
} LAK_HASH_ROCK;

typedef struct lak_password_scheme {
      char *hash;
      int (*check) (const char *cred, const char *passwd, void *rock);
      void *rock;
} LAK_PASSWORD_SCHEME;

static int lak_config_read(LAK_CONF *, const char *);
static int lak_config_int(const char *);
static int lak_config_switch(const char *);
static void lak_config_free(LAK_CONF *);
static int lak_config(const char *, LAK_CONF **);
static int lak_escape(const char *, const unsigned int, char **);
static int lak_tokenize_domains(const char *, int, char **);
static int lak_expand_tokens(const char *, const char *, const char *, const char *, const char *, char **);
static int lak_connect(LAK *);
static int lak_bind(LAK *, LAK_USER *);
static void lak_unbind(LAK *);
static int lak_auth_custom(LAK *, const char *, const char *, const char *, const char *);
static int lak_auth_bind(LAK *, const char *, const char *, const char *, const char *);
static int lak_auth_fastbind(LAK *, const char *, const char *, const char *, const char *);
static int lak_group_member(LAK *, const char *, const char *, const char *, const char *);
static char *lak_result_get(const LAK_RESULT *, const char *);
static int lak_result_add(const char *, const char *, LAK_RESULT **);
static int lak_check_password(const char *, const char *, void *);
static int lak_check_crypt(const char *, const char *, void *);
#ifdef HAVE_OPENSSL
static int lak_base64_decode(const char *, char **, int *);
static int lak_check_hashed(const char *, const char *, void *);
#endif
static int lak_sasl_interact(LDAP *, unsigned, void *, void *);
static int lak_user(const char *, const char *, const char *, const char *, const char *, const char *, LAK_USER **);
static int lak_user_copy(LAK_USER **, const LAK_USER *);
static int lak_user_cmp(const LAK_USER *, const LAK_USER *);
static void lak_user_free(LAK_USER *);

static LAK_AUTH_METHOD authenticator[] = {
      { LAK_AUTH_METHOD_BIND, lak_auth_bind },
      { LAK_AUTH_METHOD_CUSTOM, lak_auth_custom },
      { LAK_AUTH_METHOD_FASTBIND, lak_auth_fastbind },
      { -1, NULL }
};

static LAK_HASH_ROCK hash_rock[] = {
      { "md5", 0 },
      { "md5", 1 },
      { "sha1", 0 },
      { "sha1", 1 }
};

static LAK_PASSWORD_SCHEME password_scheme[] = {
      { "{CRYPT}", lak_check_crypt, NULL },
      { "{UNIX}", lak_check_crypt, NULL },
#ifdef HAVE_OPENSSL
      { "{MD5}", lak_check_hashed, &hash_rock[0] },
      { "{SMD5}", lak_check_hashed, &hash_rock[1] },
      { "{SHA}", lak_check_hashed, &hash_rock[2] },
      { "{SSHA}", lak_check_hashed, &hash_rock[3] },
#endif
      { NULL, NULL, NULL }
};

static const char *dn_attr = "dn";

#define ISSET(x)  ((x != NULL) && (*(x) != '\0'))
#define EMPTY(x)  ((x == NULL) || (*(x) == '\0'))

static int lak_config_read(
      LAK_CONF *conf,
      const char *configfile)
{
      FILE *infile;
      int lineno = 0;
      char buf[4096];
      char *p, *key;

      infile = fopen(configfile, "r");
      if (!infile) {
          syslog(LOG_ERR|LOG_AUTH,
               "Could not open saslauthd config file: %s (%m)",
               configfile);
          return LAK_FAIL;
      }
    
      while (fgets(buf, sizeof(buf), infile)) {
            lineno++;

            if (buf[strlen(buf)-1] == '\n') 
                  buf[strlen(buf)-1] = '\0';
            for (p = buf; *p && isspace((int) *p); p++);
                  if (!*p || *p == '#') 
                        continue;

            key = p;
            while (*p && (isalnum((int) *p) || *p == '-' || *p == '_')) {
                  if (isupper((int) *p)) 
                        *p = tolower(*p);
                  p++;
            }
            if (*p != ':')
                  return LAK_FAIL;
            
            *p++ = '\0';

            while (*p && isspace((int) *p)) 
                  p++;

            if (!*p)
                  return LAK_FAIL;

            if (!strcasecmp(key, "ldap_servers"))
                  strlcpy(conf->servers, p, LAK_URL_LEN);

            else if (!strcasecmp(key, "ldap_bind_dn"))
                  strlcpy(conf->bind_dn, p, LAK_DN_LEN);

            else if (!strcasecmp(key, "ldap_bind_pw") ||
                     !strcasecmp(key, "ldap_password"))
                  strlcpy(conf->password, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_version"))
                  conf->version = lak_config_int(p);

            else if (!strcasecmp(key, "ldap_search_base"))
                  strlcpy(conf->search_base, p, LAK_DN_LEN);

            else if (!strcasecmp(key, "ldap_filter"))
                  strlcpy(conf->filter, p, LAK_DN_LEN);
            
            else if (!strcasecmp(key, "ldap_password_attr"))
                  strlcpy(conf->password_attr, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_group_dn"))
                  strlcpy(conf->group_dn, p, LAK_DN_LEN);
            
            else if (!strcasecmp(key, "ldap_group_attr"))
                  strlcpy(conf->group_attr, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_group_filter"))
                  strlcpy(conf->group_filter, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_group_search_base"))
                  strlcpy(conf->group_search_base, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_group_scope")) {
                  if (!strcasecmp(p, "one")) {
                        conf->group_scope = LDAP_SCOPE_ONELEVEL;
                  } else if (!strcasecmp(p, "base")) {
                        conf->group_scope = LDAP_SCOPE_BASE;
                  }
            } else if (!strcasecmp(key, "ldap_group_match_method")) {
                  if (!strcasecmp(p, "filter")) {
                        conf->group_match_method = LAK_GROUP_MATCH_METHOD_FILTER;
                  } else if (!strcasecmp(p, "attr")) {
                        conf->group_match_method = LAK_GROUP_MATCH_METHOD_ATTR;
                  }
            } else if (!strcasecmp(key, "ldap_default_realm") ||
                     !strcasecmp(key, "ldap_default_domain"))
                  strlcpy(conf->default_realm, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_auth_method")) {
                  if (!strcasecmp(p, "custom")) {
                        conf->auth_method = LAK_AUTH_METHOD_CUSTOM;
                  } else if (!strcasecmp(p, "fastbind")) {
                        conf->auth_method = LAK_AUTH_METHOD_FASTBIND;
                  }
            } else if (!strcasecmp(key, "ldap_timeout")) {
                  conf->timeout.tv_sec = lak_config_int(p);
                  conf->timeout.tv_usec = 0;
            } else if (!strcasecmp(key, "ldap_size_limit"))
                  conf->size_limit = lak_config_int(p);

            else if (!strcasecmp(key, "ldap_time_limit"))
                  conf->time_limit = lak_config_int(p);

            else if (!strcasecmp(key, "ldap_deref")) {
                  if (!strcasecmp(p, "search")) {
                        conf->deref = LDAP_DEREF_SEARCHING;
                  } else if (!strcasecmp(p, "find")) {
                        conf->deref = LDAP_DEREF_FINDING;
                  } else if (!strcasecmp(p, "always")) {
                        conf->deref = LDAP_DEREF_ALWAYS;
                  } else if (!strcasecmp(p, "never")) {
                        conf->deref = LDAP_DEREF_NEVER;
                  }
            } else if (!strcasecmp(key, "ldap_referrals")) {
                  conf->referrals = lak_config_switch(p);

            } else if (!strcasecmp(key, "ldap_restart")) {
                  conf->restart = lak_config_switch(p);

            } else if (!strcasecmp(key, "ldap_scope")) {
                  if (!strcasecmp(p, "one")) {
                        conf->scope = LDAP_SCOPE_ONELEVEL;
                  } else if (!strcasecmp(p, "base")) {
                        conf->scope = LDAP_SCOPE_BASE;
                  }
            } else if (!strcasecmp(key, "ldap_use_sasl")) {
                  conf->use_sasl = lak_config_switch(p);

            } else if (!strcasecmp(key, "ldap_id") ||
                   !strcasecmp(key, "ldap_sasl_authc_id"))
                  strlcpy(conf->id, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_authz_id") ||
                 !strcasecmp(key, "ldap_sasl_authz_id"))
                  strlcpy(conf->authz_id, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_realm") ||
                 !strcasecmp(key, "ldap_sasl_realm"))
                  strlcpy(conf->realm, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_mech") ||
                 !strcasecmp(key, "ldap_sasl_mech"))
                  strlcpy(conf->mech, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_sasl_secprops"))
                  strlcpy(conf->sasl_secprops, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_start_tls"))
                  conf->start_tls = lak_config_switch(p);

            else if (!strcasecmp(key, "ldap_tls_check_peer"))
                  conf->tls_check_peer = lak_config_switch(p);

            else if (!strcasecmp(key, "ldap_tls_cacert_file"))
                  strlcpy(conf->tls_cacert_file, p, LAK_PATH_LEN);

            else if (!strcasecmp(key, "ldap_tls_cacert_dir"))
                  strlcpy(conf->tls_cacert_dir, p, LAK_PATH_LEN);

            else if (!strcasecmp(key, "ldap_tls_ciphers"))
                  strlcpy(conf->tls_ciphers, p, LAK_BUF_LEN);

            else if (!strcasecmp(key, "ldap_tls_cert"))
                  strlcpy(conf->tls_cert, p, LAK_PATH_LEN);

            else if (!strcasecmp(key, "ldap_tls_key"))
                  strlcpy(conf->tls_key, p, LAK_PATH_LEN);

            else if (!strcasecmp(key, "ldap_debug"))
                  conf->debug = lak_config_int(p);
      }

      if (conf->version != LDAP_VERSION3 && 
          (conf->use_sasl ||
           conf->start_tls))
          conf->version = LDAP_VERSION3;

    if (conf->use_sasl &&
        conf->auth_method == LAK_AUTH_METHOD_BIND)
        conf->auth_method = LAK_AUTH_METHOD_FASTBIND;

    if ( ISSET(conf->group_filter) &&
         ISSET(conf->search_base) &&
         EMPTY(conf->group_search_base) )
        strlcpy(conf->group_search_base, conf->search_base, LAK_DN_LEN);
        
      fclose(infile);

      return LAK_OK;
}

static int lak_config_int(
      const char *val)
{
    if (!val) return 0;

    if (!isdigit((int) *val) && (*val != '-' || !isdigit((int) val[1]))) return 0;

    return atoi(val);
}

static int lak_config_switch(
      const char *val)
{
    if (!val) return 0;
    
    if (*val == '0' || *val == 'n' ||
      (*val == 'o' && val[1] == 'f') || *val == 'f') {
      return 0;
    } else if (*val == '1' || *val == 'y' ||
           (*val == 'o' && val[1] == 'n') || *val == 't') {
      return 1;
    }
    return 0;
}

static int lak_config(
      const char *configfile, 
      LAK_CONF **ret)
{
      LAK_CONF *conf;
      int rc = 0;

      conf = malloc( sizeof(LAK_CONF) );
      if (conf == NULL) {
            return LAK_NOMEM;
      }

      memset(conf, 0, sizeof(LAK_CONF));

      strlcpy(conf->servers, "ldap://localhost/", LAK_BUF_LEN);
      conf->version = LDAP_VERSION3;
      strlcpy(conf->filter, "(uid=%u)", LAK_DN_LEN);
      strlcpy(conf->password_attr, "userPassword", LAK_BUF_LEN);
      conf->scope = LDAP_SCOPE_SUBTREE;
      strlcpy(conf->group_attr, "uniqueMember", LAK_BUF_LEN);
      conf->group_scope = LDAP_SCOPE_SUBTREE;
    conf->group_match_method = LAK_GROUP_MATCH_METHOD_ATTR;
      conf->auth_method = LAK_AUTH_METHOD_BIND;
      conf->timeout.tv_sec = 5;
      conf->timeout.tv_usec = 0;
      conf->size_limit = 1;
      conf->time_limit = 5;
      conf->deref = LDAP_DEREF_NEVER;
      conf->restart = 1;
      conf->start_tls = 0;
      conf->use_sasl = 0;

      strlcpy(conf->path, configfile, LAK_PATH_LEN);

      rc = lak_config_read(conf, conf->path);
      if (rc != LAK_OK) {
            lak_config_free(conf);
            return rc;
      }

      *ret = conf;
      return LAK_OK;
}

static void lak_config_free(
      LAK_CONF *conf) 
{
      if (conf == NULL) {
            return;
      }

      memset(conf, 0, sizeof(LAK_CONF));

      free (conf);

      return;
}

/*
 * Note: calling function must free memory.
 */
static int lak_escape(
      const char *s, 
      const unsigned int n, 
      char **result) 
{
      char *buf;
      char *end, *ptr, *temp;

      if (n > strlen(s))  // Sanity check, just in case
            return LAK_FAIL;

      buf = malloc(n * 5 + 1);
      if (buf == NULL) {
            return LAK_NOMEM;
      }

      buf[0] = '\0';
      ptr = (char *)s;
      end = ptr + n;

      while (((temp = strpbrk(ptr, "*()\\\0"))!=NULL) && (temp<end)) {

            if (temp>ptr)
                  strncat(buf, ptr, temp-ptr);

            switch (*temp) {
                  case '*':
                        strcat(buf, "\\2a");
                        break;
                  case '(':
                        strcat(buf, "\\28");
                        break;
                  case ')':
                        strcat(buf, "\\29");
                        break;
                  case '\\':
                        strcat(buf, "\\5c");
                        break;
                  case '\0':
                        strcat(buf, "\\00");
                        break;
            }
            ptr=temp+1;
      }
      if (ptr<end)
            strncat(buf, ptr, end-ptr);

      *result = buf;

      return LAK_OK;
}

static int lak_tokenize_domains(
      const char *d, 
      int n, 
      char **result)
{
      char *s, *s1;
      char *lasts;
      int nt, i, rc;

      *result = NULL;

      if (d == NULL || n < 1 || n > 9)
            return LAK_FAIL;

      s = strdup(d);
      if (s == NULL)
            return LAK_NOMEM;

      for( nt=0, s1=s; *s1; s1++ )
            if( *s1 == '.' ) nt++;
      nt++;

      if (n > nt) {
            free(s);
            return LAK_FAIL;
      }

      i = nt - n;
      s1 = (char *)strtok_r(s, ".", &lasts);
      while(s1) {
            if (i == 0) {
                  *result = strdup(s1);
                  if (*result != NULL)
                        rc = lak_escape(s1, strlen(s1), result);
                  else
                        rc = LAK_NOMEM;
                  free(s);
                  return rc;
            }
            s1 = (char *)strtok_r(NULL, ".", &lasts);
            i--;
      }

      free(s);
      return LAK_FAIL;
}

#define LAK_MAX(a,b) (a>b?a:b)

/*
 * lak_expand_tokens
 * Parts with the strings provided.
 *   %%   = %
 *   %u   = user
 *   %U   = user part of %u
 *   %d   = domain part of %u if available, othwise same as %r
 *   %1-9 = domain if not available realm, token
 *          (%1 = tld, %2 = domain when %r = domain.tld)
 *   %s   = service
 *   %r   = realm
 *   %R   = prepend '@' to realm
 *   %D   = user DN
 * Note: calling function must free memory.
 */
static int lak_expand_tokens(
      const char *pattern,
      const char *username, 
      const char *service,
      const char *realm,
      const char *dn,
      char **result) 
{
      char *buf; 
      char *end, *ptr, *temp;
      char *ebuf, *user;
      char *domain;
      int rc;

      /* to permit multiple occurences of username and/or realm in filter */
      /* and avoid memory overflow in filter build [eg: (|(uid=%u)(userid=%u)) ] */
      int percents, service_len, realm_len, dn_len, user_len, maxparamlength;
      
      if (pattern == NULL) {
            syslog(LOG_ERR|LOG_AUTH, "filter pattern not setup");
            return LAK_FAIL;
      }

      /* find the longest param of username and realm, 
         do not worry about domain because it is always shorter 
         then username                                           */
      user_len=ISSET(username) ? strlen(username) : 0;
      service_len=ISSET(service) ? strlen(service) : 0;
      realm_len=ISSET(realm) ? strlen(realm) : 0;
      dn_len=ISSET(dn) ? strlen(dn) : 0;

      maxparamlength = LAK_MAX(user_len, service_len);
      maxparamlength = LAK_MAX(maxparamlength, realm_len + 1);  /* +1 for %R when '@' is prepended */
      maxparamlength = LAK_MAX(maxparamlength, dn_len);

      /* find the number of occurences of percent sign in filter */
      for( percents=0, buf=(char *)pattern; *buf; buf++ ) {
            if( *buf == '%' ) percents++;
      }

      /* percents * 3 * maxparamlength because we need to account for
         * an entirely-escaped worst-case-length parameter */
      buf=malloc(strlen(pattern) + (percents * 3 * maxparamlength) + 1);
      if(buf == NULL)
            return LAK_NOMEM;
      buf[0] = '\0';
      
      ptr = (char *)pattern;
      end = ptr + strlen(ptr);

      while ((temp=strchr(ptr,'%'))!=NULL ) {

            if ((temp-ptr) > 0)
                  strncat(buf, ptr, temp-ptr);

            if ((temp+1) >= end) {
                  syslog(LOG_DEBUG|LOG_AUTH, "Incomplete lookup substitution format");
                  break;
            }

            switch (*(temp+1)) {
                  case '%':
                        strncat(buf,temp+1,1);
                        break;
                  case 'u':
                        if (ISSET(username)) {
                              rc=lak_escape(username, strlen(username), &ebuf);
                              if (rc == LAK_OK) {
                                    strcat(buf,ebuf);
                                    free(ebuf);
                              }
                        } else
                              syslog(LOG_DEBUG|LOG_AUTH, "Username not available.");
                        break;
                  case 'U':
                        if (ISSET(username)) {
                              user = strchr(username, '@');
                              rc=lak_escape(username, (user ? user - username : strlen(username)), &ebuf);
                              if (rc == LAK_OK) {
                                    strcat(buf,ebuf);
                                    free(ebuf);
                              }
                        } else
                              syslog(LOG_DEBUG|LOG_AUTH, "Username not available.");
                        break;
                  case '1':
                  case '2':
                  case '3':
                  case '4':
                  case '5':
                  case '6':
                  case '7':
                  case '8':
                  case '9':
                        if (ISSET(username) && 
                            ((domain = strchr(username, '@')) && domain[1]!='\0')) {
                              rc=lak_tokenize_domains(domain+1, (int) *(temp+1)-48, &ebuf);
                              if (rc == LAK_OK) {
                                    strcat(buf,ebuf);
                                    free(ebuf);
                              }
                        } else if (ISSET(realm)) {
                              rc=lak_tokenize_domains(realm, (int) *(temp+1)-48, &ebuf);
                              if (rc == LAK_OK) {
                                    strcat(buf,ebuf);
                                    free(ebuf);
                              }
                        } else
                              syslog(LOG_DEBUG|LOG_AUTH, "Domain/Realm not available.");
                        break;
                  case 'd':
                        if (ISSET(username) && 
                            ((domain = strchr(username, '@')) && domain[1]!='\0')) {
                              rc=lak_escape(domain+1, strlen(domain+1), &ebuf);
                              if (rc == LAK_OK) {
                                    strcat(buf,ebuf);
                                    free(ebuf);
                              }
                              break;
                        } 
                  case 'R':
                  case 'r':
                        if (ISSET(realm)) {
                              rc = lak_escape(realm, strlen(realm), &ebuf);
                              if (rc == LAK_OK) {
                                    if (*(temp+1) == 'R')
                                          strcat(buf,"@");
                                    strcat(buf,ebuf);
                                    free(ebuf);
                              }
                        } else
                              syslog(LOG_DEBUG|LOG_AUTH, "Domain/Realm not available.");
                        break;
                  case 's':
                        if (ISSET(service)) {
                              rc = lak_escape(service, strlen(service), &ebuf);
                              if (rc == LAK_OK) {
                                    strcat(buf,ebuf);
                                    free(ebuf);
                              }
                        } else
                              syslog(LOG_DEBUG|LOG_AUTH, "Service not available.");
                        break;
                  case 'D':
                        if (ISSET(dn)) {
                              rc = lak_escape(dn, strlen(dn), &ebuf);
                              if (rc == LAK_OK) {
                                    strcat(buf,ebuf);
                                    free(ebuf);
                              }
                        } else
                              syslog(LOG_DEBUG|LOG_AUTH, "User DN not available.");
                        break;
                  default:
                        break;
            }
            ptr=temp+2;
      }
      if (temp<end)
            strcat(buf, ptr);

      *result = buf;

      return LAK_OK;
}

int lak_init(
      const char *configfile, 
      LAK **ret) 
{
      LAK *lak;
      int rc;

      lak = *ret;

      if (lak != NULL) {
            return LAK_OK;
      }

      lak = (LAK *)malloc(sizeof(LAK));
      if (lak == NULL)
            return LAK_NOMEM;

      lak->status=LAK_NOT_BOUND;
      lak->ld=NULL;
      lak->conf=NULL;
      lak->user=NULL;

      rc = lak_config(configfile, &lak->conf);
      if (rc != LAK_OK) {
            free(lak);
            return rc;
      }

#ifdef HAVE_OPENSSL
      OpenSSL_add_all_digests();
#endif

      *ret=lak;
      return LAK_OK;
}

void lak_close(
      LAK *lak) 
{

      if (lak == NULL)
            return;

      lak_config_free(lak->conf);

      lak_unbind(lak);

      free(lak);

#ifdef HAVE_OPENSSL
      EVP_cleanup();
#endif

      return;
}

static int lak_connect(
      LAK *lak)
{
      int rc = 0;
      char *p = NULL;

      if (ISSET(lak->conf->tls_cacert_file)) {
            rc = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE, lak->conf->tls_cacert_file);
            if (rc != LDAP_SUCCESS) {
                  syslog (LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_X_TLS_CACERTFILE (%s).", ldap_err2string (rc));
            }
      }

      if (ISSET(lak->conf->tls_cacert_dir)) {
            rc = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTDIR, lak->conf->tls_cacert_dir);
            if (rc != LDAP_SUCCESS) {
                  syslog (LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_X_TLS_CACERTDIR (%s).", ldap_err2string (rc));
            }
      }

      if (lak->conf->tls_check_peer != 0) {
            rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &lak->conf->tls_check_peer);
            if (rc != LDAP_SUCCESS) {
                  syslog (LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_X_TLS_REQUIRE_CERT (%s).", ldap_err2string (rc));
            }
      }

      if (ISSET(lak->conf->tls_ciphers)) {
            /* set cipher suite, certificate and private key: */
            rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CIPHER_SUITE, lak->conf->tls_ciphers);
            if (rc != LDAP_SUCCESS) {
                  syslog (LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_X_TLS_CIPHER_SUITE (%s).", ldap_err2string (rc));
            }
      }

      if (ISSET(lak->conf->tls_cert)) {
            rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CERTFILE, lak->conf->tls_cert);
            if (rc != LDAP_SUCCESS) {
                  syslog (LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_X_TLS_CERTFILE (%s).", ldap_err2string (rc));
            }
      }

      if (ISSET(lak->conf->tls_key)) {
            rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_KEYFILE, lak->conf->tls_key);
            if (rc != LDAP_SUCCESS) {
                  syslog (LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_X_TLS_KEYFILE (%s).", ldap_err2string (rc));
            }
      }

      rc = ldap_initialize(&lak->ld, lak->conf->servers);
      if (rc != LDAP_SUCCESS) {
            syslog(LOG_ERR|LOG_AUTH, "ldap_initialize failed (%s)", lak->conf->servers);
            return LAK_CONNECT_FAIL;
      }

      if (lak->conf->debug) {
            rc = ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &(lak->conf->debug));
            if (rc != LDAP_OPT_SUCCESS)
                  syslog(LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_DEBUG_LEVEL %x.", lak->conf->debug);
      }

      rc = ldap_set_option(lak->ld, LDAP_OPT_PROTOCOL_VERSION, &(lak->conf->version));
      if (rc != LDAP_OPT_SUCCESS) {

            if (lak->conf->use_sasl ||
                lak->conf->start_tls) {
                  syslog(LOG_ERR|LOG_AUTH, "Failed to set LDAP_OPT_PROTOCOL_VERSION %d, required for ldap_start_tls and ldap_use_sasl.", lak->conf->version);
                  lak_unbind(lak);
                  return LAK_CONNECT_FAIL;
            } else
                  syslog(LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_PROTOCOL_VERSION %d.", lak->conf->version);

            lak->conf->version = LDAP_VERSION2;

      }

      rc = ldap_set_option(lak->ld, LDAP_OPT_NETWORK_TIMEOUT, &(lak->conf->timeout));
      if (rc != LDAP_OPT_SUCCESS) {
            syslog(LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_NETWORK_TIMEOUT %d.%d.", lak->conf->timeout.tv_sec, lak->conf->timeout.tv_usec);
      }

      rc = ldap_set_option(lak->ld, LDAP_OPT_TIMELIMIT, &(lak->conf->time_limit));
      if (rc != LDAP_OPT_SUCCESS) {
            syslog(LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_TIMELIMIT %d.", lak->conf->time_limit);
      }

      rc = ldap_set_option(lak->ld, LDAP_OPT_DEREF, &(lak->conf->deref));
      if (rc != LDAP_OPT_SUCCESS) {
            syslog(LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_DEREF %d.", lak->conf->deref);
      }

      rc = ldap_set_option(lak->ld, LDAP_OPT_REFERRALS, lak->conf->referrals ? LDAP_OPT_ON : LDAP_OPT_OFF);
      if (rc != LDAP_OPT_SUCCESS) {
            syslog(LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_REFERRALS.");
      }

      rc = ldap_set_option(lak->ld, LDAP_OPT_SIZELIMIT, &(lak->conf->size_limit));
      if (rc != LDAP_OPT_SUCCESS)
            syslog(LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_SIZELIMIT %d.", lak->conf->size_limit);

      rc = ldap_set_option(lak->ld, LDAP_OPT_RESTART, lak->conf->restart ? LDAP_OPT_ON : LDAP_OPT_OFF);
      if (rc != LDAP_OPT_SUCCESS) {
            syslog(LOG_WARNING|LOG_AUTH, "Unable to set LDAP_OPT_RESTART.");
      }

      if (lak->conf->start_tls) {

            rc = ldap_start_tls_s(lak->ld, NULL, NULL);
            if (rc != LDAP_SUCCESS) {
                  syslog(LOG_ERR|LOG_AUTH, "start tls failed (%s).", ldap_err2string(rc));
                  lak_unbind(lak);
                  return LAK_CONNECT_FAIL;
            }
      }
      
      if (lak->conf->use_sasl) {

            if (EMPTY(lak->conf->mech)) {
                  ldap_get_option(lak->ld, LDAP_OPT_X_SASL_MECH, &p);
                  if (p)
                        strlcpy(lak->conf->mech, p, LAK_BUF_LEN);
            }

            if (EMPTY(lak->conf->realm)) {
                  ldap_get_option(lak->ld, LDAP_OPT_X_SASL_REALM, &p);
                  if (p)
                        strlcpy(lak->conf->realm, p, LAK_BUF_LEN);
            }

            if (ISSET(lak->conf->sasl_secprops)) {
                  rc = ldap_set_option(lak->ld, LDAP_OPT_X_SASL_SECPROPS, (void *) lak->conf->sasl_secprops);
                  if( rc != LDAP_OPT_SUCCESS ) {
                        syslog(LOG_ERR|LOG_AUTH, "Unable to set LDAP_OPT_X_SASL_SECPROPS.");
                        lak_unbind(lak);
                        return LAK_CONNECT_FAIL;
                  }
            }
      }


      return LAK_OK;
}

static int lak_user(
      const char *bind_dn, 
      const char *id, 
      const char *authz_id, 
      const char *mech, 
      const char *realm, 
      const char *password, 
      LAK_USER **ret)
{
      LAK_USER *lu = NULL;
      
      *ret = NULL;

      lu = (LAK_USER *)malloc(sizeof(LAK_USER));
      if (lu == NULL)
            return LAK_NOMEM;

      memset(lu, 0, sizeof(LAK_USER));

      if (ISSET(bind_dn))
            strlcpy(lu->bind_dn, bind_dn, LAK_DN_LEN);

      if (ISSET(id))
            strlcpy(lu->id, id, LAK_BUF_LEN);

      if (ISSET(authz_id))
            strlcpy(lu->authz_id, authz_id, LAK_BUF_LEN);

      if (ISSET(mech))
            strlcpy(lu->mech, mech, LAK_BUF_LEN);

      if (ISSET(realm))
            strlcpy(lu->realm, realm, LAK_BUF_LEN);

      if (ISSET(password))
            strlcpy(lu->password, password, LAK_BUF_LEN);
      
      *ret = lu;
      return LAK_OK;
}

static int lak_user_cmp(
      const LAK_USER *lu1,
      const LAK_USER *lu2)
{

      if (lu1 == NULL ||
          lu2 == NULL)
            return LAK_FAIL;
      
      if (memcmp(lu1, lu2, sizeof(LAK_USER)) == 0)
            return LAK_OK;

      return LAK_FAIL;
}

static int lak_user_copy(
      LAK_USER **lu1,
      const LAK_USER *lu2)
{
      LAK_USER *lu;

      lu = *lu1;

      if (lu2 == NULL)
            return LAK_FAIL;

      if (lu == NULL) {
            lu = (LAK_USER *)malloc(sizeof(LAK_USER));
            if (lu == NULL)
                  return LAK_NOMEM;
            
            *lu1 = lu;
      }

      memcpy((void *)lu, (void *)lu2, sizeof(LAK_USER));

      return LAK_OK;
}

static void lak_user_free(
      LAK_USER *user)
{
      if (user == NULL) {
            return;
      }

      memset(user, 0, sizeof(LAK_USER));

      free(user);

      return;
}

static int lak_sasl_interact(
      LDAP *ld, 
      unsigned flags __attribute__((unused)), 
      void *def, 
      void *inter)
{
      sasl_interact_t *in = inter;
      const char *p;
      LAK_USER *lu = def;

      for (;in->id != SASL_CB_LIST_END;in++) {
            p = NULL;
            switch(in->id) {
                  case SASL_CB_AUTHNAME:
                        if (ISSET(lu->id))
                              p = lu->id;
                        if (!p)
                              ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHCID, &p);
                        break;
                  case SASL_CB_USER:
                        if (ISSET(lu->authz_id))
                              p = lu->authz_id;
                        if (!p)
                              ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHZID, &p);
                        break;
                  case SASL_CB_GETREALM:
                        if (ISSET(lu->realm))
                              p = lu->realm;
                        break;          
                  case SASL_CB_PASS:
                        if (ISSET(lu->password))
                              p = lu->password;
                        break;
            }

            in->result = ISSET(p) ? p : "";
            in->len = strlen(in->result);
      }

      return LDAP_SUCCESS;
}

static int lak_bind(
      LAK *lak, 
      LAK_USER *user)
{
      int rc;

      if (user == NULL)  // Sanity Check
            return LAK_FAIL;

      if ((lak->status == LAK_BOUND) &&
          (lak_user_cmp(lak->user, user) == LAK_OK))
            return LAK_OK;

      lak_user_free(lak->user);
      lak->user = NULL;

      if (
#if LDAP_VENDOR_VERSION < 20204
        lak->conf->use_sasl ||
#endif
          lak->conf->version == LDAP_VERSION2) 
            lak->status = LAK_NOT_BOUND;

      if (lak->status == LAK_NOT_BOUND) {
            lak_unbind(lak);
            rc = lak_connect(lak);
            if (rc != LAK_OK)
                  return rc;
      }

      if (lak->conf->use_sasl)
            rc = ldap_sasl_interactive_bind_s(
                  lak->ld, 
                  user->bind_dn,
                  user->mech, 
                  NULL, 
                  NULL, 
                  LDAP_SASL_QUIET, 
                  lak_sasl_interact, 
                  user);
      else
            rc = ldap_simple_bind_s(lak->ld, user->bind_dn, user->password);

      switch (rc) {
            case LDAP_SUCCESS:
                  break;
        case LDAP_INVALID_CREDENTIALS:
        case LDAP_INSUFFICIENT_ACCESS:
        case LDAP_INVALID_DN_SYNTAX:
        case LDAP_OTHER: 
                  lak->status = LAK_NOT_BOUND;
            return LAK_BIND_FAIL;
            case LDAP_TIMEOUT:
            case LDAP_SERVER_DOWN:
            default:
                  syslog(LOG_DEBUG|LOG_AUTH,
                           (lak->conf->use_sasl ? "ldap_sasl_interactive_bind() failed %d (%s)." : "ldap_simple_bind() failed %d (%s)."), rc, ldap_err2string(rc));
                  lak->status = LAK_NOT_BOUND;
                  return LAK_RETRY;
      }

      rc = lak_user_copy(&(lak->user), user);
      if (rc != LAK_OK)
            return rc;

      lak->status = LAK_BOUND;

      return LAK_OK;
}

static void lak_unbind(
      LAK *lak)
{
      if (!lak)
            return;

      lak_user_free(lak->user);

      if (lak->ld)
            ldap_unbind(lak->ld);

      lak->ld = NULL;
      lak->user = NULL;
      lak->status = LAK_NOT_BOUND;

      return;
}

/* 
 * lak_retrieve - retrieve user@realm values specified by 'attrs'
 */
int lak_retrieve(
      LAK *lak, 
      const char *user, 
      const char *service, 
      const char *realm, 
      const char **attrs, 
      LAK_RESULT **ret)
{
      int rc = 0, i;
      char *filter = NULL;
      char *search_base = NULL;
      LDAPMessage *res = NULL;
      LDAPMessage *entry = NULL;
      BerElement *ber = NULL;
      char *attr = NULL, **vals = NULL, *dn = NULL;
      LAK_USER *lu = NULL;
    
      *ret = NULL;

      if (lak == NULL) {
            syslog(LOG_ERR|LOG_AUTH, "lak_init did not run.");
            return LAK_FAIL;
      }

      if (EMPTY(user))
            return LAK_FAIL;

      if (EMPTY(realm))
            realm = lak->conf->default_realm;

      rc = lak_user(    
            lak->conf->bind_dn,
            lak->conf->id,
            lak->conf->authz_id,
            lak->conf->mech,
            lak->conf->realm,
            lak->conf->password,
            &lu);
      if (rc != LAK_OK)
            return rc;

      rc = lak_bind(lak, lu);
      if (rc != LAK_OK)
            goto done;

      rc = lak_expand_tokens(lak->conf->filter, user, service, realm, NULL, &filter);
      if (rc != LAK_OK)
        goto done;

      rc = lak_expand_tokens(lak->conf->search_base, user, service, realm, NULL, &search_base);
      if (rc != LAK_OK)
        goto done;

      rc = ldap_search_st(lak->ld, search_base, lak->conf->scope, filter, (char **) attrs, 0, &(lak->conf->timeout), &res);
      switch (rc) {
            case LDAP_SUCCESS:
            case LDAP_NO_SUCH_OBJECT:
                  break;
            case LDAP_TIMELIMIT_EXCEEDED:
            case LDAP_BUSY:
            case LDAP_UNAVAILABLE:
            case LDAP_INSUFFICIENT_ACCESS:
                  /*  We do not need to re-connect to the LDAP server 
                      under these conditions */
                  syslog(LOG_ERR|LOG_AUTH, "user ldap_search_st() failed: %s", ldap_err2string(rc));
            rc = LAK_USER_NOT_FOUND;
                  goto done;
            case LDAP_TIMEOUT:
            case LDAP_SERVER_DOWN:
            default:
                  syslog(LOG_ERR|LOG_AUTH, "user ldap_search_st() failed: %s", ldap_err2string(rc));
            rc = LAK_RETRY;
                  lak->status = LAK_NOT_BOUND;
                  goto done;
      }

    i = ldap_count_entries(lak->ld, res);
    if (i != 1) {
        if (i == 0)
                  syslog(LOG_DEBUG|LOG_AUTH, "Entry not found (%s).", filter);
        else
            syslog(LOG_DEBUG|LOG_AUTH, "Duplicate entries found (%s).", filter);
        rc = LAK_USER_NOT_FOUND;
        goto done;
    }
      
    rc = LAK_FAIL;

      if ((entry = ldap_first_entry(lak->ld, res)) != NULL)  {
        for (i=0; attrs[i] != NULL; i++) {
            
            if (!strcmp(attrs[i], dn_attr)) {
                dn = ldap_get_dn(lak->ld, entry);
                if (dn == NULL)
                    goto done;

                rc = lak_result_add(dn_attr, dn, ret);
                if (rc != LAK_OK) {
                    lak_result_free(*ret);
                    *ret = NULL;
                    goto done;
                }
            }
        }

        for (attr = ldap_first_attribute(lak->ld, entry, &ber); attr != NULL; 
            attr = ldap_next_attribute(lak->ld, entry, ber)) {

            vals = ldap_get_values(lak->ld, entry, attr);
            if (vals == NULL)
                continue;

            for (i = 0; vals[i] != NULL; i++) {
                rc = lak_result_add(attr, vals[i], ret);
                if (rc != LAK_OK) {
                    lak_result_free(*ret);
                    *ret = NULL;
                    goto done;
                }
            }

            ldap_value_free(vals);
            vals = NULL;
            ldap_memfree(attr);
            attr = NULL;
        }
    }

done:;
      if (res)
            ldap_msgfree(res);
      if (dn)
            ldap_memfree(dn);
      if (vals)
            ldap_value_free(vals);
      if (attr)
            ldap_memfree(attr);
      if (ber != NULL)
            ber_free(ber, 0);
      if (filter)
            free(filter);
      if (search_base)
            free(search_base);
      if (lu)
            lak_user_free(lu);

      return rc;
}

static int lak_group_member(
      LAK *lak, 
      const char *user, 
      const char *service, 
      const char *realm, 
      const char *dn)
{
      char *group_dn = NULL, *user_dn = NULL;
    char *group_filter = NULL;
    char *group_search_base = NULL;
      struct berval *dn_bv = NULL;
      int rc;
    LAK_RESULT *lres = NULL;
    const char *attrs[] = { dn_attr, NULL };
    const char *group_attrs[] = {"1.1", NULL};

      LDAPMessage *res = NULL;

    user_dn = (char *)dn;

    if (EMPTY(user_dn)) {
        if (lak->conf->use_sasl) {

#if LDAP_VENDOR_VERSION >= 20122
            if (ldap_whoami_s(lak->ld, &dn_bv, NULL, NULL) != LDAP_SUCCESS || !dn_bv) {
                syslog(LOG_ERR|LOG_AUTH, "ldap_whoami_s() failed.");
                rc =  LAK_NOT_GROUP_MEMBER;
                goto done;
            }

            user_dn = dn_bv->bv_val;
#else
            syslog(LOG_ERR|LOG_AUTH, "Your OpenLDAP API does not supported ldap_whoami().");
            rc =  LAK_NOT_GROUP_MEMBER;
            goto done;
#endif

        } else {
            
            rc = lak_retrieve(lak, user, service, realm, attrs, &lres);
            if (rc != LAK_OK)
                goto done;

            user_dn = lres->value;
        }
    }

    if (lak->conf->group_match_method == LAK_GROUP_MATCH_METHOD_ATTR) {

            rc = lak_expand_tokens(lak->conf->group_dn, user, service, realm, NULL, &group_dn);
            if (rc != LAK_OK)
                goto done;

            rc = ((ldap_compare_s(lak->ld, group_dn, lak->conf->group_attr, user_dn)) == LDAP_COMPARE_TRUE ? 
                     LAK_OK : LAK_NOT_GROUP_MEMBER);

    } else if (lak->conf->group_match_method == LAK_GROUP_MATCH_METHOD_FILTER) {

        rc = lak_expand_tokens(lak->conf->group_filter, user, service, realm, user_dn, &group_filter);
        if (rc != LAK_OK)
            goto done;

        rc = lak_expand_tokens(lak->conf->group_search_base, user, service, realm, user_dn, &group_search_base);
        if (rc != LAK_OK)
            goto done;

        rc = ldap_search_st(lak->ld, group_search_base, lak->conf->group_scope, group_filter, (char **) group_attrs, 0, &(lak->conf->timeout), &res);
        switch (rc) {
            case LDAP_SUCCESS:
            case LDAP_NO_SUCH_OBJECT:
                break;
            case LDAP_TIMELIMIT_EXCEEDED:
            case LDAP_BUSY:
            case LDAP_UNAVAILABLE:
            case LDAP_INSUFFICIENT_ACCESS:
                syslog(LOG_ERR|LOG_AUTH, "group ldap_search_st() failed: %s", ldap_err2string(rc));
                rc = LAK_NOT_GROUP_MEMBER;
                goto done;
            case LDAP_TIMEOUT:
            case LDAP_SERVER_DOWN:
            default:
                syslog(LOG_ERR|LOG_AUTH, "group ldap_search_st() failed: %s", ldap_err2string(rc));
                rc = LAK_RETRY;
                lak->status = LAK_NOT_BOUND;
                goto done;
        }

        rc = ( (ldap_count_entries(lak->ld, res) >= 1) ? LAK_OK : LAK_NOT_GROUP_MEMBER );

    } else {

            syslog(LOG_WARNING|LOG_AUTH, "Unknown ldap_group_match_method value.");
            rc = LAK_FAIL;

    }

done:;
      if (res)
            ldap_msgfree(res);
    if (group_dn)
        free(group_dn);
    if (group_filter)
        free(group_filter);
    if (group_search_base)
        free(group_search_base);
    if (lres)
        lak_result_free(lres);
    if (dn_bv)
        ber_bvfree(dn_bv);

      return rc;
}

static int lak_auth_custom(
      LAK *lak,
      const char *user,
      const char *service,
      const char *realm,
      const char *password) 
{
      LAK_RESULT *lres;
      int rc;
      const char *attrs[] = { lak->conf->password_attr, NULL};

      rc = lak_retrieve(lak, user, service, realm, attrs, &lres);
      if (rc != LAK_OK)
            return rc;

    rc = lak_check_password(lres->value, password, NULL);

      if ( rc == LAK_OK &&
          (ISSET(lak->conf->group_dn) ||
         ISSET(lak->conf->group_filter)) )
        rc = lak_group_member(lak, user, service, realm, NULL);
      
      lak_result_free(lres);

      return(rc);
}

static int lak_auth_bind(
      LAK *lak,
      const char *user,
      const char *service,
      const char *realm,
      const char *password) 
{
    LAK_USER *lu = NULL;
      LAK_RESULT *dn = NULL;
      int rc;
      const char *attrs[] = {dn_attr, NULL};

      rc = lak_retrieve(lak, user, service, realm, attrs, &dn);
      if (rc != LAK_OK)
            goto done;

      rc = lak_user(    
            dn->value,
            NULL,
            NULL,
            NULL,
            NULL,
            password,
            &lu);
      if (rc != LAK_OK)
            goto done;

      rc = lak_bind(lak, lu);

      if ( rc == LAK_OK &&
          (ISSET(lak->conf->group_dn) ||
         ISSET(lak->conf->group_filter)) )
            rc = lak_group_member(lak, user, service, realm, dn->value);

done:;
      if (lu)
            lak_user_free(lu);
      if (dn)
            lak_result_free(dn);

      return rc;
}

static int lak_auth_fastbind(
      LAK *lak,
      const char *user,
      const char *service,
      const char *realm,
      const char *password) 
{
      int rc;
      LAK_USER *lu = NULL;
      char *dn = NULL;
      char id[LAK_BUF_LEN];

      *id = '\0';

      if (lak->conf->use_sasl) {
            strlcpy(id, user, LAK_BUF_LEN);
            if (!strchr(id, '@') &&
                (ISSET(realm) ||
                 ISSET(lak->conf->default_realm))) {
                        strlcat(id, "@", LAK_BUF_LEN);
                        strlcat(id, (ISSET(realm) ? realm : lak->conf->default_realm), LAK_BUF_LEN);
            }
      } else {
            rc = lak_expand_tokens(lak->conf->filter, user, service, realm, NULL, &dn);
            if (rc != LAK_OK || 
            EMPTY(dn))
                  goto done;
      }
                  
      rc = lak_user(    
            dn,
            id,
            NULL,
            lak->conf->mech,
            lak->conf->realm,
            password,
            &lu);
      if (rc != LAK_OK)
            goto done;

      rc = lak_bind(lak, lu);

      if ( rc == LAK_OK &&
          (ISSET(lak->conf->group_dn) ||
         ISSET(lak->conf->group_filter)) )
            rc = lak_group_member(lak, user, service, realm, dn);

done:;
      if (lu)
            lak_user_free(lu);
      if (dn != NULL)
            free(dn);

      return rc;
}

int lak_authenticate(
      LAK *lak,
      const char *user,
      const char *service,
      const char *realm,
      const char *password) 
{
      int i;
      int rc;
    int retry = 2;

      if (lak == NULL) {
            syslog(LOG_ERR|LOG_AUTH, "lak_init did not run.");
            return LAK_FAIL;
      }

      if (EMPTY(user))
            return LAK_FAIL;

      if (EMPTY(realm))
            realm = lak->conf->default_realm;

      for (i = 0; authenticator[i].method != -1; i++) {
            if (authenticator[i].method == lak->conf->auth_method) {
                  if (authenticator[i].check) {
                for (;retry > 0; retry--) {
                    rc = (authenticator[i].check)(lak, user, service, realm, password);
                    switch(rc) {
                        case LAK_OK:
                            return LAK_OK;
                        case LAK_RETRY:
                            if (retry > 1) {
                                syslog(LOG_INFO|LOG_AUTH, "Retrying authentication");
                                break;
                            }
                        default:
                            syslog(
                                LOG_DEBUG|LOG_AUTH, 
                                "Authentication failed for %s%s%s: %s (%d)", 
                                user, 
                                (ISSET(realm) ? "/" : ""), 
                                (ISSET(realm) ? realm : ""), 
                                lak_error(rc), 
                                rc);
                            return LAK_FAIL;
                    }
                }
            }
                  break;
            }
      }

    /* Should not get here */
    syslog(LOG_DEBUG|LOG_AUTH, "Authentication method not setup properly (%d)", lak->conf->auth_method);

      return LAK_FAIL;
}

char *lak_error(
    const int errno)
{

    switch (errno) {
        case LAK_OK:
            return "Success";
        case LAK_FAIL:
            return "Generic error";
        case LAK_NOMEM:
            return "Out of memory";
        case LAK_RETRY:
            return "Retry condition (ldap server connection reset or broken)";
        case LAK_NOT_GROUP_MEMBER:
            return "Group member check failed";
        case LAK_INVALID_PASSWORD:
            return "Invalid password";
        case LAK_USER_NOT_FOUND:
            return "User not found";
        case LAK_BIND_FAIL:
            return "Bind to ldap server failed (invalid user/password or insufficient access)";
        case LAK_CONNECT_FAIL:
            return "Cannot connect to ldap server (configuration error)";
        default:
            return "Unknow error";
    }
}

static char *lak_result_get(
    const LAK_RESULT *lres, 
    const char *attr) 
{
    LAK_RESULT *ptr;


    for (ptr = (LAK_RESULT *)lres; ptr != NULL; ptr = ptr->next)
        if (!strcasecmp(ptr->attribute, attr))
            return ptr->value;

    return NULL;
}

static int lak_result_add(
      const char *attr,
      const char *val,
      LAK_RESULT **ret)  
{
      LAK_RESULT *lres;
      
      lres = (LAK_RESULT *) malloc(sizeof(LAK_RESULT));
      if (lres == NULL) {
            return LAK_NOMEM;
      }

      lres->next = NULL;

      lres->attribute = strdup(attr);
      if (lres->attribute == NULL) {
            lak_result_free(lres);
            return LAK_NOMEM;
      }

      lres->value = strdup(val);
      if (lres->value == NULL) {
            lak_result_free(lres);
            return LAK_NOMEM;
      }
      lres->len = strlen(lres->value);

      lres->next = *ret;

      *ret = lres;
      return LAK_OK;
}

void lak_result_free(
      LAK_RESULT *res) 
{
      LAK_RESULT *lres, *ptr = res;

      if (ptr == NULL)
            return;

      for (lres = ptr; lres != NULL; lres = ptr) {

            ptr = lres->next;

            if (lres->attribute != NULL) {
                  memset(lres->attribute, 0, strlen(lres->attribute));
                  free(lres->attribute);  
            }

            if (lres->value != NULL) {
                  memset(lres->value, 0, strlen(lres->value));
                  free(lres->value);      
            }

            lres->next = NULL;

            free(lres);
      }

      return;
}

static int lak_check_password(
      const char *hash, 
      const char *passwd,
      void *rock __attribute__((unused))) 
{
      int i, hlen;

      if (EMPTY(hash))
            return LAK_INVALID_PASSWORD;

      if (EMPTY(passwd))
            return LAK_INVALID_PASSWORD;

      for (i = 0; password_scheme[i].hash != NULL; i++) {

            hlen = strlen(password_scheme[i].hash);
            if (!strncasecmp(password_scheme[i].hash, hash, hlen)) {
                  if (password_scheme[i].check) {
                        return (password_scheme[i].check)(hash+hlen, passwd,
                                                password_scheme[i].rock);
                  }
                  return LAK_FAIL;
            }
      }

      return strcmp(hash, passwd) ? LAK_INVALID_PASSWORD : LAK_OK;
}

#ifdef HAVE_OPENSSL

static int lak_base64_decode(
      const char *src,
      char **ret,
      int *rlen) 
{

      int rc, i, tlen = 0;
      char *text;
      EVP_ENCODE_CTX EVP_ctx;

      text = (char *)malloc(((strlen(src)+3)/4 * 3) + 1);
      if (text == NULL)
            return LAK_NOMEM;

      EVP_DecodeInit(&EVP_ctx);
      rc = EVP_DecodeUpdate(&EVP_ctx, text, &i, (char *)src, strlen(src));
      if (rc < 0) {
            free(text);
            return LAK_FAIL;
      }
      tlen += i;
      EVP_DecodeFinal(&EVP_ctx, text, &i); 

      *ret = text;
      if (rlen != NULL)
            *rlen = tlen;

      return LAK_OK;
}

static int lak_check_hashed(
      const char *hash,
      const char *passwd,
      void *rock)
{
      int rc, clen;
      LAK_HASH_ROCK *hrock = (LAK_HASH_ROCK *) rock;
      EVP_MD_CTX mdctx;
      const EVP_MD *md;
      unsigned char digest[EVP_MAX_MD_SIZE];
      char *cred;

      md = EVP_get_digestbyname(hrock->mda);
      if (!md)
            return LAK_FAIL;

      rc = lak_base64_decode(hash, &cred, &clen);
      if (rc != LAK_OK)
            return rc;

      EVP_DigestInit(&mdctx, md);
      EVP_DigestUpdate(&mdctx, passwd, strlen(passwd));
      if (hrock->salted) {
            EVP_DigestUpdate(&mdctx, &cred[EVP_MD_size(md)],
                         clen - EVP_MD_size(md));
      }
      EVP_DigestFinal(&mdctx, digest, NULL);

      rc = memcmp((char *)cred, (char *)digest, EVP_MD_size(md));
      free(cred);
      return rc ? LAK_INVALID_PASSWORD : LAK_OK;
}

#endif /* HAVE_OPENSSL */

static int lak_check_crypt(
      const char *hash,
      const char *passwd,
      void *rock __attribute__((unused))) 
{
      char *cred;

      if (strlen(hash) < 2 )
            return LAK_INVALID_PASSWORD;

      cred = crypt(passwd, hash);
      if (EMPTY(cred))
            return LAK_INVALID_PASSWORD;

      return strcmp(hash, cred) ? LAK_INVALID_PASSWORD : LAK_OK;
}

#endif /* AUTH_LDAP */

Generated by  Doxygen 1.6.0   Back to index