How to authenticate ProFTPd against a LDAP server with client certificates
March 2012.
Problem
If you are the paranoid type, you might have restricted connections to your LDAP server to clients with SSL client certificates.
Unfortunately, the support of client certificate is missing in a lot of implementation of LDAP clients.
Solution
Go to mod_ldap and download the sources.
Apply the following patch:
--- mod_ldap.c.orig 2012-02-01 12:34:22.587267553 +0100
+++ mod_ldap.c 2012-02-01 12:34:04.636276785 +0100
@@ -161,7 +161,14 @@
*ldap_attr_memberuid = "memberUid",
*ldap_attr_ftpquota = "ftpQuota",
*ldap_attr_ftpquota_profiledn = "ftpQuotaProfileDN",
- *ldap_attr_ssh_pubkey = "sshPublicKey";
+ *ldap_attr_ssh_pubkey = "sshPublicKey",
+ *ldap_tls_ca_cert_dir,
+ *ldap_tls_ca_cert_file,
+ *ldap_tls_cert_file,
+ *ldap_tls_cipher_suite,
+ *ldap_tls_crl_file,
+ *ldap_tls_dh_file,
+ *ldap_tls_key_file;
#ifdef HAS_LDAP_INITIALIZE
static char *ldap_server_url;
#endif /* HAS_LDAP_INITIALIZE */
@@ -171,7 +178,9 @@
ldap_forcedefaultuid = 0, ldap_forcedefaultgid = 0,
ldap_forcegenhdir = 0, ldap_protocol_version = 3,
ldap_dereference = LDAP_DEREF_NEVER,
- ldap_search_scope = LDAP_SCOPE_SUBTREE;
+ ldap_search_scope = LDAP_SCOPE_SUBTREE,
+ ldap_tls_crl_check = -1,
+ ldap_tls_require_cert = -1;
static struct timeval ldap_querytimeout_tp;
static uid_t ldap_defaultuid = -1;
@@ -214,6 +223,86 @@
struct berval bindcred;
#endif
+ if (ldap_tls_ca_cert_dir) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTDIR, ldap_tls_ca_cert_dir);
+ if (ret != LDAP_OPT_SUCCESS) {
+ pr_log_pri(PR_LOG_ERR, MOD_LDAP_VERSION ": pr_ldap_connect(): Setting LDAP_OPT_X_TLS_CACERTDIR option failed: %s", ldap_err2string(ret));
+ pr_ldap_unbind();
+ return -1;
+ }
+ pr_log_debug(DEBUG3, MOD_LDAP_VERSION ": set LDAP_OPT_X_TLS_CACERTDIR to %s", ldap_tls_ca_cert_dir);
+ }
+
+ if (ldap_tls_ca_cert_file) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ldap_tls_ca_cert_file);
+ if (ret != LDAP_OPT_SUCCESS) {
+ pr_log_pri(PR_LOG_ERR, MOD_LDAP_VERSION ": pr_ldap_connect(): Setting LDAP_OPT_X_TLS_CACERTFILE option failed: %s", ldap_err2string(ret));
+ pr_ldap_unbind();
+ return -1;
+ }
+ pr_log_debug(DEBUG3, MOD_LDAP_VERSION ": set LDAP_OPT_X_TLS_CACERTFILE to %s", ldap_tls_ca_cert_file);
+ }
+
+ if (ldap_tls_cert_file) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CERTFILE, ldap_tls_cert_file);
+ if (ret != LDAP_OPT_SUCCESS) {
+ pr_log_pri(PR_LOG_ERR, MOD_LDAP_VERSION ": pr_ldap_connect(): Setting LDAP_OPT_X_TLS_CERTFILE option failed: %s", ldap_err2string(ret));
+ pr_ldap_unbind();
+ return -1;
+ }
+ pr_log_debug(DEBUG3, MOD_LDAP_VERSION ": set LDAP_OPT_X_TLS_CERTFILE to %s", ldap_tls_cert_file);
+ }
+
+ if (ldap_tls_cipher_suite) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CIPHER_SUITE, ldap_tls_cipher_suite);
+ if (ret != LDAP_OPT_SUCCESS) {
+ pr_log_pri(PR_LOG_ERR, MOD_LDAP_VERSION ": pr_ldap_connect(): Setting LDAP_OPT_X_TLS_CIPHER_SUITE option failed: %s", ldap_err2string(ret));
+ pr_ldap_unbind();
+ return -1;
+ }
+ pr_log_debug(DEBUG3, MOD_LDAP_VERSION ": set LDAP_OPT_X_TLS_CIPHER_SUITE to %s", ldap_tls_cipher_suite);
+ }
+
+ if (ldap_tls_dh_file) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_DHFILE, ldap_tls_dh_file);
+ if (ret != LDAP_OPT_SUCCESS) {
+ pr_log_pri(PR_LOG_ERR, MOD_LDAP_VERSION ": pr_ldap_connect(): Setting LDAP_OPT_X_TLS_DHFILE option failed: %s", ldap_err2string(ret));
+ pr_ldap_unbind();
+ return -1;
+ }
+ pr_log_debug(DEBUG3, MOD_LDAP_VERSION ": set LDAP_OPT_X_TLS_DHFILE version to %s", ldap_tls_dh_file);
+ }
+
+ if (ldap_tls_key_file) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_KEYFILE, ldap_tls_key_file);
+ if (ret != LDAP_OPT_SUCCESS) {
+ pr_log_pri(PR_LOG_ERR, MOD_LDAP_VERSION ": pr_ldap_connect(): Setting LDAP_OPT_X_TLS_KEYFILE option failed: %s", ldap_err2string(ret));
+ pr_ldap_unbind();
+ return -1;
+ }
+ pr_log_debug(DEBUG3, MOD_LDAP_VERSION ": pr_ldap_connect(): set LDAP_OPT_X_TLS_KEYFILE to %s", ldap_tls_key_file);
+ }
+
+ if (ldap_tls_crl_check != -1) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_CRLCHECK, (void *)&ldap_tls_crl_check);
+ if (ret != LDAP_OPT_SUCCESS) {
+ pr_log_pri(PR_LOG_ERR, MOD_LDAP_VERSION ": pr_ldap_connect(): Setting LDAP_OPT_X_TLS_CRLCHECK option failed: %s", ldap_err2string(ret));
+ pr_ldap_unbind();
+ return -1;
+ }
+ pr_log_debug(DEBUG3, MOD_LDAP_VERSION ": set LDAP_OPT_X_TLS_CRLCHECK to %d", ldap_tls_crl_check);
+ }
+
+ if (ldap_tls_require_cert != -1) {
+ ret = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, (void *)&ldap_tls_require_cert);
+ if (ret != LDAP_OPT_SUCCESS) {
+ pr_log_pri(PR_LOG_ERR, MOD_LDAP_VERSION ": pr_ldap_connect(): Setting LDAP_OPT_X_TLS_REQUIRE_CERT option failed: %s", ldap_err2string(ret));
+ pr_ldap_unbind();
+ return -1;
+ }
+ pr_log_debug(DEBUG3, MOD_LDAP_VERSION ": set LDAP_OPT_X_TLS_REQUIRE_CERT to %d", ldap_tls_require_cert);
+ }
+
#ifdef HAS_LDAP_INITIALIZE
pr_log_debug(DEBUG3, MOD_LDAP_VERSION ": attempting connection to %s", ldap_server_url ? ldap_server_url : "(null)");
@@ -1876,6 +1965,130 @@
return PR_HANDLED(cmd);
}
+MODRET
+set_ldap_tls_ca_cert_dir(cmd_rec *cmd)
+{
+ CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_GLOBAL);
+
+ add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
+ return PR_HANDLED(cmd);
+}
+
+MODRET
+set_ldap_tls_ca_cert_file(cmd_rec *cmd)
+{
+ CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_GLOBAL);
+
+ add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
+ return PR_HANDLED(cmd);
+}
+
+MODRET
+set_ldap_tls_cert_file(cmd_rec *cmd)
+{
+ CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_GLOBAL);
+
+ add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
+ return PR_HANDLED(cmd);
+}
+
+MODRET
+set_ldap_tls_cipher_suite(cmd_rec *cmd)
+{
+ CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_GLOBAL);
+
+ add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
+ return PR_HANDLED(cmd);
+}
+
+MODRET
+set_ldap_tls_crl_check(cmd_rec *cmd)
+{
+ int value;
+ config_rec *c;
+
+ CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_GLOBAL);
+
+ if (strcasecmp(cmd->argv[1], "none") == 0) {
+ value = LDAP_OPT_X_TLS_CRL_NONE;
+ } else if (strcasecmp(cmd->argv[1], "peer") == 0) {
+ value = LDAP_OPT_X_TLS_CRL_PEER;
+ } else if (strcasecmp(cmd->argv[1], "all") == 0) {
+ value = LDAP_OPT_X_TLS_CRL_ALL;
+ } else {
+ CONF_ERROR(cmd, "LDAPTLSCrlCheck: expected a valid LDAP_OPT_X_TLS_CRLCHECK option (none, peer, all).");
+ }
+
+ c = add_config_param("LDAPTLSCrlCheck", 1, NULL);
+ c->argv[0] = pcalloc(c->pool, sizeof(int));
+ *((int *) c->argv[0]) = value;
+ return PR_HANDLED(cmd);
+}
+
+MODRET
+set_ldap_tls_dh_file(cmd_rec *cmd)
+{
+ CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_GLOBAL);
+
+ add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
+ return PR_HANDLED(cmd);
+}
+
+MODRET
+set_ldap_tls_crl_file(cmd_rec *cmd)
+{
+ CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_GLOBAL);
+
+ add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
+ return PR_HANDLED(cmd);
+}
+
+MODRET
+set_ldap_tls_key_file(cmd_rec *cmd)
+{
+ CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_GLOBAL);
+
+ add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
+ return PR_HANDLED(cmd);
+}
+
+MODRET
+set_ldap_tls_require_cert(cmd_rec *cmd)
+{
+ int value;
+ config_rec *c;
+
+ CHECK_ARGS(cmd, 1);
+ CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_GLOBAL);
+
+ if (strcasecmp(cmd->argv[1], "never") == 0) {
+ value = LDAP_OPT_X_TLS_NEVER;
+ } else if (strcasecmp(cmd->argv[1], "hard") == 0) {
+ value = LDAP_OPT_X_TLS_HARD;
+ } else if (strcasecmp(cmd->argv[1], "demand") == 0) {
+ value = LDAP_OPT_X_TLS_DEMAND;
+ } else if (strcasecmp(cmd->argv[1], "allow") == 0) {
+ value = LDAP_OPT_X_TLS_ALLOW;
+ } else if (strcasecmp(cmd->argv[1], "try") == 0) {
+ value = LDAP_OPT_X_TLS_TRY;
+ } else {
+ CONF_ERROR(cmd, "LDAPTLSRequireCert: expected a valid LDAP_OPT_X_TLS_REQUIRE_CERT option (never, hard, demand, allow, try).");
+ }
+
+ c = add_config_param("LDAPTLSRequireCert", 1, NULL);
+ c->argv[0] = pcalloc(c->pool, sizeof(int));
+ *((int *) c->argv[0]) = value;
+ return PR_HANDLED(cmd);
+}
+
static int
ldap_getconf(void)
{
@@ -2059,6 +2272,22 @@
"(&(", ldap_attr_memberuid, "=%v)(objectclass=posixGroup))", NULL);
}
}
+
+ ldap_tls_ca_cert_dir = (char *)get_param_ptr(main_server->conf, "LDAPTLSCACertDir", FALSE);
+ ldap_tls_ca_cert_file = (char *)get_param_ptr(main_server->conf, "LDAPTLSCACertFile", FALSE);
+ ldap_tls_cert_file = (char *)get_param_ptr(main_server->conf, "LDAPTLSCertFile", FALSE);
+ ldap_tls_cipher_suite = (char *)get_param_ptr(main_server->conf, "LDAPTLSCipherSuite", FALSE);
+ ldap_tls_crl_file = (char *)get_param_ptr(main_server->conf, "LDAPTLSCrlFile", FALSE);
+ ldap_tls_dh_file = (char *)get_param_ptr(main_server->conf, "LDAPTLSDHFile", FALSE);
+ ldap_tls_key_file = (char *)get_param_ptr(main_server->conf, "LDAPTLSKeyFile", FALSE);
+ ptr = get_param_ptr(main_server->conf, "LDAPTLSCrlCheck", FALSE);
+ if (ptr) {
+ ldap_tls_crl_check = *((int *) ptr);
+ }
+ ptr = get_param_ptr(main_server->conf, "LDAPTLSRequireCert", FALSE);
+ if (ptr) {
+ ldap_tls_require_cert = *((int *) ptr);
+ }
return 0;
}
@@ -2092,7 +2321,15 @@
{ "LDAPGenerateHomedirPrefixNoUsername", set_ldap_genhdirprefixnouname, NULL },
{ "LDAPForceGeneratedHomedir", set_ldap_forcegenhdir, NULL },
{ "LDAPDefaultQuota", set_ldap_defaultquota, NULL },
- { "LDAPGroups", set_ldap_grouplookups, NULL },
+ { "LDAPTLSCACertDir", set_ldap_tls_ca_cert_dir, NULL },
+ { "LDAPTLSCACertFile", set_ldap_tls_ca_cert_file, NULL },
+ { "LDAPTLSCertFile", set_ldap_tls_cert_file, NULL },
+ { "LDAPTLSCipherSuite", set_ldap_tls_cipher_suite, NULL },
+ { "LDAPTLSCrlCheck", set_ldap_tls_crl_check, NULL },
+ { "LDAPTLSCrlFile", set_ldap_tls_crl_file, NULL },
+ { "LDAPTLSDHFile", set_ldap_tls_dh_file, NULL },
+ { "LDAPTLSKeyFile", set_ldap_tls_key_file, NULL },
+ { "LDAPTLSRequireCert", set_ldap_tls_require_cert, NULL },
{ NULL, NULL, NULL },
};
Then, configure ProFTPd ([/usr/local]/etc/proftpd.conf), for example:
LDAPServer ldaps://aaa.bbb.ccc.ddd:636/??one
LDAPAuthBinds on
LDAPBindDN "cn=user,dc=example,dc=net" "password"
LDAPUsers "ou=people,o=organisation,dc=example,dc=net"
LDAPTLSCACertFile /usr/local/etc/certs/example-ca-crt.pem
LDAPTLSCertFile /usr/local/etc/certs/example-ftp-crt.pem
LDAPTLSKeyFile /usr/local/etc/certs/exemple-key.pem
The available options are:
- LDAPTLSCACertDir
- LDAPTLSCACertFile
- LDAPTLSCertFile
- LDAPTLSCipherSuite
- LDAPTLSCrlCheck
- LDAPTLSCrlFile
- LDAPTLSDHFile
- LDAPTLSKeyFile
- LDAPTLSRequireCert
You can find more information about what they do by typing "man 3 ldap_set_option".