Re: [PATCH] nfsidmap:umich_ldap: Add support for SASL binds.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




On 4/4/20 5:02 AM, Srikrishan Malik wrote:
> umich_ldap can now do a sasl interactive bind to LDAP server based
> on the values of new tunables added.
> The tunables are similar to the ones in nslcd for SASL binds.
Committed... (tag: nfs-utils-2-4-4-rc3)

steved.
> 
> Signed-off-by: Srikrishan Malik <srikrishanmalik@xxxxxxxxx>
> ---
>  configure.ac                   |  23 ++-
>  support/nfsidmap/Makefile.am   |   5 +-
>  support/nfsidmap/idmapd.conf   |  23 +++
>  support/nfsidmap/idmapd.conf.5 |  37 ++++-
>  support/nfsidmap/umich_ldap.c  | 250 ++++++++++++++++++++++++++++++++-
>  5 files changed, 330 insertions(+), 8 deletions(-)
> 
> diff --git a/configure.ac b/configure.ac
> index bb8b000e..00b32800 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -453,12 +453,33 @@ if test "x$enable_ldap" != "xno" ; then
>                                [have_ldap="yes"],[have_ldap="no"])],
>                  [have_ldap="no"])
>          if test "x$have_ldap" = "xyes" ; then
> -                AC_DEFINE([ENABLE_LDAP], 1, [Enable LDAP Support])
> +                dnl check for sasl funcs
> +                AC_CHECK_HEADERS(sasl.h sasl/sasl.h)
> +                AC_CHECK_HEADERS(gsssasl.h)
> +                AC_CHECK_TYPE(sasl_interact_t,[have_sasl_interact_t="yes"],,[
> +                        #ifdef HAVE_SASL_SASL_H
> +                        #include <sasl/sasl.h>
> +                        #elif defined(HAVE_SASL_H)
> +                        #include <sasl.h>
> +                        #endif])
> +		AC_CHECK_LIB([ldap],[ldap_sasl_interactive_bind_s],[have_ldap_sasl_interactive_bind_s="yes"])
> +		AC_CHECK_LIB([gssapi_krb5],[gss_krb5_ccache_name],[have_gss_krb5_ccache_name="yes"])
> +		if test "x$have_sasl_interact_t" = "xyes" -a \
> +			"x$have_ldap_sasl_interactive_bind_s" = "xyes" -a \
> +			"x$have_gss_krb5_ccache_name" = "xyes"; then
> +			AC_DEFINE([HAVE_LDAP_SASL_INTERACTIVE_BIND_S],[1],[Has ldap_sasl_interactive_bind_s function])
> +			AC_DEFINE([HAVE_GSS_KRB5_CCACHE_NAME],[1],[Has gss_krb5_ccache_name function])
> +			AC_CHECK_HEADERS(gssapi/gssapi.h gssapi/gssapi_generic.h gssapi/gssapi_krb5.h gssapi.h krb5.h)
> +			AC_DEFINE([ENABLE_LDAP_SASL],1,[Enable LDAP SASL support])
> +			have_ldap_sasl="yes"
> +		fi
> +		AC_DEFINE([ENABLE_LDAP], 1, [Enable LDAP Support])
>          elif test "x$enable_ldap$have_ldap" = "xyesno" ; then
>                  AC_MSG_ERROR(LDAP support not found!)
>          fi
>  fi
>  AM_CONDITIONAL(ENABLE_LDAP, test "x$have_ldap" = "xyes")
> +AM_CONDITIONAL(ENABLE_LDAP_SASL, test "x$have_ldap_sasl" = "xyes")
>  
>  dnl Should we build gums mapping library?
>  AC_ARG_ENABLE([gums],
> diff --git a/support/nfsidmap/Makefile.am b/support/nfsidmap/Makefile.am
> index 9c21fa34..cb2c0ced 100644
> --- a/support/nfsidmap/Makefile.am
> +++ b/support/nfsidmap/Makefile.am
> @@ -14,6 +14,9 @@ GUMS_MAPPING_LIB = gums.la
>  else
>  GUMS_MAPPING_LIB =
>  endif
> +if ENABLE_LDAP_SASL
> +KRB5_GSS_LIB=-lgssapi_krb5
> +endif
>  lib_LTLIBRARIES = libnfsidmap.la
>  pkgplugin_LTLIBRARIES = nsswitch.la static.la $(UMICH_LDAP_LIB) $(GUMS_MAPPING_LIB)
>  
> @@ -43,7 +46,7 @@ static_la_LIBADD = ../../support/nfs/libnfsconf.la
>  
>  umich_ldap_la_SOURCES = umich_ldap.c
>  umich_ldap_la_LDFLAGS = -module -avoid-version
> -umich_ldap_la_LIBADD = -lldap ../../support/nfs/libnfsconf.la
> +umich_ldap_la_LIBADD = -lldap $(KRB5_GSS_LIB) ../../support/nfs/libnfsconf.la
>  
>  gums_la_SOURCES = gums.c
>  gums_la_LDFLAGS = -module -avoid-version
> diff --git a/support/nfsidmap/idmapd.conf b/support/nfsidmap/idmapd.conf
> index b673c7d7..aeeca1bf 100644
> --- a/support/nfsidmap/idmapd.conf
> +++ b/support/nfsidmap/idmapd.conf
> @@ -110,6 +110,29 @@ LDAP_base = dc=local,dc=domain,dc=edu
>  # is not set to "never"
>  #LDAP_ca_cert = /etc/ldapca.cert
>  
> +# SASL mechanism to use while binding to LDAP
> +#LDAP_sasl_mech = <SASL mech>
> +
> +# SASL realm to be used for SASL auth
> +#LDAP_sasl_realm = <SASL realm>
> +
> +# Authentication identity to be used for SASL auth
> +#LDAP_sasl_authcid = <SASL authcid>
> +
> +# Authorization identity for SASL auth
> +#LDAP_sasl_authzid = <SASL authzid>
> +
> +# Cyrus SASL security properties
> +#LDAP_sasl_secprops = <secprops>
> +
> +# Specifies whether the LDAP server hostname should be canonicalised.
> +# If set to yes LDAP lib with do a reverse hostname lookup.
> +# If this is not set the LDAP library's default will be used.
> +#LDAP_sasl_canonicalize <yes | no>
> +
> +# Specifies the kerberos ticket cache to be used
> +#LDAP_sasl_krb5_ccname = <kerberos ticket cache>
> +
>  # Objectclass mapping information
>  
>  # Mapping for the person (account) object class
> diff --git a/support/nfsidmap/idmapd.conf.5 b/support/nfsidmap/idmapd.conf.5
> index 61fbb613..fdd6d589 100644
> --- a/support/nfsidmap/idmapd.conf.5
> +++ b/support/nfsidmap/idmapd.conf.5
> @@ -206,6 +206,39 @@ It can take the same values as ldap.conf(5)'s
>  tunable.
>  (Default: "hard")
>  .TP
> +.B LDAP_timeout_seconds
> +Number of seconds before timing out an LDAP request
> +(Default: 4)
> +.TP
> +.B LDAP_sasl_mech
> +SASL mechanism to be used for sasl authentication.  Required
> +if SASL auth is to be used (Default: None)
> +.TP
> +.B LDAP_realm
> +SASL realm to be used for sasl authentication. (Default: None)
> +.TP
> +.B LDAP_sasl_authcid
> +Authentication identity to be used for sasl authentication. (Default: None)
> +.TP
> +.B LDAP_sasl_authzid
> +Authorization identity to be used for sasl authentication. (Default: None)
> +.TP
> +.B LDAP_sasl_secprops
> +Cyrus SASL security properties. It can  the same values as ldap.conf(5)'s
> +sasl_secprops.
> +.TP
> +.B LDAP_sasl_canonicalize
> +Specifies whether the LDAP server hostname should be canonicalised.
> +If set to yes LDAP lib with do a reverse hostname lookup.
> +If this is not set the LDAP library's default will be used. (Default:
> +None)
> +.TP
> +.B LDAP_sasl_krb5_ccname
> +Path to kerberos credential cache. If it is not set then the value
> +of environment variable KRB5CCNAME will be used. If the environment
> +variable is not set then the default mechanism of kerberos library
> +will be used.
> +.TP
>  .B NFSv4_person_objectclass
>  The object class name for people accounts in your local LDAP schema
>  (Default: NFSv4RemotePerson)
> @@ -257,10 +290,6 @@ is true, this is the attribute to be searched for.
>  .B NFSv4_grouplist_filter
>  An optional search filter for determining group membership.
>  (No Default)
> -.TP
> -.B LDAP_timeout_seconds
> -Number of seconds before timing out an LDAP request
> -(Default: 4)
>  .\"
>  .\" -------------------------------------------------------------------
>  .\" An Example
> diff --git a/support/nfsidmap/umich_ldap.c b/support/nfsidmap/umich_ldap.c
> index b7445c37..d5a7731a 100644
> --- a/support/nfsidmap/umich_ldap.c
> +++ b/support/nfsidmap/umich_ldap.c
> @@ -31,6 +31,7 @@
>   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
>   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
>   */
> +#include "config.h"
>  
>  #include <sys/types.h>
>  #include <sys/socket.h>
> @@ -43,6 +44,15 @@
>  #include <limits.h>
>  #include <pwd.h>
>  #include <err.h>
> +#ifdef HAVE_GSSAPI_GSSAPI_KRB5_H
> +#include <gssapi/gssapi_krb5.h>
> +#endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */
> +#ifdef HAVE_SASL_H
> +#include <sasl.h>
> +#endif /* HAVE_SASL_H */
> +#ifdef HAVE_SASL_SASL_H
> +#include <sasl/sasl.h>
> +#endif /* HAVE_SASL_SASL_H */
>  /* We are using deprecated functions, get the prototypes... */
>  #define LDAP_DEPRECATED 1
>  #include <ldap.h>
> @@ -105,6 +115,13 @@ struct umich_ldap_info {
>  				   looking up user groups */
>  	int ldap_timeout;	/* Timeout in seconds for searches
>  				   by ldap_search_st */
> +	char *sasl_mech;	/* sasl mech to be used */
> +	char *sasl_realm;	/* SASL realm for SASL authentication */
> +	char *sasl_authcid;	/* authentication identity to be used  */
> +	char *sasl_authzid;	/* authorization identity to be used */
> +	char *sasl_secprops;	/* Cyrus SASL security properties. */
> +	int sasl_canonicalize;	/* canonicalize LDAP server host name */
> +	char *sasl_krb5_ccname;	/* krb5 ticket cache */
>  };
>  
>  /* GLOBAL data */
> @@ -122,6 +139,13 @@ static struct umich_ldap_info ldap_info = {
>  	.tls_reqcert = LDAP_OPT_X_TLS_HARD,
>  	.memberof_for_groups = 0,
>  	.ldap_timeout = DEFAULT_UMICH_SEARCH_TIMEOUT,
> +	.sasl_mech = NULL,
> +	.sasl_realm = NULL,
> +	.sasl_authcid = NULL,
> +	.sasl_authzid = NULL,
> +	.sasl_secprops = NULL,
> +	.sasl_canonicalize = -1, /* leave to the LDAP lib */
> +	.sasl_krb5_ccname = NULL,
>  };
>  
>  static struct ldap_map_names ldap_map = {
> @@ -138,6 +162,119 @@ static struct ldap_map_names ldap_map = {
>  	.NFSv4_grouplist_filter = NULL,
>  };
>  
> +#ifdef ENABLE_LDAP_SASL
> +
> +/**
> + * Set the path of the krb5 ticket cache
> + * use gss_krb5_ccache_name if available else set the env var
> + */
> +static int set_krb5_ccname(const char *krb5_ccache_name)
> +{
> +	int retval = 0;
> +#ifdef HAVE_GSS_KRB5_CCACHE_NAME
> +	OM_uint32 status;
> +
> +	if (gss_krb5_ccache_name(&status, krb5_ccache_name, NULL) !=
> +		GSS_S_COMPLETE) {
> +		IDMAP_LOG(5,
> +		  ("Failed to set creds cache for kerberos, minor_status(%d)",
> +		   status));
> +		retval = status;
> +		goto out;
> +	}
> +#else /* HAVE_GSS_KRB5_CCACHE_NAME */
> +	char *env;
> +	int buflen = 0;
> +
> +	buflen = strlen("KRB5CCNAME=") + strlen(krb5_ccache_name) + 1;
> +	env = malloc(buflen);
> +	if (env == NULL) {
> +		retval = ENOMEM;
> +		goto out;
> +	}
> +	snprintf(env, buflen, "KRB5CCNAME=%s", krb5_ccache_name);
> +	if (putenv(env) != 0) {
> +		retval = errno;
> +		IDMAP_LOG(5, ("Failed to set creds cache for kerberos, err(%d)",
> +			      retval));
> +	}
> +#endif /* else HAVE_GSS_KRB5_CCACHE_NAME */
> +out:
> +	return retval;
> +}
> +
> +/**
> + * SASL interact callback
> + */
> +static int sasl_interact_cb(__attribute__((unused)) LDAP * ld,
> +		__attribute__((unused)) unsigned int flags, void *defaults,
> +		void *ctx)
> +{
> +	struct umich_ldap_info *linfo = defaults;
> +	sasl_interact_t *interact = ctx;
> +
> +	while (interact->id != SASL_CB_LIST_END) {
> +		switch (interact->id) {
> +		case SASL_CB_AUTHNAME:
> +			if (linfo->sasl_authcid == NULL ||
> +			    linfo->sasl_authcid[0] == '\0') {
> +				IDMAP_LOG(2, ("SASL_CB_AUTHNAME asked in "
> +					    "callback but not found in conf"));
> +			} else {
> +				IDMAP_LOG(5,
> +					  ("Setting SASL_CB_AUTHNAME to %s",
> +					   linfo->sasl_authcid));
> +				interact->result = linfo->sasl_authcid;
> +				interact->len = strlen(linfo->sasl_authcid);
> +			}
> +			break;
> +		case SASL_CB_PASS:
> +			if (linfo->passwd == NULL || linfo->passwd[0] == '\0') {
> +				IDMAP_LOG(2, ("SASL_CB_PASS asked in callback "
> +					      "but not found in conf"));
> +			} else {
> +				IDMAP_LOG(5,
> +					  ("Setting SASL_CB_PASS to ***"));
> +				interact->result = linfo->passwd;
> +				interact->len = strlen(linfo->passwd);
> +			}
> +			break;
> +		case SASL_CB_GETREALM:
> +			if (linfo->sasl_realm == NULL ||
> +			    linfo->sasl_realm[0] == '\0') {
> +				IDMAP_LOG(2, ("SASL_CB_GETREALM asked in "
> +					    "callback but not found in conf"));
> +			} else {
> +				IDMAP_LOG(5,
> +					  ("Setting SASL_CB_GETREALM to %s",
> +					   linfo->sasl_realm));
> +				interact->result = linfo->sasl_realm;
> +				interact->len = strlen(linfo->sasl_realm);
> +			}
> +			break;
> +		case SASL_CB_USER:
> +			if (linfo->sasl_authzid == NULL ||
> +			    linfo->sasl_authzid[0] == '\0') {
> +				IDMAP_LOG(2, ("SASL_CB_USER asked in callback "
> +					      "but not found in conf"));
> +			} else {
> +				IDMAP_LOG(5, ("Setting SASL_CB_USER to %s",
> +					      linfo->sasl_authzid));
> +				interact->result = linfo->sasl_authzid;
> +				interact->len = strlen(linfo->sasl_authzid);
> +			}
> +			break;
> +		default:
> +			IDMAP_LOG(2, ("Undefined value requested %d",
> +				      interact->id));
> +			break;
> +		}
> +		interact++;
> +	}
> +	return LDAP_SUCCESS;
> +}
> +#endif /* ENABLE_LDAP_SASL */
> +
>  /* Local routines */
>  
>  static int
> @@ -244,7 +381,57 @@ ldap_init_and_bind(LDAP **pld,
>  	/* If we have a DN (and password) attempt an authenticated bind */
>  	if (linfo->user_dn) {
>  retry_bind:
> +#ifdef ENABLE_LDAP_SASL
> +		if (linfo->sasl_mech != NULL && linfo->sasl_mech[0] != '\0') {
> +		/* use sasl bind */
> +			if (linfo->sasl_canonicalize != -1) {
> +				lerr = ldap_set_option(ld,
> +						LDAP_OPT_X_SASL_NOCANON,
> +						linfo->sasl_canonicalize ?
> +						  LDAP_OPT_OFF : LDAP_OPT_ON);
> +				if (lerr != LDAP_SUCCESS) {
> +					IDMAP_LOG(2, ("ldap_init_and_bind: "
> +						    "setting sasl_canonicalize"
> +						    " failed: %s (%d)",
> +						    ldap_err2string(lerr),
> +						    lerr));
> +					goto out;
> +				}
> +			}
> +			if (linfo->sasl_secprops != NULL &&
> +			    linfo->sasl_secprops[0] != '\0') {
> +				lerr = ldap_set_option(ld,
> +						LDAP_OPT_X_SASL_SECPROPS,
> +						(void *) linfo->sasl_secprops);
> +				if (lerr != LDAP_SUCCESS) {
> +					IDMAP_LOG(2, ("ldap_init_and_bind: "
> +						      "setting sasl_secprops"
> +						      " failed: %s (%d)",
> +						      ldap_err2string(lerr),
> +						      lerr));
> +					goto out;
> +				}
> +			}
> +			if (linfo->sasl_krb5_ccname != NULL &&
> +			    linfo->sasl_krb5_ccname[0] != '\0') {
> +				lerr = set_krb5_ccname(linfo->sasl_krb5_ccname);
> +				if (lerr != 0) {
> +					IDMAP_LOG(2,
> +						("ldap_init_and_bind: Failed "
> +						 "to set krb5 ticket cache, "
> +						 "err=%d", lerr));
> +				}
> +			}
> +			lerr = ldap_sasl_interactive_bind_s(ld, linfo->user_dn,
> +				linfo->sasl_mech, NULL, NULL, LDAP_SASL_QUIET,
> +				sasl_interact_cb, linfo);
> +		} else {
> +			lerr = ldap_simple_bind_s(ld, linfo->user_dn,
> +						  linfo->passwd);
> +		}
> +#else /* ENABLE_LDAP_SASL */
>  		lerr = ldap_simple_bind_s(ld, linfo->user_dn, linfo->passwd);
> +#endif /* else ENABLE_LDAP_SASL */
>  		if (lerr) {
>  			char *errmsg;
>  			if (lerr == LDAP_PROTOCOL_ERROR) {
> @@ -267,10 +454,22 @@ retry_bind:
>  				}
>  				goto retry_bind;
>  			}
> -			IDMAP_LOG(2, ("ldap_init_and_bind: ldap_simple_bind_s "
> +#ifdef ENABLE_LDAP_SASL
> +			IDMAP_LOG(2, ("ldap_init_and_bind: %s "
> +				  "to [%s] as user '%s': %s (%d)",
> +				  (linfo->sasl_mech != NULL &&
> +				   linfo->sasl_mech[0] != '\0') ?
> +				   "ldap_sasl_interactive_bind_s" :
> +				   "ldap_simple_bind_s",
> +				  server_url, linfo->user_dn,
> +				  ldap_err2string(lerr), lerr));
> +#else /* ENABLE_LDAP_SASL */
> +			IDMAP_LOG(2, ("ldap_init_and_bind: ldap_simple_bind_s"
>  				  "to [%s] as user '%s': %s (%d)",
>  				  server_url, linfo->user_dn,
>  				  ldap_err2string(lerr), lerr));
> +
> +#endif /* else ENABLE_LDAP_SASL */
>  			if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg) == LDAP_SUCCESS)
>  					&& (errmsg != NULL)&& (*errmsg != '\0')) {
>  				IDMAP_LOG(2, ("ldap_init_and_bind: "
> @@ -1155,6 +1354,30 @@ umichldap_init(void)
>  				      (ldap_info.use_ssl) ?
>  				      LDAPS_PORT : LDAP_PORT);
>  
> +	ldap_info.sasl_mech = conf_get_str(LDAP_SECTION, "LDAP_sasl_mech");
> +	ldap_info.sasl_realm = conf_get_str(LDAP_SECTION, "LDAP_sasl_realm");
> +	ldap_info.sasl_authcid = conf_get_str(LDAP_SECTION,
> +					      "LDAP_sasl_authcid");
> +	ldap_info.sasl_authzid = conf_get_str(LDAP_SECTION,
> +					      "LDAP_sasl_authzid");
> +	ldap_info.sasl_secprops = conf_get_str(LDAP_SECTION,
> +					       "LDAP_sasl_secprops");
> +
> +	/* If it is not set let the ldap lib work with the lib default */
> +	canonicalize = conf_get_str_with_def(LDAP_SECTION,
> +					     "LDAP_sasl_canonicalize", "undef");
> +	if ((strcasecmp(canonicalize, "true") == 0) ||
> +	    (strcasecmp(canonicalize, "on") == 0) ||
> +	    (strcasecmp(canonicalize, "yes") == 0)) {
> +		ldap_info.sasl_canonicalize = 1;
> +	} else if ((strcasecmp(canonicalize, "false") == 0) ||
> +	    (strcasecmp(canonicalize, "off") == 0) ||
> +	    (strcasecmp(canonicalize, "no") == 0)) {
> +		ldap_info.sasl_canonicalize = 0;
> +	}
> +	ldap_info.sasl_krb5_ccname = conf_get_str(LDAP_SECTION,
> +						  "LDAP_sasl_krb5_ccname");
> +
>  	/* Verify required information is supplied */
>  	if (server_in == NULL || strlen(server_in) == 0)
>  		strncat(missing_msg, "LDAP_server ", sizeof(missing_msg)-1);
> @@ -1167,7 +1390,8 @@ umichldap_init(void)
>  	}
>  
>  	ldap_info.server = server_in;
> -	canonicalize = conf_get_str_with_def(LDAP_SECTION, "LDAP_canonicalize_name", "yes");
> +	canonicalize = conf_get_str_with_def(LDAP_SECTION,
> +					     "LDAP_canonicalize_name", "yes");
>  	if ((strcasecmp(canonicalize, "true") == 0) ||
>  	    (strcasecmp(canonicalize, "on") == 0) ||
>  	    (strcasecmp(canonicalize, "yes") == 0)) {
> @@ -1296,6 +1520,28 @@ umichldap_init(void)
>  		  ldap_info.tls_reqcert));
>  	IDMAP_LOG(1, ("umichldap_init: use_memberof_for_groups : %s",
>  		  ldap_info.memberof_for_groups ? "yes" : "no"));
> +	IDMAP_LOG(1, ("umichldap_init: sasl_mech: %s",
> +		  (ldap_info.sasl_mech && strlen(ldap_info.sasl_mech) != 0) ?
> +		  ldap_info.sasl_mech : "<not-supplied>"));
> +	IDMAP_LOG(1, ("umichldap_init: sasl_realm: %s",
> +		  (ldap_info.sasl_realm && strlen(ldap_info.sasl_realm) != 0) ?
> +		  ldap_info.sasl_realm : "<not-supplied>"));
> +	IDMAP_LOG(1, ("umichldap_init: sasl_authcid: %s",
> +		  (ldap_info.sasl_authcid &&
> +		   strlen(ldap_info.sasl_authcid) != 0) ?
> +		  ldap_info.sasl_authcid : "<not-supplied>"));
> +	IDMAP_LOG(1, ("umichldap_init: sasl_authzid: %s",
> +		  (ldap_info.sasl_authzid &&
> +		   strlen(ldap_info.sasl_authzid) != 0) ?
> +		  ldap_info.sasl_authzid : "<not-supplied>"));
> +	IDMAP_LOG(1, ("umichldap_init: sasl_secprops: %s",
> +		  (ldap_info.sasl_secprops &&
> +		   strlen(ldap_info.sasl_secprops) != 0) ?
> +		  ldap_info.sasl_secprops : "<not-supplied>"));
> +	IDMAP_LOG(1, ("umichldap_init: sasl_canonicalize: %d",
> +		      ldap_info.sasl_canonicalize));
> +	IDMAP_LOG(1, ("umichldap_init: sasl_krb5_ccname: %s",
> +		      ldap_info.sasl_krb5_ccname));
>  
>  	IDMAP_LOG(1, ("umichldap_init: NFSv4_person_objectclass : %s",
>  		  ldap_map.NFSv4_person_objcls));
> 




[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux