This is a forward-port of the OpenSSH LPK support patch. It adds support for storing OpenSSH public keys in LDAP. It also supports grouping of machines in the LDAP data to limit users to specific machines. The latest homepage for the LPK project is: http://code.google.com/p/openssh-lpk/ The 0.3.10 version of the patch includes a fix for 64-bit platforms, as discovered by Gentoo, where the bind timeout and search timeout values were not being parsed correctly: http://bugs.gentoo.org/210110 Forward-ported-from: openssh-lpk-5.1p1-0.3.9.patch Signed-off-by: Robin H. Johnson diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/auth2-pubkey.c openssh-5.1p1+lpk/auth2-pubkey.c --- openssh-5.1p1.orig/auth2-pubkey.c 2008-07-03 19:54:25.000000000 -0700 +++ openssh-5.1p1+lpk/auth2-pubkey.c 2008-08-23 15:02:47.000000000 -0700 @@ -55,6 +55,10 @@ #include "monitor_wrap.h" #include "misc.h" +#ifdef WITH_LDAP_PUBKEY +#include "ldapauth.h" +#endif + /* import */ extern ServerOptions options; extern u_char *session_id2; @@ -187,10 +191,79 @@ u_long linenum = 0; Key *found; char *fp; +#ifdef WITH_LDAP_PUBKEY + ldap_key_t * k; + unsigned int i = 0; +#endif /* Temporarily use the user's uid. */ temporarily_use_uid(pw); +#ifdef WITH_LDAP_PUBKEY + found_key = 0; + /* allocate a new key type */ + found = key_new(key->type); + + /* first check if the options is enabled, then try.. */ + if (options.lpk.on) { + debug("[LDAP] trying LDAP first uid=%s",pw->pw_name); + if (ldap_ismember(&options.lpk, pw->pw_name) > 0) { + if ((k = ldap_getuserkey(&options.lpk, pw->pw_name)) != NULL) { + /* Skip leading whitespace, empty and comment lines. */ + for (i = 0 ; i < k->num ; i++) { + /* dont forget if multiple keys to reset options */ + char *cp, *options = NULL; + + for (cp = (char *)k->keys[i]->bv_val; *cp == ' ' || *cp == '\t'; cp++) + ; + if (!*cp || *cp == '\n' || *cp == '#') + continue; + + if (key_read(found, &cp) != 1) { + /* no key? check if there are options for this key */ + int quoted = 0; + debug2("[LDAP] user_key_allowed: check options: '%s'", cp); + options = cp; + for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) { + if (*cp == '\\' && cp[1] == '"') + cp++; /* Skip both */ + else if (*cp == '"') + quoted = !quoted; + } + /* Skip remaining whitespace. */ + for (; *cp == ' ' || *cp == '\t'; cp++) + ; + if (key_read(found, &cp) != 1) { + debug2("[LDAP] user_key_allowed: advance: '%s'", cp); + /* still no key? advance to next line*/ + continue; + } + } + + if (key_equal(found, key) && + auth_parse_options(pw, options, file, linenum) == 1) { + found_key = 1; + debug("[LDAP] matching key found"); + fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); + verbose("[LDAP] Found matching %s key: %s", key_type(found), fp); + + /* restoring memory */ + ldap_keys_free(k); + xfree(fp); + restore_uid(); + key_free(found); + return found_key; + break; + } + }/* end of LDAP for() */ + } else { + logit("[LDAP] no keys found for '%s'!", pw->pw_name); + } + } else { + logit("[LDAP] '%s' is not in '%s'", pw->pw_name, options.lpk.sgroup); + } + } +#endif debug("trying public key file %s", file); f = auth_openkeyfile(file, pw, options.strict_modes); diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/auth-rsa.c openssh-5.1p1+lpk/auth-rsa.c --- openssh-5.1p1.orig/auth-rsa.c 2008-07-02 05:37:30.000000000 -0700 +++ openssh-5.1p1+lpk/auth-rsa.c 2008-08-23 15:02:47.000000000 -0700 @@ -174,10 +174,96 @@ FILE *f; u_long linenum = 0; Key *key; +#ifdef WITH_LDAP_PUBKEY + ldap_key_t * k; + unsigned int i = 0; +#endif /* Temporarily use the user's uid. */ temporarily_use_uid(pw); +#ifdef WITH_LDAP_PUBKEY + /* here is the job */ + key = key_new(KEY_RSA1); + + if (options.lpk.on) { + debug("[LDAP] trying LDAP first uid=%s", pw->pw_name); + if ( ldap_ismember(&options.lpk, pw->pw_name) > 0) { + if ( (k = ldap_getuserkey(&options.lpk, pw->pw_name)) != NULL) { + for (i = 0 ; i < k->num ; i++) { + char *cp, *options = NULL; + + for (cp = k->keys[i]->bv_val; *cp == ' ' || *cp == '\t'; cp++) + ; + if (!*cp || *cp == '\n' || *cp == '#') + continue; + + /* + * Check if there are options for this key, and if so, + * save their starting address and skip the option part + * for now. If there are no options, set the starting + * address to NULL. + */ + if (*cp < '0' || *cp > '9') { + int quoted = 0; + options = cp; + for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) { + if (*cp == '\\' && cp[1] == '"') + cp++; /* Skip both */ + else if (*cp == '"') + quoted = !quoted; + } + } else + options = NULL; + + /* Parse the key from the line. */ + if (hostfile_read_key(&cp, &bits, key) == 0) { + debug("[LDAP] line %d: non ssh1 key syntax", i); + continue; + } + /* cp now points to the comment part. */ + + /* Check if the we have found the desired key (identified by its modulus). */ + if (BN_cmp(key->rsa->n, client_n) != 0) + continue; + + /* check the real bits */ + if (bits != (unsigned int)BN_num_bits(key->rsa->n)) + logit("[LDAP] Warning: ldap, line %lu: keysize mismatch: " + "actual %d vs. announced %d.", (unsigned long)i, BN_num_bits(key->rsa->n), bits); + + /* We have found the desired key. */ + /* + * If our options do not allow this key to be used, + * do not send challenge. + */ + if (!auth_parse_options(pw, options, "[LDAP]", (unsigned long) i)) + continue; + + /* break out, this key is allowed */ + allowed = 1; + + /* add the return stuff etc... */ + /* Restore the privileged uid. */ + restore_uid(); + + /* return key if allowed */ + if (allowed && rkey != NULL) + *rkey = key; + else + key_free(key); + + ldap_keys_free(k); + return (allowed); + } + } else { + logit("[LDAP] no keys found for '%s'!", pw->pw_name); + } + } else { + logit("[LDAP] '%s' is not in '%s'", pw->pw_name, options.lpk.sgroup); + } + } +#endif /* The authorized keys. */ file = authorized_keys_file(pw); debug("trying public RSA key file %s", file); diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/config.h.in openssh-5.1p1+lpk/config.h.in --- openssh-5.1p1.orig/config.h.in 2008-07-21 01:30:49.000000000 -0700 +++ openssh-5.1p1+lpk/config.h.in 2008-08-23 15:02:47.000000000 -0700 @@ -560,6 +560,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_IF_TUN_H +/* Define if you want LDAP support */ +#undef WITH_LDAP_PUBKEY + /* Define if your libraries define login() */ #undef HAVE_LOGIN --- openssh-5.7p1/configure.orig 2011-01-22 11:29:11.000000000 +0200 +++ openssh-5.7p1/configure 2011-01-24 16:33:06.271393457 +0200 @@ -1348,6 +1348,7 @@ --with-tcp-wrappers[=PATH] Enable tcpwrappers support (optionally in PATH) --with-libedit[=PATH] Enable libedit support for sftp --with-audit=module Enable audit support (modules=debug,bsm,linux) + --with-ldap[=PATH] Enable LDAP pubkey support (optionally in PATH) --with-ssl-dir=PATH Specify path to OpenSSL installation --without-openssl-header-check Disable OpenSSL version consistency check --with-ssl-engine Enable OpenSSL (hardware) ENGINE support @@ -12198,6 +12199,85 @@ fi +# Check whether user wants LDAP support +LDAP_MSG="no" + +# Check whether --with-ldap was given. +if test "${with_ldap+set}" = set; then + withval=$with_ldap; + if test "x$withval" != "xno" ; then + + if test "x$withval" != "xyes" ; then + CPPFLAGS="$CPPFLAGS -I${withval}/include" + LDFLAGS="$LDFLAGS -L${withval}/lib" + fi + + +cat >>confdefs.h <<\_ACEOF +#define WITH_LDAP_PUBKEY 1 +_ACEOF + + LIBS="-lldap $LIBS" + LDAP_MSG="yes" + + { echo "$as_me:$LINENO: checking for LDAP support" >&5 +echo $ECHO_N "checking for LDAP support... $ECHO_C" >&6; } + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include + #include +int +main () +{ +(void)ldap_init(0, 0); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + { echo "$as_me:$LINENO: result: yes" >&5 +echo "${ECHO_T}yes" >&6; } +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + + { echo "$as_me:$LINENO: result: no" >&5 +echo "${ECHO_T}no" >&6; } + { { echo "$as_me:$LINENO: error: ** Incomplete or missing ldap libraries **" >&5 +echo "$as_me: error: ** Incomplete or missing ldap libraries **" >&2;} + { (exit 1); exit 1; }; } + + +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + fi + + +fi + + @@ -31970,6 +32050,7 @@ echo " Smartcard support: $SCARD_MSG" echo " S/KEY support: $SKEY_MSG" echo " TCP Wrappers support: $TCPW_MSG" +echo " LDAP support: $LDAP_MSG" echo " MD5 password support: $MD5_MSG" echo " libedit support: $LIBEDIT_MSG" echo " Solaris process contract support: $SPC_MSG" diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/configure.ac openssh-5.1p1+lpk/configure.ac --- openssh-5.1p1.orig/configure.ac 2008-07-09 04:07:19.000000000 -0700 +++ openssh-5.1p1+lpk/configure.ac 2008-08-23 15:02:47.000000000 -0700 @@ -1299,6 +1299,37 @@ esac ] ) +# Check whether user wants LDAP support +LDAP_MSG="no" +AC_ARG_WITH(ldap, + [ --with-ldap[[=PATH]] Enable LDAP pubkey support (optionally in PATH)], + [ + if test "x$withval" != "xno" ; then + + if test "x$withval" != "xyes" ; then + CPPFLAGS="$CPPFLAGS -I${withval}/include" + LDFLAGS="$LDFLAGS -L${withval}/lib" + fi + + AC_DEFINE([WITH_LDAP_PUBKEY], 1, [Enable LDAP pubkey support]) + LIBS="-lldap $LIBS" + LDAP_MSG="yes" + + AC_MSG_CHECKING([for LDAP support]) + AC_TRY_COMPILE( + [#include + #include ], + [(void)ldap_init(0, 0);], + [AC_MSG_RESULT(yes)], + [ + AC_MSG_RESULT(no) + AC_MSG_ERROR([** Incomplete or missing ldap libraries **]) + ] + ) + fi + ] +) + dnl Checks for library functions. Please keep in alphabetical order AC_CHECK_FUNCS( \ arc4random \ @@ -4137,6 +4168,7 @@ echo " Smartcard support: $SCARD_MSG" echo " S/KEY support: $SKEY_MSG" echo " TCP Wrappers support: $TCPW_MSG" +echo " LDAP support: $LDAP_MSG" echo " MD5 password support: $MD5_MSG" echo " libedit support: $LIBEDIT_MSG" echo " Solaris process contract support: $SPC_MSG" diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/ldapauth.c openssh-5.1p1+lpk/ldapauth.c --- openssh-5.1p1.orig/ldapauth.c 1969-12-31 16:00:00.000000000 -0800 +++ openssh-5.1p1+lpk/ldapauth.c 2008-08-23 15:02:47.000000000 -0700 @@ -0,0 +1,575 @@ +/* + * $Id$ + */ + +/* + * + * Copyright (c) 2005, Eric AUGE + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the phear.org nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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. + * + * + */ + +#include "includes.h" + +#ifdef WITH_LDAP_PUBKEY + +#include +#include +#include +#include + +#include "ldapauth.h" +#include "log.h" + +static char *attrs[] = { + PUBKEYATTR, + NULL +}; + +/* filter building infos */ +#define FILTER_GROUP_PREFIX "(&(objectclass=posixGroup)" +#define FILTER_OR_PREFIX "(|" +#define FILTER_OR_SUFFIX ")" +#define FILTER_CN_PREFIX "(cn=" +#define FILTER_CN_SUFFIX ")" +#define FILTER_UID_FORMAT "(memberUid=%s)" +#define FILTER_GROUP_SUFFIX ")" +#define FILTER_GROUP_SIZE(group) (size_t) (strlen(group)+(ldap_count_group(group)*5)+52) + +/* just filter building stuff */ +#define REQUEST_GROUP_SIZE(filter, uid) (size_t) (strlen(filter)+strlen(uid)+1) +#define REQUEST_GROUP(buffer, prefilter, pwname) \ + buffer = (char *) calloc(REQUEST_GROUP_SIZE(prefilter, pwname), sizeof(char)); \ + if (!buffer) { \ + perror("calloc()"); \ + return FAILURE; \ + } \ + snprintf(buffer, REQUEST_GROUP_SIZE(prefilter,pwname), prefilter, pwname) +/* +XXX OLD group building macros +#define REQUEST_GROUP_SIZE(grp, uid) (size_t) (strlen(grp)+strlen(uid)+46) +#define REQUEST_GROUP(buffer,pwname,grp) \ + buffer = (char *) calloc(REQUEST_GROUP_SIZE(grp, pwname), sizeof(char)); \ + if (!buffer) { \ + perror("calloc()"); \ + return FAILURE; \ + } \ + snprintf(buffer,REQUEST_GROUP_SIZE(grp,pwname),"(&(objectclass=posixGroup)(cn=%s)(memberUid=%s))",grp,pwname) + */ + +/* +XXX stock upstream version without extra filter support +#define REQUEST_USER_SIZE(uid) (size_t) (strlen(uid)+64) +#define REQUEST_USER(buffer, pwname) \ + buffer = (char *) calloc(REQUEST_USER_SIZE(pwname), sizeof(char)); \ + if (!buffer) { \ + perror("calloc()"); \ + return NULL; \ + } \ + snprintf(buffer,REQUEST_USER_SIZE(pwname),"(&(objectclass=posixAccount)(objectclass=ldapPublicKey)(uid=%s))",pwname) + */ + +#define REQUEST_USER_SIZE(uid, filter) (size_t) (strlen(uid)+64+(filter != NULL ? strlen(filter) : 0)) +#define REQUEST_USER(buffer, pwname, customfilter) \ + buffer = (char *) calloc(REQUEST_USER_SIZE(pwname, customfilter), sizeof(char)); \ + if (!buffer) { \ + perror("calloc()"); \ + return NULL; \ + } \ + snprintf(buffer, REQUEST_USER_SIZE(pwname, customfilter), \ + "(&(objectclass=posixAccount)(objectclass=ldapPublicKey)(uid=%s)%s)", \ + pwname, (customfilter != NULL ? customfilter : "")) + +/* some portable and working tokenizer, lame though */ +static int tokenize(char ** o, size_t size, char * input) { + unsigned int i = 0, num; + const char * charset = " \t"; + char * ptr = input; + + /* leading white spaces are ignored */ + num = strspn(ptr, charset); + ptr += num; + + while ((num = strcspn(ptr, charset))) { + if (i < size-1) { + o[i++] = ptr; + ptr += num; + if (*ptr) + *ptr++ = '\0'; + } + } + o[i] = NULL; + return SUCCESS; +} + +void ldap_close(ldap_opt_t * ldap) { + + if (!ldap) + return; + + if ( ldap_unbind_ext(ldap->ld, NULL, NULL) < 0) + ldap_perror(ldap->ld, "ldap_unbind()"); + + ldap->ld = NULL; + FLAG_SET_DISCONNECTED(ldap->flags); + + return; +} + +/* init && bind */ +int ldap_connect(ldap_opt_t * ldap) { + int version = LDAP_VERSION3; + + if (!ldap->servers) + return FAILURE; + + /* Connection Init and setup */ + ldap->ld = ldap_init(ldap->servers, LDAP_PORT); + if (!ldap->ld) { + ldap_perror(ldap->ld, "ldap_init()"); + return FAILURE; + } + + if ( ldap_set_option(ldap->ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) { + ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_PROTOCOL_VERSION)"); + return FAILURE; + } + + /* Timeouts setup */ + if (ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, &ldap->b_timeout) != LDAP_SUCCESS) { + ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_NETWORK_TIMEOUT)"); + } + if (ldap_set_option(ldap->ld, LDAP_OPT_TIMEOUT, &ldap->s_timeout) != LDAP_SUCCESS) { + ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_TIMEOUT)"); + } + + /* TLS support */ + if ( (ldap->tls == -1) || (ldap->tls == 1) ) { + if (ldap_start_tls_s(ldap->ld, NULL, NULL ) != LDAP_SUCCESS) { + /* failed then reinit the initial connect */ + ldap_perror(ldap->ld, "ldap_connect: (TLS) ldap_start_tls()"); + if (ldap->tls == 1) + return FAILURE; + + ldap->ld = ldap_init(ldap->servers, LDAP_PORT); + if (!ldap->ld) { + ldap_perror(ldap->ld, "ldap_init()"); + return FAILURE; + } + + if ( ldap_set_option(ldap->ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) { + ldap_perror(ldap->ld, "ldap_set_option()"); + return FAILURE; + } + } + } + + + if ( ldap_simple_bind_s(ldap->ld, ldap->binddn, ldap->bindpw) != LDAP_SUCCESS) { + ldap_perror(ldap->ld, "ldap_simple_bind_s()"); + return FAILURE; + } + + /* says it is connected */ + FLAG_SET_CONNECTED(ldap->flags); + + return SUCCESS; +} + +/* must free allocated ressource */ +static char * ldap_build_host(char *host, int port) { + unsigned int size = strlen(host)+11; + char * h = (char *) calloc (size, sizeof(char)); + int rc; + if (!h) + return NULL; + + rc = snprintf(h, size, "%s:%d ", host, port); + if (rc == -1) + return NULL; + return h; +} + +static int ldap_count_group(const char * input) { + const char * charset = " \t"; + const char * ptr = input; + unsigned int count = 0; + unsigned int num; + + num = strspn(ptr, charset); + ptr += num; + + while ((num = strcspn(ptr, charset))) { + count++; + ptr += num; + ptr++; + } + + return count; +} + +/* format filter */ +char * ldap_parse_groups(const char * groups) { + unsigned int buffer_size = FILTER_GROUP_SIZE(groups); + char * buffer = (char *) calloc(buffer_size, sizeof(char)); + char * g = NULL; + char * garray[32]; + unsigned int i = 0; + + if ((!groups)||(!buffer)) + return NULL; + + g = strdup(groups); + if (!g) { + free(buffer); + return NULL; + } + + /* first separate into n tokens */ + if ( tokenize(garray, sizeof(garray)/sizeof(*garray), g) < 0) { + free(g); + free(buffer); + return NULL; + } + + /* build the final filter format */ + strlcat(buffer, FILTER_GROUP_PREFIX, buffer_size); + strlcat(buffer, FILTER_OR_PREFIX, buffer_size); + i = 0; + while (garray[i]) { + strlcat(buffer, FILTER_CN_PREFIX, buffer_size); + strlcat(buffer, garray[i], buffer_size); + strlcat(buffer, FILTER_CN_SUFFIX, buffer_size); + i++; + } + strlcat(buffer, FILTER_OR_SUFFIX, buffer_size); + strlcat(buffer, FILTER_UID_FORMAT, buffer_size); + strlcat(buffer, FILTER_GROUP_SUFFIX, buffer_size); + + free(g); + return buffer; +} + +/* a bit dirty but leak free */ +char * ldap_parse_servers(const char * servers) { + char * s = NULL; + char * tmp = NULL, *urls[32]; + unsigned int num = 0 , i = 0 , asize = 0; + LDAPURLDesc *urld[32]; + + if (!servers) + return NULL; + + /* local copy of the arg */ + s = strdup(servers); + if (!s) + return NULL; + + /* first separate into URL tokens */ + if ( tokenize(urls, sizeof(urls)/sizeof(*urls), s) < 0) + return NULL; + + i = 0; + while (urls[i]) { + if (! ldap_is_ldap_url(urls[i]) || + (ldap_url_parse(urls[i], &urld[i]) != 0)) { + return NULL; + } + i++; + } + + /* now free(s) */ + free (s); + + /* how much memory do we need */ + num = i; + for (i = 0 ; i < num ; i++) + asize += strlen(urld[i]->lud_host)+11; + + /* alloc */ + s = (char *) calloc( asize+1 , sizeof(char)); + if (!s) { + for (i = 0 ; i < num ; i++) + ldap_free_urldesc(urld[i]); + return NULL; + } + + /* then build the final host string */ + for (i = 0 ; i < num ; i++) { + /* built host part */ + tmp = ldap_build_host(urld[i]->lud_host, urld[i]->lud_port); + strncat(s, tmp, strlen(tmp)); + ldap_free_urldesc(urld[i]); + free(tmp); + } + + return s; +} + +void ldap_options_print(ldap_opt_t * ldap) { + debug("ldap options:"); + debug("servers: %s", ldap->servers); + if (ldap->u_basedn) + debug("user basedn: %s", ldap->u_basedn); + if (ldap->g_basedn) + debug("group basedn: %s", ldap->g_basedn); + if (ldap->binddn) + debug("binddn: %s", ldap->binddn); + if (ldap->bindpw) + debug("bindpw: %s", ldap->bindpw); + if (ldap->sgroup) + debug("group: %s", ldap->sgroup); + if (ldap->filter) + debug("filter: %s", ldap->filter); +} + +void ldap_options_free(ldap_opt_t * l) { + if (!l) + return; + if (l->servers) + free(l->servers); + if (l->u_basedn) + free(l->u_basedn); + if (l->g_basedn) + free(l->g_basedn); + if (l->binddn) + free(l->binddn); + if (l->bindpw) + free(l->bindpw); + if (l->sgroup) + free(l->sgroup); + if (l->fgroup) + free(l->fgroup); + if (l->filter) + free(l->filter); + if (l->l_conf) + free(l->l_conf); + free(l); +} + +/* free keys */ +void ldap_keys_free(ldap_key_t * k) { + ldap_value_free_len(k->keys); + free(k); + return; +} + +ldap_key_t * ldap_getuserkey(ldap_opt_t *l, const char * user) { + ldap_key_t * k = (ldap_key_t *) calloc (1, sizeof(ldap_key_t)); + LDAPMessage *res, *e; + char * filter; + int i; + + if ((!k) || (!l)) + return NULL; + + /* Am i still connected ? RETRY n times */ + /* XXX TODO: setup some conf value for retrying */ + if (!(l->flags & FLAG_CONNECTED)) + for (i = 0 ; i < 2 ; i++) + if (ldap_connect(l) == 0) + break; + + /* quick check for attempts to be evil */ + if ((strchr(user, '(') != NULL) || (strchr(user, ')') != NULL) || + (strchr(user, '*') != NULL) || (strchr(user, '\\') != NULL)) + return NULL; + + /* build filter for LDAP request */ + REQUEST_USER(filter, user, l->filter); + + if ( ldap_search_st( l->ld, + l->u_basedn, + LDAP_SCOPE_SUBTREE, + filter, + attrs, 0, &l->s_timeout, &res ) != LDAP_SUCCESS) { + + ldap_perror(l->ld, "ldap_search_st()"); + + free(filter); + free(k); + + /* XXX error on search, timeout etc.. close ask for reconnect */ + ldap_close(l); + + return NULL; + } + + /* free */ + free(filter); + + /* check if any results */ + i = ldap_count_entries(l->ld,res); + if (i <= 0) { + ldap_msgfree(res); + free(k); + return NULL; + } + + if (i > 1) + debug("[LDAP] duplicate entries, using the FIRST entry returned"); + + e = ldap_first_entry(l->ld, res); + k->keys = ldap_get_values_len(l->ld, e, PUBKEYATTR); + k->num = ldap_count_values_len(k->keys); + + ldap_msgfree(res); + return k; +} + + +/* -1 if trouble + 0 if user is NOT member of current server group + 1 if user IS MEMBER of current server group + */ +int ldap_ismember(ldap_opt_t * l, const char * user) { + LDAPMessage *res; + char * filter; + int i; + + if ((!l->sgroup) || !(l->g_basedn)) + return 1; + + /* Am i still connected ? RETRY n times */ + /* XXX TODO: setup some conf value for retrying */ + if (!(l->flags & FLAG_CONNECTED)) + for (i = 0 ; i < 2 ; i++) + if (ldap_connect(l) == 0) + break; + + /* quick check for attempts to be evil */ + if ((strchr(user, '(') != NULL) || (strchr(user, ')') != NULL) || + (strchr(user, '*') != NULL) || (strchr(user, '\\') != NULL)) + return FAILURE; + + /* build filter for LDAP request */ + REQUEST_GROUP(filter, l->fgroup, user); + + if (ldap_search_st( l->ld, + l->g_basedn, + LDAP_SCOPE_SUBTREE, + filter, + NULL, 0, &l->s_timeout, &res) != LDAP_SUCCESS) { + + ldap_perror(l->ld, "ldap_search_st()"); + + free(filter); + + /* XXX error on search, timeout etc.. close ask for reconnect */ + ldap_close(l); + + return FAILURE; + } + + free(filter); + + /* check if any results */ + if (ldap_count_entries(l->ld, res) > 0) { + ldap_msgfree(res); + return 1; + } + + ldap_msgfree(res); + return 0; +} + +/* + * ldap.conf simple parser + * XXX TODO: sanity checks + * must either + * - free the previous ldap_opt_before replacing entries + * - free each necessary previously parsed elements + * ret: + * -1 on FAILURE, 0 on SUCCESS + */ +int ldap_parse_lconf(ldap_opt_t * l) { + FILE * lcd; /* ldap.conf descriptor */ + char buf[BUFSIZ]; + char * s = NULL, * k = NULL, * v = NULL; + int li, len; + + lcd = fopen (l->l_conf, "r"); + if (lcd == NULL) { + /* debug("Cannot open %s", l->l_conf); */ + perror("ldap_parse_lconf()"); + return FAILURE; + } + + while (fgets (buf, sizeof (buf), lcd) != NULL) { + + if (*buf == '\n' || *buf == '#') + continue; + + k = buf; + v = k; + while (*v != '\0' && *v != ' ' && *v != '\t') + v++; + + if (*v == '\0') + continue; + + *(v++) = '\0'; + + while (*v == ' ' || *v == '\t') + v++; + + li = strlen (v) - 1; + while (v[li] == ' ' || v[li] == '\t' || v[li] == '\n') + --li; + v[li + 1] = '\0'; + + if (!strcasecmp (k, "uri")) { + if ((l->servers = ldap_parse_servers(v)) == NULL) { + fatal("error in ldap servers"); + return FAILURE; + } + + } + else if (!strcasecmp (k, "base")) { + s = strchr (v, '?'); + if (s != NULL) { + len = s - v; + l->u_basedn = malloc (len + 1); + strncpy (l->u_basedn, v, len); + l->u_basedn[len] = '\0'; + } else { + l->u_basedn = strdup (v); + } + } + else if (!strcasecmp (k, "binddn")) { + l->binddn = strdup (v); + } + else if (!strcasecmp (k, "bindpw")) { + l->bindpw = strdup (v); + } + else if (!strcasecmp (k, "timelimit")) { + l->s_timeout.tv_sec = atoi (v); + } + else if (!strcasecmp (k, "bind_timelimit")) { + l->b_timeout.tv_sec = atoi (v); + } + else if (!strcasecmp (k, "ssl")) { + if (!strcasecmp (v, "start_tls")) + l->tls = 1; + } + } + + fclose (lcd); + return SUCCESS; +} + +#endif /* WITH_LDAP_PUBKEY */ diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/ldapauth.h openssh-5.1p1+lpk/ldapauth.h --- openssh-5.1p1.orig/ldapauth.h 1969-12-31 16:00:00.000000000 -0800 +++ openssh-5.1p1+lpk/ldapauth.h 2008-08-23 15:02:47.000000000 -0700 @@ -0,0 +1,124 @@ +/* + * $Id$ + */ + +/* + * + * Copyright (c) 2005, Eric AUGE + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of the phear.org nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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. + * + * + */ + +#ifndef LDAPAUTH_H +#define LDAPAUTH_H + +#define LDAP_DEPRECATED 1 + +#include +#include +#include +#include + +/* tokens in use for config */ +#define _DEFAULT_LPK_TOKEN "UseLPK" +#define _DEFAULT_SRV_TOKEN "LpkServers" +#define _DEFAULT_USR_TOKEN "LpkUserDN" +#define _DEFAULT_GRP_TOKEN "LpkGroupDN" +#define _DEFAULT_BDN_TOKEN "LpkBindDN" +#define _DEFAULT_BPW_TOKEN "LpkBindPw" +#define _DEFAULT_MYG_TOKEN "LpkServerGroup" +#define _DEFAULT_FIL_TOKEN "LpkFilter" +#define _DEFAULT_TLS_TOKEN "LpkForceTLS" +#define _DEFAULT_BTI_TOKEN "LpkBindTimelimit" +#define _DEFAULT_STI_TOKEN "LpkSearchTimelimit" +#define _DEFAULT_LDP_TOKEN "LpkLdapConf" + +/* default options */ +#define _DEFAULT_LPK_ON 0 +#define _DEFAULT_LPK_SERVERS NULL +#define _DEFAULT_LPK_UDN NULL +#define _DEFAULT_LPK_GDN NULL +#define _DEFAULT_LPK_BINDDN NULL +#define _DEFAULT_LPK_BINDPW NULL +#define _DEFAULT_LPK_SGROUP NULL +#define _DEFAULT_LPK_FILTER NULL +#define _DEFAULT_LPK_TLS -1 +#define _DEFAULT_LPK_BTIMEOUT 10 +#define _DEFAULT_LPK_STIMEOUT 10 +#define _DEFAULT_LPK_LDP NULL + +/* flags */ +#define FLAG_EMPTY 0x00000000 +#define FLAG_CONNECTED 0x00000001 + +/* flag macros */ +#define FLAG_SET_EMPTY(x) x&=(FLAG_EMPTY) +#define FLAG_SET_CONNECTED(x) x|=(FLAG_CONNECTED) +#define FLAG_SET_DISCONNECTED(x) x&=~(FLAG_CONNECTED) + +/* defines */ +#define FAILURE -1 +#define SUCCESS 0 +#define PUBKEYATTR "sshPublicKey" + +/* + * + * defined files path + * (should be relocated to pathnames.h, + * if one day it's included within the tree) + * + */ +#define _PATH_LDAP_CONFIG_FILE "/etc/ldap.conf" + +/* structures */ +typedef struct ldap_options { + int on; /* Use it or NOT */ + LDAP * ld; /* LDAP file desc */ + char * servers; /* parsed servers for ldaplib failover handling */ + char * u_basedn; /* user basedn */ + char * g_basedn; /* group basedn */ + char * binddn; /* binddn */ + char * bindpw; /* bind password */ + char * sgroup; /* server group */ + char * fgroup; /* group filter */ + char * filter; /* additional filter */ + char * l_conf; /* use ldap.conf */ + int tls; /* TLS only */ + struct timeval b_timeout; /* bind timeout */ + struct timeval s_timeout; /* search timeout */ + unsigned int flags; /* misc flags (reconnection, future use?) */ +} ldap_opt_t; + +typedef struct ldap_keys { + struct berval ** keys; /* the public keys retrieved */ + unsigned int num; /* number of keys */ +} ldap_key_t; + + +/* function headers */ +void ldap_close(ldap_opt_t *); +int ldap_connect(ldap_opt_t *); +char * ldap_parse_groups(const char *); +char * ldap_parse_servers(const char *); +void ldap_options_print(ldap_opt_t *); +void ldap_options_free(ldap_opt_t *); +void ldap_keys_free(ldap_key_t *); +int ldap_parse_lconf(ldap_opt_t *); +ldap_key_t * ldap_getuserkey(ldap_opt_t *, const char *); +int ldap_ismember(ldap_opt_t *, const char *); + +#endif diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/lpk-user-example.txt openssh-5.1p1+lpk/lpk-user-example.txt --- openssh-5.1p1.orig/lpk-user-example.txt 1969-12-31 16:00:00.000000000 -0800 +++ openssh-5.1p1+lpk/lpk-user-example.txt 2008-08-23 15:02:47.000000000 -0700 @@ -0,0 +1,117 @@ + +Post to ML -> User Made Quick Install Doc. +Contribution from John Lane + +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +OpenSSH LDAP keystore Patch +=========================== + +NOTE: these notes are a transcript of a specific installation + they work for me, your specifics may be different! + from John Lane March 17th 2005 john@lane.uk.net + +This is a patch to OpenSSH 4.0p1 to allow it to obtain users' public keys +from their LDAP record as an alternative to ~/.ssh/authorized_keys. + +(Assuming here that necessary build stuff is in $BUILD) + +cd $BUILD/openssh-4.0p1 +patch -Np1 -i $BUILD/openssh-lpk-4.0p1-0.3.patch +mkdir -p /var/empty && +./configure --prefix=/usr --sysconfdir=/etc/ssh \ + --libexecdir=/usr/sbin --with-md5-passwords --with-pam \ + --with-libs="-lldap" --with-cppflags="-DWITH_LDAP_PUBKEY" +Now do. +make && +make install + +Add the following config to /etc/ssh/ssh_config +UseLPK yes +LpkServers ldap://myhost.mydomain.com +LpkUserDN ou=People,dc=mydomain,dc=com + +We need to tell sshd about the SSL keys during boot, as root's +environment does not exist at that time. Edit /etc/rc.d/init.d/sshd. +Change the startup code from this: + echo "Starting SSH Server..." + loadproc /usr/sbin/sshd + ;; +to this: + echo "Starting SSH Server..." + LDAPRC="/root/.ldaprc" loadproc /usr/sbin/sshd + ;; + +Re-start the sshd daemon: +/etc/rc.d/init.d/sshd restart + +Install the additional LDAP schema +cp $BUILD/openssh-lpk-0.2.schema /etc/openldap/schema/openssh.schema + +Now add the openSSH LDAP schema to /etc/openldap/slapd.conf: +Add the following to the end of the existing block of schema includes +include /etc/openldap/schema/openssh.schema + +Re-start the LDAP server: +/etc/rc.d/init.d/slapd restart + +To add one or more public keys to a user, eg "testuser" : +ldapsearch -x -W -Z -LLL -b "uid=testuser,ou=People,dc=mydomain,dc=com" -D +"uid=testuser,ou=People,dc=mydomain,dc=com" > /tmp/testuser + +append the following to this /tmp/testuser file +objectclass: ldapPublicKey +sshPublicKey: ssh-rsa +AAAAB3NzaC1yc2EAAAABJQAAAIB3dsrwqXqD7E4zYYrxwdDKBUQxKMioXy9pxFVai64kAPxjU9KS +qIo7QfkjslfsjflksjfldfkjsldfjLX/5zkzRmT28I5piGzunPv17S89z8XwSsuAoR1t86t+5dlI +7eZE/gVbn2UQkQq7+kdDTS2yXV6VnC52N/kKLG3ciBkBAw== General Purpose RSA Key + +Then do a modify: +ldapmodify -x -D "uid=testuser,ou=People,dc=mydomain,dc=com" -W -f +/tmp/testuser -Z +Enter LDAP Password: +modifying entry "uid=testuser,ou=People,dc=mydomain,dc=com" +And check the modify is ok: +ldapsearch -x -W -Z -b "uid=testuser,ou=People,dc=mydomain,dc=com" -D +"uid=testuser,ou=People,dc=mydomain,dc=com" +Enter LDAP Password: +# extended LDIF +# +# LDAPv3 +# base with scope sub +# filter: (objectclass=*) +# requesting: ALL +# + +# testuser, People, mydomain.com +dn: uid=testuser,ou=People,dc=mydomain,dc=com +uid: testuser +cn: testuser +objectClass: account +objectClass: posixAccount +objectClass: top +objectClass: shadowAccount +objectClass: ldapPublicKey +shadowLastChange: 12757 +shadowMax: 99999 +shadowWarning: 7 +loginShell: /bin/bash +uidNumber: 9999 +gidNumber: 501 +homeDirectory: /home/testuser +userPassword:: e1NTSEF9UDgwV1hnM1VjUDRJK0k1YnFiL1d4ZUJObXlZZ3Z3UTU= +sshPublicKey: ssh-rsa +AAAAB3NzaC1yc2EAAAABJQAAAIB3dsrwqXqD7E4zYYrxwdDKBUQxKMioXy9pxFVai64kAPxjU9KSqIo7QfkjslfsjflksjfldfkjsldfjLX/5zkzRmT28I5piGzunPv17S89z +8XwSsuAoR1t86t+5dlI7eZE/gVbn2UQkQq7+kdDTS2yXV6VnC52N/kKLG3ciBkBAw== General Purpose RSA Key + +# search result +search: 3 +result: 0 Success + +# numResponses: 2 +# numEntries: 1 + +Now start a ssh session to user "testuser" from usual ssh client (e.g. +puTTY). Login should succeed. + +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --- openssh-5.7p1/Makefile.in.orig 2011-01-17 12:15:29.000000000 +0200 +++ openssh-5.7p1/Makefile.in 2011-01-24 16:35:51.174726790 +0200 @@ -93,7 +93,7 @@ auth2-gss.o gss-serv.o gss-serv-krb5.o \ loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ sftp-server.o sftp-common.o \ - roaming_common.o roaming_serv.o + roaming_common.o roaming_serv.o ldapauth.o MANPAGES = moduli.5.out scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-rand-helper.8.out ssh-keysign.8.out ssh-pkcs11-helper.8.out sshd_config.5.out ssh_config.5.out MANPAGES_IN = moduli.5 scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-rand-helper.8 ssh-keysign.8 ssh-pkcs11-helper.8 sshd_config.5 ssh_config.5 diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/openssh-lpk_openldap.schema openssh-5.1p1+lpk/openssh-lpk_openldap.schema --- openssh-5.1p1.orig/openssh-lpk_openldap.schema 1969-12-31 16:00:00.000000000 -0800 +++ openssh-5.1p1+lpk/openssh-lpk_openldap.schema 2008-08-23 15:02:47.000000000 -0700 @@ -0,0 +1,19 @@ +# +# LDAP Public Key Patch schema for use with openssh-ldappubkey +# Author: Eric AUGE +# +# Based on the proposal of : Mark Ruijter +# + + +# octetString SYNTAX +attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' + DESC 'MANDATORY: OpenSSH Public key' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) + +# printableString SYNTAX yes|no +objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY + DESC 'MANDATORY: OpenSSH LPK objectclass' + MUST ( sshPublicKey $ uid ) + ) diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/openssh-lpk_sun.schema openssh-5.1p1+lpk/openssh-lpk_sun.schema --- openssh-5.1p1.orig/openssh-lpk_sun.schema 1969-12-31 16:00:00.000000000 -0800 +++ openssh-5.1p1+lpk/openssh-lpk_sun.schema 2008-08-23 15:02:47.000000000 -0700 @@ -0,0 +1,21 @@ +# +# LDAP Public Key Patch schema for use with openssh-ldappubkey +# Author: Eric AUGE +# +# Schema for Sun Directory Server. +# Based on the original schema, modified by Stefan Fischer. +# + +dn: cn=schema + +# octetString SYNTAX +attributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' + DESC 'MANDATORY: OpenSSH Public key' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) + +# printableString SYNTAX yes|no +objectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY + DESC 'MANDATORY: OpenSSH LPK objectclass' + MUST ( sshPublicKey $ uid ) + ) diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/README.lpk openssh-5.1p1+lpk/README.lpk --- openssh-5.1p1.orig/README.lpk 1969-12-31 16:00:00.000000000 -0800 +++ openssh-5.1p1+lpk/README.lpk 2008-08-23 15:02:47.000000000 -0700 @@ -0,0 +1,267 @@ +OpenSSH LDAP PUBLIC KEY PATCH +Copyright (c) 2003 Eric AUGE (eau@phear.org) +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. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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. + +purposes of this patch: + +This patch would help to have authentication centralization policy +using ssh public key authentication. +This patch could be an alternative to other "secure" authentication system +working in a similar way (Kerberos, SecurID, etc...), except the fact +that it's based on OpenSSH and its public key abilities. + +>> FYI: << +'uid': means unix accounts existing on the current server +'lpkServerGroup:' mean server group configured on the current server ('lpkServerGroup' in sshd_config) + +example schema: + + + server1 (uid: eau,rival,toto) (lpkServerGroup: unix) + ___________ / + / \ --- - server3 (uid: eau, titi) (lpkServerGroup: unix) + | LDAP Server | \ + | eau ,rival | server2 (uid: rival, eau) (lpkServerGroup: unix) + | titi ,toto | + | userx,.... | server5 (uid: eau) (lpkServerGroup: mail) + \___________/ \ / + ----- - server4 (uid: eau, rival) (no group configured) + \ + etc... + +- WHAT WE NEED : + + * configured LDAP server somewhere on the network (i.e. OpenLDAP) + * patched sshd (with this patch ;) + * LDAP user(/group) entry (look at users.ldif (& groups.ldif)): + User entry: + - attached to the 'ldapPublicKey' objectclass + - attached to the 'posixAccount' objectclass + - with a filled 'sshPublicKey' attribute + Example: + dn: uid=eau,ou=users,dc=cuckoos,dc=net + objectclass: top + objectclass: person + objectclass: organizationalPerson + objectclass: posixAccount + objectclass: ldapPublicKey + description: Eric AUGE Account + userPassword: blah + cn: Eric AUGE + sn: Eric AUGE + uid: eau + uidNumber: 1034 + gidNumber: 1 + homeDirectory: /export/home/eau + sshPublicKey: ssh-dss AAAAB3... + sshPublicKey: ssh-dss AAAAM5... + + Group entry: + - attached to the 'posixGroup' objectclass + - with a 'cn' groupname attribute + - with multiple 'memberUid' attributes filled with usernames allowed in this group + Example: + # few members + dn: cn=unix,ou=groups,dc=cuckoos,dc=net + objectclass: top + objectclass: posixGroup + description: Unix based servers group + cn: unix + gidNumber: 1002 + memberUid: eau + memberUid: user1 + memberUid: user2 + + +- HOW IT WORKS : + + * without patch + If a user wants to authenticate to log in a server the sshd, will first look for authentication method allowed (RSAauth,kerberos,etc..) + and if RSAauth and tickets based auth fails, it will fallback to standard password authentication (if enabled). + + * with the patch + If a user want to authenticate to log in a server, the sshd will first look for auth method including LDAP pubkey, if the ldappubkey options is enabled. + It will do an ldapsearch to get the public key directly from the LDAP instead of reading it from the server filesystem. + (usually in $HOME/.ssh/authorized_keys) + + If groups are enabled, it will also check if the user that wants to login is in the group of the server he is trying to log into. + If it fails, it falls back on RSA auth files ($HOME/.ssh/authorized_keys), etc.. and finally to standard password authentication (if enabled). + + 7 tokens are added to sshd_config : + # here is the new patched ldap related tokens + # entries in your LDAP must be posixAccount & strongAuthenticationUser & posixGroup + UseLPK yes # look the pub key into LDAP + LpkServers ldap://10.31.32.5/ ldap://10.31.32.4 ldap://10.31.32.3 # which LDAP server for users ? (URL format) + LpkUserDN ou=users,dc=foobar,dc=net # which base DN for users ? + LpkGroupDN ou=groups,dc=foobar,dc=net # which base DN for groups ? + LpkBindDN cn=manager,dc=foobar,dc=net # which bind DN ? + LpkBindPw asecret # bind DN credidentials + LpkServerGroup agroupname # the group the server is part of + + Right now i'm using anonymous binding to get public keys, because getting public keys of someone doesn't impersonate him¸ but there is some + flaws you have to take care of. + +- HOW TO INSERT A USER/KEY INTO AN LDAP ENTRY + + * my way (there is plenty :) + - create ldif file (i.e. users.ldif) + - cat ~/.ssh/id_dsa.pub OR cat ~/.ssh/id_rsa.pub OR cat ~/.ssh/identity.pub + - my way in 4 steps : + Example: + + # you add this to the user entry in the LDIF file : + [...] + objectclass: posixAccount + objectclass: ldapPublicKey + [...] + sshPubliKey: ssh-dss AAAABDh12DDUR2... + [...] + + # insert your entry and you're done :) + ldapadd -D balblabla -w bleh < file.ldif + + all standard options can be present in the 'sshPublicKey' attribute. + +- WHY : + + Simply because, i was looking for a way to centralize all sysadmins authentication, easily, without completely using LDAP + as authentication method (like pam_ldap etc..). + + After looking into Kerberos, SecurID, and other centralized secure authentications systems, the use of RSA and LDAP to get + public key for authentication allows us to control who has access to which server (the user needs an account and to be in 'strongAuthenticationUser' + objectclass within LDAP and part of the group the SSH server is in). + + Passwords update are no longer a nightmare for a server farm (key pair passphrase is stored on each user's box and private key is locally encrypted using his passphrase + so each user can change it as much as he wants). + + Blocking a user account can be done directly from the LDAP (if sshd is using RSAAuth + ldap only). + +- RULES : + Entry in the LDAP server must respect 'posixAccount' and 'ldapPublicKey' which are defined in core.schema. + and the additionnal lpk.schema. + + This patch could allow a smooth transition between standard auth (/etc/passwd) and complete LDAP based authentication + (pamldap, nss_ldap, etc..). + + This can be an alternative to other (old?/expensive?) authentication methods (Kerberos/SecurID/..). + + Referring to schema at the beginning of this file if user 'eau' is only in group 'unix' + 'eau' would ONLY access 'server1', 'server2', 'server3' AND 'server4' BUT NOT 'server5'. + If you then modify the LDAP 'mail' group entry to add 'memberUid: eau' THEN user 'eau' would be able + to log in 'server5' (i hope you got the idea, my english is bad :). + + Each server's sshd is patched and configured to ask the public key and the group infos in the LDAP + server. + When you want to allow a new user to have access to the server parc, you just add him an account on + your servers, you add his public key into his entry on the LDAP server, it's done. + + Because sshds are looking public keys into the LDAP directly instead of a file ($HOME/.ssh/authorized_keys). + + When the user needs to change his passphrase he can do it directly from his workstation by changing + his own key set lock passphrase, and all servers are automatically aware. + + With a CAREFUL LDAP server configuration you could allow a user to add/delete/modify his own entry himself + so he can add/modify/delete himself his public key when needed. + +­ FLAWS : + LDAP must be well configured, getting the public key of some user is not a problem, but if anonymous LDAP + allow write to users dn, somebody could replace someuser's public key by its own and impersonate some + of your users in all your server farm be VERY CAREFUL. + + MITM attack when sshd is requesting the public key, could lead to a compromise of your servers allowing login + as the impersonnated user. + + If LDAP server is down then, fallback on passwd auth. + + the ldap code part has not been well audited yet. + +- LDAP USER ENTRY EXAMPLES (LDIF Format, look in users.ldif) + --- CUT HERE --- + dn: uid=jdoe,ou=users,dc=foobar,dc=net + objectclass: top + objectclass: person + objectclass: organizationalPerson + objectclass: posixAccount + objectclass: ldapPublicKey + description: My account + cn: John Doe + sn: John Doe + uid: jdoe + uidNumber: 100 + gidNumber: 100 + homeDirectory: /home/jdoe + sshPublicKey: ssh-dss AAAAB3NzaC1kc3MAAAEBAOvL8pREUg9wSy/8+hQJ54YF3AXkB0OZrXB.... + [...] + --- CUT HERE --- + +- LDAP GROUP ENTRY EXAMPLES (LDIF Format, look in groups.ldif) + --- CUT HERE --- + dn: cn=unix,ou=groups,dc=cuckoos,dc=net + objectclass: top + objectclass: posixGroup + description: Unix based servers group + cn: unix + gidNumber: 1002 + memberUid: jdoe + memberUid: user1 + memberUid: user2 + [...] + --- CUT HERE --- + +>> FYI: << +Multiple 'sshPublicKey' in a user entry are allowed, as well as multiple 'memberUid' attributes in a group entry + +- COMPILING: + 1. Apply the patch + 2. ./configure --with-your-options --with-ldap=/prefix/to/ldap_libs_and_includes + 3. make + 4. it's done. + +- BLA : + I hope this could help, and i hope to be clear enough,, or give ideas. questions/comments/improvements are welcome. + +- TODO : + Redesign differently. + +- DOCS/LINK : + http://pacsec.jp/core05/psj05-barisani-en.pdf + http://fritz.potsdam.edu/projects/openssh-lpk/ + http://fritz.potsdam.edu/projects/sshgate/ + http://dev.inversepath.com/trac/openssh-lpk + http://lam.sf.net/ ( http://lam.sourceforge.net/documentation/supportedSchemas.htm ) + +- CONTRIBUTORS/IDEAS/GREETS : + - Falk Siemonsmeier. + - Jacob Rief. + - Michael Durchgraf. + - frederic peters. + - Finlay dobbie. + - Stefan Fisher. + - Robin H. Johnson. + - Adrian Bridgett. + +- CONTACT : + - Eric AUGE + - Andrea Barisani --- openssh-5.7p1/servconf.c.orig 2010-11-20 06:19:38.000000000 +0200 +++ openssh-5.7p1/servconf.c 2011-01-24 16:38:27.381393458 +0200 @@ -46,6 +46,10 @@ #include "channels.h" #include "groupaccess.h" +#ifdef WITH_LDAP_PUBKEY +#include "ldapauth.h" +#endif + static void add_listen_addr(ServerOptions *, char *, int); static void add_one_listen_addr(ServerOptions *, char *, int); @@ -139,6 +143,24 @@ options->authorized_principals_file = NULL; options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; +#ifdef WITH_LDAP_PUBKEY + /* XXX dirty */ + options->lpk.ld = NULL; + options->lpk.on = -1; + options->lpk.servers = NULL; + options->lpk.u_basedn = NULL; + options->lpk.g_basedn = NULL; + options->lpk.binddn = NULL; + options->lpk.bindpw = NULL; + options->lpk.sgroup = NULL; + options->lpk.filter = NULL; + options->lpk.fgroup = NULL; + options->lpk.l_conf = NULL; + options->lpk.tls = -1; + options->lpk.b_timeout.tv_sec = -1; + options->lpk.s_timeout.tv_sec = -1; + options->lpk.flags = FLAG_EMPTY; +#endif } void @@ -281,6 +303,32 @@ options->ip_qos_interactive = IPTOS_LOWDELAY; if (options->ip_qos_bulk == -1) options->ip_qos_bulk = IPTOS_THROUGHPUT; +#ifdef WITH_LDAP_PUBKEY + if (options->lpk.on == -1) + options->lpk.on = _DEFAULT_LPK_ON; + if (options->lpk.servers == NULL) + options->lpk.servers = _DEFAULT_LPK_SERVERS; + if (options->lpk.u_basedn == NULL) + options->lpk.u_basedn = _DEFAULT_LPK_UDN; + if (options->lpk.g_basedn == NULL) + options->lpk.g_basedn = _DEFAULT_LPK_GDN; + if (options->lpk.binddn == NULL) + options->lpk.binddn = _DEFAULT_LPK_BINDDN; + if (options->lpk.bindpw == NULL) + options->lpk.bindpw = _DEFAULT_LPK_BINDPW; + if (options->lpk.sgroup == NULL) + options->lpk.sgroup = _DEFAULT_LPK_SGROUP; + if (options->lpk.filter == NULL) + options->lpk.filter = _DEFAULT_LPK_FILTER; + if (options->lpk.tls == -1) + options->lpk.tls = _DEFAULT_LPK_TLS; + if (options->lpk.b_timeout.tv_sec == -1) + options->lpk.b_timeout.tv_sec = _DEFAULT_LPK_BTIMEOUT; + if (options->lpk.s_timeout.tv_sec == -1) + options->lpk.s_timeout.tv_sec = _DEFAULT_LPK_STIMEOUT; + if (options->lpk.l_conf == NULL) + options->lpk.l_conf = _DEFAULT_LPK_LDP; +#endif /* Turn privilege separation on by default */ if (use_privsep == -1) @@ -329,6 +377,12 @@ sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, sKexAlgorithms, sIPQoS, sDeprecated, sUnsupported +#ifdef WITH_LDAP_PUBKEY + ,sLdapPublickey, sLdapServers, sLdapUserDN + ,sLdapGroupDN, sBindDN, sBindPw, sMyGroup + ,sLdapFilter, sForceTLS, sBindTimeout + ,sSearchTimeout, sLdapConf +#endif } ServerOpCodes; #define SSHCFG_GLOBAL 0x01 /* allowed in main section of sshd_config */ @@ -439,6 +493,20 @@ { "clientalivecountmax", sClientAliveCountMax, SSHCFG_GLOBAL }, { "authorizedkeysfile", sAuthorizedKeysFile, SSHCFG_ALL }, { "authorizedkeysfile2", sAuthorizedKeysFile2, SSHCFG_ALL }, +#ifdef WITH_LDAP_PUBKEY + { _DEFAULT_LPK_TOKEN, sLdapPublickey, SSHCFG_GLOBAL }, + { _DEFAULT_SRV_TOKEN, sLdapServers, SSHCFG_GLOBAL }, + { _DEFAULT_USR_TOKEN, sLdapUserDN, SSHCFG_GLOBAL }, + { _DEFAULT_GRP_TOKEN, sLdapGroupDN, SSHCFG_GLOBAL }, + { _DEFAULT_BDN_TOKEN, sBindDN, SSHCFG_GLOBAL }, + { _DEFAULT_BPW_TOKEN, sBindPw, SSHCFG_GLOBAL }, + { _DEFAULT_MYG_TOKEN, sMyGroup, SSHCFG_GLOBAL }, + { _DEFAULT_FIL_TOKEN, sLdapFilter, SSHCFG_GLOBAL }, + { _DEFAULT_TLS_TOKEN, sForceTLS, SSHCFG_GLOBAL }, + { _DEFAULT_BTI_TOKEN, sBindTimeout, SSHCFG_GLOBAL }, + { _DEFAULT_STI_TOKEN, sSearchTimeout, SSHCFG_GLOBAL }, + { _DEFAULT_LDP_TOKEN, sLdapConf, SSHCFG_GLOBAL }, +#endif { "useprivilegeseparation", sUsePrivilegeSeparation, SSHCFG_GLOBAL}, { "acceptenv", sAcceptEnv, SSHCFG_GLOBAL }, { "permittunnel", sPermitTunnel, SSHCFG_ALL }, @@ -1411,6 +1479,107 @@ while (arg) arg = strdelim(&cp); break; +#ifdef WITH_LDAP_PUBKEY + case sLdapPublickey: + intptr = &options->lpk.on; + goto parse_flag; + case sLdapServers: + /* arg = strdelim(&cp); */ + p = line; + while(*p++); + arg = p; + if (!arg || *arg == '\0') + fatal("%s line %d: missing ldap server",filename,linenum); + arg[strlen(arg)] = '\0'; + if ((options->lpk.servers = ldap_parse_servers(arg)) == NULL) + fatal("%s line %d: error in ldap servers", filename, linenum); + memset(arg,0,strlen(arg)); + break; + case sLdapUserDN: + arg = cp; + if (!arg || *arg == '\0') + fatal("%s line %d: missing ldap server",filename,linenum); + arg[strlen(arg)] = '\0'; + options->lpk.u_basedn = xstrdup(arg); + memset(arg,0,strlen(arg)); + break; + case sLdapGroupDN: + arg = cp; + if (!arg || *arg == '\0') + fatal("%s line %d: missing ldap server",filename,linenum); + arg[strlen(arg)] = '\0'; + options->lpk.g_basedn = xstrdup(arg); + memset(arg,0,strlen(arg)); + break; + case sBindDN: + arg = cp; + if (!arg || *arg == '\0') + fatal("%s line %d: missing binddn",filename,linenum); + arg[strlen(arg)] = '\0'; + options->lpk.binddn = xstrdup(arg); + memset(arg,0,strlen(arg)); + break; + case sBindPw: + arg = cp; + if (!arg || *arg == '\0') + fatal("%s line %d: missing bindpw",filename,linenum); + arg[strlen(arg)] = '\0'; + options->lpk.bindpw = xstrdup(arg); + memset(arg,0,strlen(arg)); + break; + case sMyGroup: + arg = cp; + if (!arg || *arg == '\0') + fatal("%s line %d: missing groupname",filename, linenum); + arg[strlen(arg)] = '\0'; + options->lpk.sgroup = xstrdup(arg); + if (options->lpk.sgroup) + options->lpk.fgroup = ldap_parse_groups(options->lpk.sgroup); + memset(arg,0,strlen(arg)); + break; + case sLdapFilter: + arg = cp; + if (!arg || *arg == '\0') + fatal("%s line %d: missing filter",filename, linenum); + arg[strlen(arg)] = '\0'; + options->lpk.filter = xstrdup(arg); + memset(arg,0,strlen(arg)); + break; + case sForceTLS: + intptr = &options->lpk.tls; + arg = strdelim(&cp); + if (!arg || *arg == '\0') + fatal("%s line %d: missing yes/no argument.", + filename, linenum); + value = 0; /* silence compiler */ + if (strcmp(arg, "yes") == 0) + value = 1; + else if (strcmp(arg, "no") == 0) + value = 0; + else if (strcmp(arg, "try") == 0) + value = -1; + else + fatal("%s line %d: Bad yes/no argument: %s", + filename, linenum, arg); + if (*intptr == -1) + *intptr = value; + break; + case sBindTimeout: + intptr = (int *) &options->lpk.b_timeout.tv_sec; + goto parse_int; + case sSearchTimeout: + intptr = (int *) &options->lpk.s_timeout.tv_sec; + goto parse_int; + break; + case sLdapConf: + arg = cp; + if (!arg || *arg == '\0') + fatal("%s line %d: missing LpkLdapConf", filename, linenum); + arg[strlen(arg)] = '\0'; + options->lpk.l_conf = xstrdup(arg); + memset(arg, 0, strlen(arg)); + break; +#endif default: fatal("%s line %d: Missing handler for opcode %s (%d)", diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/servconf.h openssh-5.1p1+lpk/servconf.h --- openssh-5.1p1.orig/servconf.h 2008-06-10 06:01:51.000000000 -0700 +++ openssh-5.1p1+lpk/servconf.h 2008-08-23 15:02:47.000000000 -0700 @@ -16,6 +16,10 @@ #ifndef SERVCONF_H #define SERVCONF_H +#ifdef WITH_LDAP_PUBKEY +#include "ldapauth.h" +#endif + #define MAX_PORTS 256 /* Max # ports. */ #define MAX_ALLOW_USERS 256 /* Max # users on allow list. */ @@ -145,6 +149,9 @@ int use_pam; /* Enable auth via PAM */ int permit_tun; +#ifdef WITH_LDAP_PUBKEY + ldap_opt_t lpk; +#endif int num_permitted_opens; diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/sshd.c openssh-5.1p1+lpk/sshd.c --- openssh-5.1p1.orig/sshd.c 2008-07-11 00:36:49.000000000 -0700 +++ openssh-5.1p1+lpk/sshd.c 2008-08-23 15:02:47.000000000 -0700 @@ -127,6 +127,10 @@ int deny_severity; #endif /* LIBWRAP */ +#ifdef WITH_LDAP_PUBKEY +#include "ldapauth.h" +#endif + #ifndef O_NOCTTY #define O_NOCTTY 0 #endif @@ -1484,6 +1488,16 @@ exit(1); } +#ifdef WITH_LDAP_PUBKEY + /* ldap_options_print(&options.lpk); */ + /* XXX initialize/check ldap connection and set *LD */ + if (options.lpk.on) { + if (options.lpk.l_conf && (ldap_parse_lconf(&options.lpk) < 0) ) + error("[LDAP] could not parse %s", options.lpk.l_conf); + if (ldap_connect(&options.lpk) < 0) + error("[LDAP] could not initialize ldap connection"); + } +#endif debug("sshd version %.100s", SSH_RELEASE); /* Store privilege separation user for later use if required. */ diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/sshd_config openssh-5.1p1+lpk/sshd_config --- openssh-5.1p1.orig/sshd_config 2008-07-02 05:35:43.000000000 -0700 +++ openssh-5.1p1+lpk/sshd_config 2008-08-23 15:02:47.000000000 -0700 @@ -109,6 +109,21 @@ # no default banner path #Banner none +# here are the new patched ldap related tokens +# entries in your LDAP must have posixAccount & ldapPublicKey objectclass +#UseLPK yes +#LpkLdapConf /etc/ldap.conf +#LpkServers ldap://10.1.7.1/ ldap://10.1.7.2/ +#LpkUserDN ou=users,dc=phear,dc=org +#LpkGroupDN ou=groups,dc=phear,dc=org +#LpkBindDN cn=Manager,dc=phear,dc=org +#LpkBindPw secret +#LpkServerGroup mail +#LpkFilter (hostAccess=master.phear.org) +#LpkForceTLS no +#LpkSearchTimelimit 3 +#LpkBindTimelimit 3 + # override default of no subsystems Subsystem sftp /usr/libexec/sftp-server diff -Nuar --exclude '*.orig' --exclude '*.rej' openssh-5.1p1.orig/sshd_config.5 openssh-5.1p1+lpk/sshd_config.5 --- openssh-5.1p1.orig/sshd_config.5 2008-07-02 05:35:43.000000000 -0700 +++ openssh-5.1p1+lpk/sshd_config.5 2008-08-23 15:02:47.000000000 -0700 @@ -1003,6 +1003,62 @@ program. The default is .Pa /usr/X11R6/bin/xauth . +.It Cm UseLPK +Specifies whether LDAP public key retrieval must be used or not. It allow +an easy centralisation of public keys within an LDAP directory. The argument must be +.Dq yes +or +.Dq no . +.It Cm LpkLdapConf +Specifies whether LDAP Public keys should parse the specified ldap.conf file +instead of sshd_config Tokens. The argument must be a valid path to an ldap.conf +file like +.Pa /etc/ldap.conf +.It Cm LpkServers +Specifies LDAP one or more [:space:] separated server's url the following form may be used: +.Pp +LpkServers ldaps://127.0.0.1 ldap://127.0.0.2 ldap://127.0.0.3 +.It Cm LpkUserDN +Specifies the LDAP user DN. +.Pp +LpkUserDN ou=users,dc=phear,dc=org +.It Cm LpkGroupDN +Specifies the LDAP groups DN. +.Pp +LpkGroupDN ou=groups,dc=phear,dc=org +.It Cm LpkBindDN +Specifies the LDAP bind DN to use if necessary. +.Pp +LpkBindDN cn=Manager,dc=phear,dc=org +.It Cm LpkBindPw +Specifies the LDAP bind credential. +.Pp +LpkBindPw secret +.It Cm LpkServerGroup +Specifies one or more [:space:] separated group the server is part of. +.Pp +LpkServerGroup unix mail prod +.It Cm LpkFilter +Specifies an additional LDAP filter to use for finding SSH keys +.Pp +LpkFilter (hostAccess=master.phear.org) +.It Cm LpkForceTLS +Specifies if the LDAP server connection must be tried, forced or not used. The argument must be +.Dq yes +or +.Dq no +or +.Dq try . +.It Cm LpkSearchTimelimit +Sepcifies the search time limit before the search is considered over. value is +in seconds. +.Pp +LpkSearchTimelimit 3 +.It Cm LpkBindTimelimit +Sepcifies the bind time limit before the connection is considered dead. value is +in seconds. +.Pp +LpkBindTimelimit 3 .El .Sh TIME FORMATS .Xr sshd 8