diff -up ./doc/pam_pkcs11.xml.uid-attribute ./doc/pam_pkcs11.xml
--- ./doc/pam_pkcs11.xml.uid-attribute 2008-08-28 12:12:46.000000000 -0700
+++ ./doc/pam_pkcs11.xml 2015-07-06 19:24:37.485287137 -0700
@@ -1679,6 +1679,8 @@ ldap_mapper configuration file shows lik
base = "ou=People,o=example,c=com";
attribute = "userCertificate";
filter = ""
+ uid_attribute = "uid";
+ attribute_map = "", "";
# SSL/TLS-Settings
ssl = tls
# tls_randfile = ...
@@ -1757,6 +1759,43 @@ attribute. All certificates are utilized
+
+uid_attribute
+The attribute in an LDAP entry which contains the
+user's login name. When the module needs to determine the user's
+login name from the information in the certificate, if this
+option is set, the module can search for an LDAP entry which
+contains the certificate that was read from the card in the
+attribute named by the setting and read the
+user's login name from this attribute. If not, the module must
+iterate through all known users, checking for each user if the
+certificate on the card matches that user.
+
+
+
+
+attribute_map
+A list of sets of conditions which can specify how the
+module should locate an entry which corresponds to the user.
+Each list item consists of a set of one or more conditions
+consisting of an LDAP attribute and a certificate attribute, with
+conditions in the same set being separated by a
+character. An LDAP entry is considered to match a user if all of
+the conditions are met. The recognized certificate attributes include
+
+"cn" a commonName attribute value from the certificate's subject
+"subject" the certificate's entire subject name
+"kpn" a Kerberos principal name subjectAltName value
+"email" an rfc822Name subjectAltName value
+"upn" a Microsoft Universal Principal Name subjectAltName value
+"uid" a UID attribute value from the certificate's subject
+"cert" the entire certificate
+
+If no is specified, the default method
+is to search for entries which contain the card certificate in
+the attribute named by the setting.
+
+
filter
diff -up ./doc/README.ldap_mapper.uid-attribute ./doc/README.ldap_mapper
--- ./doc/README.ldap_mapper.uid-attribute 2008-08-28 12:12:46.000000000 -0700
+++ ./doc/README.ldap_mapper 2015-07-06 19:24:37.485287137 -0700
@@ -27,6 +27,10 @@ pam_pkcs11.conf:
attribute = "userCertificate";
# Searchfilter for user entry. Must only let pass user entry for the login user.
filter = "(&(objectClass=posixAccount)(uid=%s))"
+ # Attribute of user entry which contains the user's login name (optional)
+ uid_attribute = "uid";
+ # List of sets of ldap attribute / cert attribute pairs (optional)
+ attribute_map = "uid=uid&mail=email", "krbprincipalname=upn", "userCertificate;binary=cert";
}
(...)
diff -up ./src/mappers/ldap_mapper.c.uid-attribute ./src/mappers/ldap_mapper.c
--- ./src/mappers/ldap_mapper.c.uid-attribute 2015-07-06 19:24:37.479287249 -0700
+++ ./src/mappers/ldap_mapper.c 2015-07-06 20:13:01.578027801 -0700
@@ -101,14 +101,12 @@ static const char *binddn="";
static const char *passwd="";
static const char *base="ou=People,o=example,c=com";
static const char *attribute="userCertificate";
+static const char *uid_attribute;
+static const scconf_list *attribute_map;
static const char *filter="(&(objectClass=posixAccount)(uid=%s)";
static int searchtimeout=20;
static int ignorecase=0;
-#ifdef HAVE_NSS
-static X509 **ldap_x509;
-#else
-static const X509 **ldap_x509;
-#endif
+static char *uid_attribute_value;
static int certcnt=0;
static ldap_ssl_options_t ssl_on = SSL_OFF;
@@ -586,31 +584,335 @@ static int ldap_add_uri (char **uris, co
return 0;
}
+/* Build a filter suitable for locating the entry for the named user. */
+static void
+ldap_x509_as_binary(X509 *x509, unsigned char **der, size_t *der_len)
+{
+#ifdef HAVE_NSS
+ *der_len = 0;
+ *der = malloc(x509->derCert.len);
+ if (*der != NULL) {
+ *der_len = x509->derCert.len;
+ memcpy(*der, x509->derCert.data, *der_len);
+ }
+#else
+ unsigned char *p = NULL, *q;
+ int len;
+
+ *der_len = 0;
+ *der = NULL;
+ len = i2d_X509(x509, NULL);
+ if (len > 0) {
+ p = malloc(len);
+ if (p != NULL) {
+ q = p;
+ if (i2d_X509(x509, &p) == len) {
+ *der = q;
+ *der_len = p - q;
+ }
+ }
+ }
+#endif
+}
+
+/* Encode anything that isn't printable in the binary string as a hex escape,
+ * and return the result as a NUL-terminated string. */
+static char *
+ldap_encode_escapes(const unsigned char *binary, size_t length)
+{
+ char *ret;
+ unsigned int i, j;
+
+ ret = malloc(length * 3 + 1);
+ if (ret == NULL) {
+ DBG("ldap_encode_escapes(): out of memory");
+ return NULL;
+ }
+ for (i = 0, j = 0; i < length; i++) {
+ if (((binary[i] >= '0') && (binary[i] <= '9')) ||
+ ((binary[i] >= 'a') && (binary[i] <= 'z')) ||
+ ((binary[i] >= 'A') && (binary[i] <= 'Z'))) {
+ ret[j++] = binary[i];
+ } else {
+ ret[j++] = '\\';
+ ret[j++] = "0123456789abcdef"[(binary[i] >> 4) & 0x0f];
+ ret[j++] = "0123456789abcdef"[binary[i] & 0x0f];
+ }
+ }
+ ret[j] = '\0';
+ return ret;
+}
+
+/* Build a subfilter for matching the passed-in certificate against the
+ * configured attribute. */
+static char *
+ldap_build_default_cert_filter(X509 *x509)
+{
+ char *buf, *cert;
+ unsigned char *der;
+ size_t buf_len, der_len;
+
+ ldap_x509_as_binary(x509, &der, &der_len);
+ if (der == NULL) {
+ DBG("ldap_build_cert_filter(): failed to encode certificate");
+ return NULL;
+ }
+ cert = ldap_encode_escapes(der, der_len);
+ free(der);
+ if (cert == NULL) {
+ DBG("ldap_build_cert_filter(): failed to escape certificate");
+ return NULL;
+ }
+ buf_len = 1 + strlen(attribute) + 1 + strlen(cert) + 2;
+ buf = malloc(buf_len);
+ if (buf == NULL) {
+ DBG("ldap_build_cert_filter(): out of memory");
+ free(cert);
+ return NULL;
+ }
+ snprintf(buf, buf_len, "(%s=%s)", attribute, cert);
+ free(cert);
+ return buf;
+}
+
+/* Build a subfilter for matching the passed-in certificate using the mapping
+ * information, or against the configured attribute. */
+static char *
+ldap_build_partial_cert_filter(const char *map, X509 *x509)
+{
+ char *buf, *certs[2] = {NULL, NULL}, **values = NULL;
+ unsigned char *der;
+ const char *p, *q;
+ size_t buf_len, der_len, len, n;
+ int i;
+
+ p = strchr(map, '=');
+ if (p == NULL) {
+ DBG1("ldap_build_cert_filter(): error parsing filter '%s'",
+ map);
+ return NULL;
+ }
+ q = p + strcspn(p, "&");
+ if (strncmp(p + 1, "cn", q - p - 1) == 0) {
+ values = cert_info(x509, CERT_CN, ALGORITHM_NULL);
+ } else
+ if (strncmp(p + 1, "subject", q - p - 1) == 0) {
+ values = cert_info(x509, CERT_SUBJECT, ALGORITHM_NULL);
+ } else
+ if (strncmp(p + 1, "kpn", q - p - 1) == 0) {
+ values = cert_info(x509, CERT_KPN, ALGORITHM_NULL);
+ } else
+ if (strncmp(p + 1, "email", q - p - 1) == 0) {
+ values = cert_info(x509, CERT_EMAIL, ALGORITHM_NULL);
+ } else
+ if (strncmp(p + 1, "upn", q - p - 1) == 0) {
+ values = cert_info(x509, CERT_UPN, ALGORITHM_NULL);
+ } else
+ if (strncmp(p + 1, "uid", q - p - 1) == 0) {
+ values = cert_info(x509, CERT_UID, ALGORITHM_NULL);
+ } else
+ if (strncmp(p + 1, "cert", q - p - 1) == 0) {
+ ldap_x509_as_binary(x509, &der, &der_len);
+ if (der == NULL) {
+ DBG("ldap_build_cert_filter(): error encoding "
+ "certificate");
+ return NULL;
+ }
+ certs[0] = ldap_encode_escapes(der, der_len);
+ free(der);
+ if (certs[0] == NULL) {
+ DBG("ldap_build_cert_filter(): error escaping "
+ "certificate");
+ return NULL;
+ }
+ values = certs;
+ } else {
+ DBG2("ldap_build_cert_filter(): unrecognized certificate "
+ "attribute '%.*s'", q - p - 1, p + 1);
+ return NULL;
+ }
+ if (values == NULL) {
+ DBG2("ldap_build_cert_filter(): no values for certificate "
+ "attribute '%.*s'", q - p - 1, p + 1);
+ return NULL;
+ }
+ DBG4("ldap_build_cert_filter(): building subfilter '%.*s'='%.*s'",
+ p - map, map, q - p - 1, p + 1);
+ for (n = 0, buf_len = 0; values[n] != NULL; n++) {
+ buf_len++;
+ buf_len += (p - map);
+ buf_len++;
+ buf_len += strlen(values[n]);
+ buf_len++;
+ }
+ buf_len += (n > 1) ? 4 : 1;
+ buf = malloc(buf_len);
+ if (buf == NULL) {
+ DBG("ldap_build_cert_filter(): out of memory");
+ free(certs[0]);
+ return NULL;
+ }
+ i = 0;
+ if (n > 1) {
+ strcpy(buf, "(|");
+ i += 2;
+ }
+ for (n = 0; values[n] != NULL; n++) {
+ buf[i++] = '(';
+ memcpy(buf + i, map, p - map);
+ i += (p - map);
+ buf[i++] = '=';
+ len = strlen(values[n]);
+ memcpy(buf + i, values[n], len);
+ i += len;
+ buf[i++] = ')';
+ }
+ if (n > 1) {
+ buf[i++] = ')';
+ }
+ buf[i] = '\0';
+ free(certs[0]);
+ return buf;
+}
+
+/* Build a filter for matching the passed-in certificate using the mapping
+ * information, or against the configured attribute. */
+static char *
+ldap_build_cert_filter(const char *map, X509 *x509)
+{
+ char *buf = NULL, *tmp, *sub;
+ const char *p;
+ size_t length, n;
+
+ if (map == NULL) {
+ DBG("ldap_build_cert_filter(): building default filter");
+ return ldap_build_default_cert_filter(x509);
+ }
+ DBG1("ldap_build_cert_filter(): building filter '%s'", map);
+ p = map;
+ n = 0;
+ while (*p != '\0') {
+ sub = ldap_build_partial_cert_filter(p, x509);
+ if (sub == NULL) {
+ free(buf);
+ return NULL;
+ }
+ if (buf != NULL) {
+ length = strlen(buf) + strlen(sub) + 1;
+ tmp = malloc(length);
+ if (tmp == NULL) {
+ free(buf);
+ free(sub);
+ return NULL;
+ }
+ snprintf(tmp, length, "%s%s", buf, sub);
+ free(buf);
+ free(sub);
+ buf = tmp;
+ } else {
+ buf = sub;
+ }
+ n++;
+ p += strcspn(p, "&");
+ p += strspn(p, "&");
+ }
+ if (n > 1) {
+ length = strlen(buf) + 4;
+ tmp = malloc(length);
+ if (tmp == NULL) {
+ free(buf);
+ return NULL;
+ }
+ snprintf(tmp, length, "(&%s)", buf);
+ free(buf);
+ buf = tmp;
+ }
+ return buf;
+}
+
+/* Build a filter suitable for locating the entry for the named user. */
+static char *
+ldap_build_filter(const char *filter, const char *login, const char *map,
+ X509 *x509)
+{
+ char *buf, *user_filter, *escaped, *cert_filter;
+ unsigned char *der;
+ size_t buf_len, user_filter_len, der_len;
+ unsigned int i;
+
+ /* If no user name is specified, this is a search across all users. */
+ if (login != NULL) {
+ escaped = ldap_encode_escapes(login, strlen(login));
+ } else {
+ escaped = strdup("*");
+ }
+ if (escaped == NULL) {
+ DBG1("ldap_build_filter(): error escaping user name '%s'",
+ login);
+ return NULL;
+ }
+
+ /* Build a user filter using the supplied filter and user name. */
+ user_filter_len = strlen(filter) + strlen(escaped) + 1;
+ user_filter = malloc(user_filter_len);
+ if (user_filter == NULL) {
+ DBG("ldap_build_filter(): out of memory for user filter");
+ free(escaped);
+ return NULL;
+ }
+ snprintf(user_filter, user_filter_len, filter, escaped);
+ free(escaped);
+
+ /* Build the part of the filter that's specific to the certificate. */
+ cert_filter = ldap_build_cert_filter(map, x509);
+ if (cert_filter == NULL) {
+ DBG("ldap_build_filter(): error building certificate filter");
+ free(user_filter);
+ return NULL;
+ }
+
+ /* Build a filter combining the user filter and the certificate. */
+ buf_len = 3 + strlen(user_filter) + 2 + 2 + strlen(cert_filter) + 2;
+ buf = malloc(buf_len);
+ if (buf != NULL) {
+ if (filter[0] == '(') {
+ snprintf(buf, buf_len, "(&%s%s)", user_filter,
+ cert_filter);
+ } else {
+ snprintf(buf, buf_len, "(&(%s)%s)", user_filter,
+ cert_filter);
+ }
+ } else {
+ DBG("ldap_build_filter(): out of memory");
+ }
+
+ free(user_filter);
+ free(cert_filter);
+ return buf;
+}
+
/**
* Get certificate from LDAP-Server.
*/
-static int ldap_get_certificate(const char *login) {
+static int ldap_get_certificate(const char *login, X509 *x509) {
LDAP *ldap_connection;
int ret, entries;
LDAPMessage *res;
LDAPMessage *entry;
- struct berval **bvals = NULL;
+ struct berval **bvals = NULL, *bv;
BerElement *ber = NULL;
- char *name = NULL;
- char filter_str[100];
- char *attrs[2];
+ char *filter_str;
+ char *attrs[3];
int rv;
-#ifndef HAVE_NSS
- void *bv_val;
-#endif
char uri[4096];
char uribuf[4096];
char *uris[LDAP_CONFIG_URI_MAX + 1];
const char *p;
int current_uri = 0, start_uri = 0;
+ const scconf_list *mapping;
char *buffer;
size_t buflen;
@@ -618,14 +920,16 @@ static int ldap_get_certificate(const ch
uris[0] = NULL;
attrs[0] = (char *)attribute;
- attrs[1] = NULL;
-
- DBG1("ldap_get_certificate(): begin login = %s", login);
-
- /* Put the login to the %s in Filterstring */
- snprintf(filter_str, sizeof(filter_str), filter, login);
+ attrs[1] = (char *)uid_attribute;
+ attrs[2] = NULL;
+ free((char *)uid_attribute_value);
+ uid_attribute_value = NULL;
- DBG1("ldap_get_certificate(): filter_str = %s", filter_str);
+ if (login != NULL) {
+ DBG1("ldap_get_certificate(): begin login = %s", login);
+ } else {
+ DBG("ldap_get_certificate(): begin login unknown");
+ }
/* parse and split URI config entry */
buffer = uribuf;
@@ -709,14 +1013,55 @@ static int ldap_get_certificate(const ch
server again. Perhaps create a state file/smem/etc. ?
*/
- rv = ldap_search_s(
- ldap_connection,
- base,
- sscope[scope],
- filter_str,
- attrs,
- 0,
- &res);
+ /* Search for matching entries. */
+ for (mapping = attribute_map;; mapping = mapping->next) {
+ /* Walk the list of mappings, and if we're out of those, let
+ * the ldap_build_filter() function just build one that uses
+ * the certificate. */
+ if (mapping != NULL) {
+ DBG1("ldap_get_certificate(): building filter_str "
+ "from template '%s'", mapping->data);
+ } else {
+ DBG("ldap_get_certificate(): building default "
+ "filter_str");
+ }
+ filter_str = ldap_build_filter(filter, login,
+ mapping ? mapping->data : NULL,
+ x509);
+ if (filter_str == NULL) {
+ DBG("ldap_get_certificate(): error building filter_str");
+ if (mapping) {
+ continue;
+ }
+ break;
+ }
+ DBG1("ldap_get_certificate(): searching with filter_str = %s",
+ filter_str);
+ rv = ldap_search_s(ldap_connection,
+ base,
+ sscope[scope],
+ filter_str,
+ attrs,
+ 0,
+ &res);
+ free(filter_str);
+ /* The first successful search means we're done. */
+ if ((rv == LDAP_SUCCESS) &&
+ (ldap_count_entries(ldap_connection, res) > 0)) {
+ DBG("ldap_get_certificate(): found an entry");
+ break;
+ }
+ DBG("ldap_get_certificate(): no matching entries");
+ /* If this was the fallback (cert-only) search, we're done. */
+ if (mapping == NULL) {
+ break;
+ }
+ }
+ if (filter_str == NULL) {
+ DBG("ldap_get_certificate(): unable to build any filter_str");
+ return(-8);
+ }
+
if ( rv != LDAP_SUCCESS ) {
DBG1("ldap_search_s() failed: %s", ldap_err2string(rv));
ldap_unbind_s(ldap_connection);
@@ -742,65 +1087,35 @@ static int ldap_get_certificate(const ch
return(-4);
}
- /* Only first attribute is used. See comment above... */
- if ( NULL == (name = ldap_first_attribute(ldap_connection, res, &ber))){
- DBG("ldap_first_attribute() failed (rc=%d)");
- ldap_unbind_s(ldap_connection);
- return(-5);
- }
- DBG1("attribute name = %s", name);
-
- bvals = ldap_get_values_len(ldap_connection, entry, name);
+ /* Count the number of certificates in the entry. */
+ DBG1("attribute name = %s", attribute);
+ bvals = ldap_get_values_len(ldap_connection, entry, attribute);
certcnt = ldap_count_values_len(bvals);
-
DBG1("number of user certificates = %d", certcnt);
+ ldap_value_free_len(bvals);
- ldap_x509 = malloc(sizeof(X509*) * certcnt );
- if (NULL == ldap_x509)
- {
- DBG("not enough memory");
- return(-7);
- }
+ if (uid_attribute != NULL) {
+ /* Try to retrieve the user's login name from the
+ * specified attribute in the entry. */
+ bvals = ldap_get_values_len(ldap_connection, entry,
+ uid_attribute);
+ DBG2("number of user names ('%s' values) = %d",
+ uid_attribute, ldap_count_values_len(bvals));
+ if (ldap_count_values_len(bvals) == 1) {
+ bv = bvals[0];
+ uid_attribute_value = malloc(bv->bv_len + 1);
+ if (uid_attribute_value != NULL) {
+ memcpy(uid_attribute_value, bv->bv_val,
+ bv->bv_len);
+ uid_attribute_value[bv->bv_len] = '\0';
+ }
+ }
+ ldap_value_free_len(bvals);
+ }
rv = 0;
- while(rv < certcnt )
- {
- /* SaW: not nifty, but otherwise gcc doesn't optimize */
-#ifdef HAVE_NSS
- {
- SECItem derdata;
- derdata.data = bvals[rv]->bv_val;
- derdata.len = bvals[rv]->bv_len;
- ldap_x509[rv] = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
- &derdata, NULL, 0, 1);
- }
-#else
- bv_val = &bvals[rv]->bv_val;
- ldap_x509[rv] = d2i_X509(NULL, ((const unsigned char **) bv_val), bvals[rv]->bv_len);
-#endif
- if (NULL == ldap_x509[rv]) {
- DBG1("d2i_X509() failed for certificate %d", rv);
- free(ldap_x509);
-#ifdef HAVE_NSS
- {
- for (rv=0; rvnext) {
+ DBG1("attribute_map = %s", attribute_map->data);
+ }
DBG1("filter = %s", filter);
DBG1("searchtimeout = %d", searchtimeout);
DBG1("ssl_on = %d", ssl_on);
@@ -895,35 +1217,16 @@ static int ldap_mapper_match_user(X509 *
int match_found = 0;
int i=0;
- if ( 1 != ldap_get_certificate(login)){
+ if ( 1 != ldap_get_certificate(login, x509)){
DBG("ldap_get_certificate() failed");
match_found = 0;
} else {
- /* TODO: maybe compare public keys instead of hashes */
- while( i