rsync & ldap authentication

Darren Jung darren at web-services.net
Wed Feb 12 04:25:12 EST 2003


Hi,

I'm trying to get rsync 2.5.6 to authenticate users via openldap-2.0.23.  I was looking through the mailing list archives and found a patch for rsync-2.4.6 that does this for me.  I was just wondering if this is still valid, or if there has been a new patch or new implementation that has superceded this patch.  Any help would be great.  The message I am referring to is as follows:



--T4sUOijqQbZv57TR
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

hello,

here is a patch against rsync-2.4.6. It may be used to get rsyncd
authentication data from a ldap directory.

cu, Stefan
-- 
Stefan Nehlsen | ParlaNet Administration | sn at parlanet.de | +49 431 988-1260

--T4sUOijqQbZv57TR
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="rsyncd-ldap.txt"

LDAP support for rsyncd

I have made a few changes to rsync 2.4.6 to add to 2 new features to rsync
when running as daemon:

1. getting authentification information from an ldap server. The user has to
   belong to a special group (groupofuniqueusers) and must have a special
   attribute in his ldap entry containing the rsync password in clear text.

2. specification of a list of prefixes that will be removed from the user name
   given by the client.

The second option was added to be compatible to our ftp server and is simple
to use or ignore. Maybe there is better, more flexible way to rewrite usernames.

Only the files authenticate.c and loadparm.c have been modified.


How to build a rsync version with ldap support

There is no autoconf support for the new features. To enable them edit the
Makefile after running configure.

 - Add -DWITH_LDAP -DWITH_MANGLE_USER to the CFLAGS line.

 - On Linux add -lldap and -llber to the LIBS Flags.

 - On Solaris add -lldap to the LIBS Flags.


New options in rsyncd.conf

All ldap options will exist even when rsync isn't build ldap support.

To enable anonymous access to rsyncd both "auth users" and
"ldap auth usergroup" have to be empty.

If both ways of authentification are enabled the local password file is checked
before ldap.

Most options are borrowed from samba :-)

GLOBAL OPTIONS

ldap server

	This option has to contain the hostname of the ldap server we want to
	ask.

	example:	ldap server = ldap.ParlaNet.de

	default:	none


ldap port

	This option has to contain the tcp port where the ldap service is
	listening.

	example:	ldap port = 11034

	default:	ldap port = 389


ldap root

	This option should contain the dn to bind with to the ldap server.

	You may leave this option and "ldap root password" empty to bind
	as anonymous but this isn't a good idea.

	If you use this option you should not make the configuration file
	world readable.

	example:	ldap root = cn=webserver-rsyncd, o=ParlaNet, c=DE

	default:	empty


ldap root passwd

	This will contain the password to bind with.

	see also: 	ldap root

	example:	ldap root passwd = sECret

	default:	empty


ldap suffix

	This option should contain the basedn as starting point for search
	operations.

	example:	ldap suffix = o=ParlaNet, c=DE

	default:	empty


MODULE OPTIONS

ldap filter

	This parameter specifies an LDAP search filter used
	to  search for a user name in the LDAP database. It
	must contain the string %u which will  be  replaced
	with the user being searched for.

	example:	ldap filter = (&(objectclass=myperson)(uid=%u))

	default:	empty string


ldap passwd attribute

	This parameter specifies the name of the attribute in the user
	object that contains the rsyncd password in clear text.

	You should use access control on this attribute and it has to be case
	sensitive.

	example:	ldap passwd attribute = myRsyncdPassword

	default:	empty string


ldap auth usergroup

	This parameter specifies the dn of a group the user must belong to.

	objectclass=groupOfUniqueNames is very good idea

	example:	ldap auth usergroup = cn=webmaster sub, o=ParlaNet, c=DE

	default:	empty string


ldap auth users attribute

	This parameter specifies the name of the attribute in the
	"ldap auth usergroup" object that contain the dn of a member.

	If objectclass is groupOfUniqueNames this always should be "uniqueMember".

	default:	ldap auth users attribute = uniqueMember


mangle user

	This option is no ldap option.

	This parameter specifies a list of prefixes that will be removed
	from the username before it is tested against the access lists.

	example:	mangle user = web- adm-

	default:	empty string


HOWTO use this

1. Create a new user. The rsyncd will use this identity to bind to the
   ldap directory.

   the very hard way:

     ldapadd -D <manager binddn> -w <password> -h <host> -v << EOT
     dn: cn=web-rsyncd, o=ParlaNet, c=DE
     objectclass: person
     sn: daemon
     cn: web-rsyncd
     description: rsyncd user from webserver
     userpassword: {bla}xxxxxxxxxxxxx
     EOT

2. Define a new case sensitive attribute (myRsyncdPassword in this
   example). Make it optional for your standard user class (myuser).

3. Put an ACL on this attribute so that only your rsync user and
   your administrators are able to read, compare and search it.

4. Your user must be able to write a value into their objects.

   the hard way:

     ldapmodify -v -D uid=test,ou=sub,o=ParlaNet,c=DE -w "pw" -h ldap << EOT
     dn: uid=test,ou=sub,o=ParlaNet,c=DE
     add: myRsyncdPassword
     myRsyncdPassword: secret
     EOT

5. Make sure all your webmaster are in the webmaster group:

     $ ldapsearch -b o=ParlaNet,c=DE -h host "cn=webmaster"
     cn=webmaster, ou=sub, o=ParlaNet, c=DE
     cn=webmaster
     description=webmaster group
     ou=sub
     objectclass=top
     objectclass=groupofuniquenames
     uniquemember=uid=test,ou=sub,o=ParlaNet,c=DE
     uniquemember=uid=kurt,ou=sub,o=ParlaNet,c=DE
     uniquemember=uid=tobi,ou=sub,o=ParlaNet,c=DE

5. write your /etc/rsyncd.conf :

   # /etc/rsyncd.conf
   motd file = /etc/rsyncd.banner
   ldap server = ldapserver.parlanet.de
   ldap root = cn=web-rsyncd, o=ParlaNet, c=DE
   ldap root passwd = password
   ldap suffix = o=ParlaNet, c=DE
   log file = /var/log/rsyncd.log

   [www]
   comment = http://www.something.de/
   path = /var/www/pages
   uid = www
   gid = www
   read only = yes
   list = yes
   ldap filter = (&(objectclass=myuser)(ou=sub)(uid=%u))
   ldap passwd attribute = myRsyncdPassword
   ldap auth usergroup = cn=webmaster,ou=sub,o=ParlaNet,c=de
   ldap auth users attribute = uniquemember
   auth users = 
   hosts allow = 192.168.1.0/24
   max connections = 1
   mangle user = web-
   # EOF /etc/ryncd.conf


things to do

	- check if "ldap auth users attribute" is really needed
	- maybe "ldap filter" should be a module option
	- The "mangle user" does exectly what I need. Maybe somebody has a
	  better idea.
	- improve my english :-)


Stefan Nehlsen  (sn at ParlaNet.de)	12.12.2000
	

--T4sUOijqQbZv57TR
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="rsync-2.4.6-ldapauth.diff"

diff -u rsync-2.4.6/authenticate.c rsync-2.4.6-ldapauth/authenticate.c
--- rsync-2.4.6/authenticate.c	Wed Sep  6 04:46:43 2000
+++ rsync-2.4.6-ldapauth/authenticate.c	Tue Dec 12 10:44:45 2000
@@ -19,6 +19,11 @@
 /* support rsync authentication */
 #include "rsync.h"
 
+#ifdef WITH_LDAP
+#include <lber.h>
+#include <ldap.h>
+#endif
+
 /***************************************************************************
 encode a buffer using base64 - simple and slow algorithm. null terminates
 the result.
@@ -78,9 +83,23 @@
 	STRUCT_STAT st;
 	int ok = 1;
 	extern int am_root;
+	char *users, *tok;
 
 	if (!fname || !*fname) return 0;
 
+	/* this code was in auth_server() */
+	users = strdup(lp_auth_users(module));
+	if (!users) return 0;
+
+	for (tok=strtok(users," ,\t"); tok; tok = strtok(NULL," ,\t")) {
+		if (strcmp(tok, user) == 0) break;
+	}
+	free(users);
+
+	if (!tok) {
+		return 0;
+	}
+
 	fd = open(fname,O_RDONLY);
 	if (fd == -1) return 0;
 
@@ -132,6 +151,84 @@
 	return 1;
 }
 
+#ifdef WITH_LDAP
+/* return the secret for a user from the ldap server. maximum length
+   is len. null terminate it */
+static int get_ldap_secret(int module, char *user, char *secret, int len)
+{
+	LDAP *ld;
+	LDAPMessage *result, *entry;
+	char *attrs[2], *dn, **vals;
+	char filter[512], *c;
+	char *group=lp_ldap_auth_usergroup(module);
+	int l=0, ok=0;
+
+	/* password attribute to get as result */
+	attrs[0]=lp_ldap_passwd_attribute(module); attrs[1]=NULL;
+
+	/* find nasty character in user that would mess up the ldap filter */
+	for (c="()!&|*=<>~"; *c; c++) {
+		if (strchr(user, *c)) {
+			return 0;
+		}
+	}
+
+	/*   $filter=&lp_ldap_filter($module))=~s/%u/$user/g; :-) */
+	memset(filter, 0, sizeof(filter));
+	for (c=lp_ldap_filter(module); *c && l < sizeof(filter) - 1; c++) {
+		if (*c=='%' && *(c+1)=='u') {
+			char *b;
+			for (b=user; *b && l < sizeof(filter) - 1; b++) {
+				filter[l++]=*b;
+			}
+			c++;
+		} else {
+			filter[l++]=*c;
+		}
+	}
+
+	if ((ld=ldap_init(lp_ldap_server(), lp_ldap_port())) == NULL) {
+		rprintf(FERROR,"ldap: init failed (%s:%d)\n", lp_ldap_server(), lp_ldap_port());
+	} else {
+		if (ldap_simple_bind_s(ld, lp_ldap_root(), lp_ldap_root_passwd()) != LDAP_SUCCESS) {
+			rprintf(FERROR,"ldap: bind failed %s\n", lp_ldap_root());
+	} else {
+		if (ldap_search_s(ld, lp_ldap_suffix(), LDAP_SCOPE_SUBTREE, filter, attrs, 0, &result) != LDAP_SUCCESS) {
+			rprintf(FERROR,"ldap: search_s failed\n");
+	} else {
+		if (!(entry=ldap_first_entry(ld, result))) {
+			rprintf(FERROR,"ldap: first_entry failed or no user found\n");
+	} else {
+		if (!(dn=ldap_get_dn(ld, entry))) {
+			rprintf(FERROR,"ldap: get_dn failed\n");
+	} else {
+		if (ldap_compare_s(ld, group, lp_ldap_auth_users_attribute(module), dn) != LDAP_COMPARE_TRUE) {
+			rprintf(FERROR,"ldap: compare_s failed or \"%s\" is not member of \"%s\"\n", dn, group);
+	} else {
+		if (!(vals=ldap_get_values(ld, result, attrs[0])) || !vals[0] || vals[1]) {
+			rprintf(FERROR,"ldap: \"%s\" has no valid password\n", dn);
+			if (vals) ldap_value_free(vals);
+	} else {
+		memset(secret, 0, sizeof(secret)); /* paranoid */
+		strlcpy(secret, vals[0], len);
+		ok=1;
+		ldap_value_free(vals);
+	} /* get_values */
+	} /* compare_s */
+		free(dn);
+	} /* get_dn */
+		ldap_msgfree(entry);
+	} /* first_entry */
+		ldap_msgfree(result);
+	} /* search */
+	} /* bind */
+	} /* init */
+	ldap_unbind(ld);
+
+	return ok;
+}
+#endif
+
 static char *getpassf(char *filename)
 {
 	char buffer[100];
@@ -205,6 +302,7 @@
 char *auth_server(int fd, int module, char *addr, char *leader)
 {
 	char *users = lp_auth_users(module);
+	char *group = lp_ldap_auth_usergroup(module);
 	char challenge[16];
 	char b64_challenge[30];
 	char line[MAXPATHLEN];
@@ -212,10 +310,19 @@
 	char secret[100];
 	char pass[30];
 	char pass2[30];
-	char *tok;
 
 	/* if no auth list then allow anyone in! */
-	if (!users || !*users) return "";
+#ifdef WITH_LDAP
+	if ((!users || !*users) && (!group || !*group)) return "";
+#else
+	if (!users || !*users) {
+		if (group && *group) {
+			rprintf(FERROR,"no ldap support: unset \"ldap auth usergroup\" for anonymous access\n");
+			return NULL;
+		}
+		return "";
+	}
+#endif
 
 	gen_challenge(addr, challenge);
 	
@@ -234,23 +341,38 @@
 		return NULL;
 	}
 
-	users = strdup(users);
-	if (!users) return NULL;
-
-	for (tok=strtok(users," ,\t"); tok; tok = strtok(NULL," ,\t")) {
-		if (strcmp(tok, user) == 0) break;
-	}
-	free(users);
-
-	if (!tok) {
-		return NULL;
+#ifdef WITH_MANGLE_USER
+	/* foreach $pattern (split(/[ ,\t]+/, &lp_mangle_user($module)){ $user=~s/^$pattern//;warn "bla";last} */
+	if (lp_mangle_user(module)) {
+		char *prefix, *tofree;
+		prefix = tofree = strdup(lp_mangle_user(module));
+		if(!prefix) return NULL; /* strdup may fail */
+		for (prefix=strtok(prefix, " ,\t"); prefix; prefix=strtok(NULL, " ,\t")) {
+			if (strstr(user, prefix) == user) {
+				char *p = user + strlen(prefix);
+				rprintf(FINFO,"mangle user: rewriting \"%s\" to \"%s\"\n", user, p);
+				memmove(user, p, strlen(p) + 1);
+				break;
+			}
+		}
+		free(tofree);
 	}
+#endif
+	/* checking if user is in "auth users" now happens in get_secret() */
 	
 	memset(secret, 0, sizeof(secret));
+#ifdef WITH_LDAP
+	if (!(users && *users && get_secret(module, user, secret, sizeof(secret)-1) ||
+	      group && *group && get_ldap_secret(module, user, secret, sizeof(secret)-1))) {
+		memset(secret, 0, sizeof(secret));
+		return NULL;
+	}
+#else
 	if (!get_secret(module, user, secret, sizeof(secret)-1)) {
 		memset(secret, 0, sizeof(secret));
 		return NULL;
 	}
+#endif
 
 	generate_hash(secret, b64_challenge, pass2);
 	memset(secret, 0, sizeof(secret));
@@ -260,7 +382,6 @@
 
 	return NULL;
 }
-
 
 void auth_client(int fd, char *user, char *challenge)
 {
Common subdirectories: rsync-2.4.6/lib and rsync-2.4.6-ldapauth/lib
diff -u rsync-2.4.6/loadparm.c rsync-2.4.6-ldapauth/loadparm.c
--- rsync-2.4.6/loadparm.c	Wed Sep  6 04:46:43 2000
+++ rsync-2.4.6-ldapauth/loadparm.c	Tue Dec 12 10:45:01 2000
@@ -44,6 +44,12 @@
  */
 
 #include "rsync.h"
+
+#ifdef WITH_LDAP
+#include <lber.h>
+#include <ldap.h>
+#endif
+
 #define PTR_DIFF(p1,p2) ((ptrdiff_t)(((char *)(p1)) - (char *)(p2)))
 #define strequal(a,b) (strcasecmp(a,b)==0)
 #define BOOLSTR(b) ((b) ? "Yes" : "No")
@@ -96,6 +102,11 @@
 	char *pid_file;
 	int syslog_facility;
 	char *socket_options;
+	char *ldap_server;
+	int ldap_port;
+	char *ldap_root;
+	char *ldap_root_passwd;
+	char *ldap_suffix;
 } global;
 
 static global Globals;
@@ -133,6 +144,13 @@
 	int timeout;
 	int max_connections;
 	BOOL ignore_nonreadable;
+	char *ldap_filter;
+	char *ldap_passwd_attribute;
+	char *ldap_auth_usergroup;
+	char *ldap_auth_users_attribute;
+#ifdef  WITH_MANGLE_USER
+	char *mangle_user;
+#endif
 } service;
 
 
@@ -164,7 +182,15 @@
 	"*.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz",    /* dont compress */
 	0,        /* timeout */
 	0,        /* max connections */
-	False     /* ignore nonreadable */
+	False,    /* ignore nonreadable */
+	NULL,     /* ldap filter */
+	NULL,     /* ldap passwd attribute */
+	NULL,     /* ldap auth usergroup */
+	"uniquemember" /* ldap auth users attribute */
+#ifdef WITH_MANGLE_USER
+	,
+	NULL      /* mangle user */
+#endif
 };
 
 
@@ -252,6 +278,11 @@
   {"socket options",   P_STRING,  P_GLOBAL, &Globals.socket_options,NULL,  0},
   {"log file",         P_STRING,  P_GLOBAL, &Globals.log_file,      NULL,  0},
   {"pid file",         P_STRING,  P_GLOBAL, &Globals.pid_file,      NULL,  0},
+  {"ldap server",      P_STRING,  P_GLOBAL, &Globals.ldap_server,   NULL,  0},
+  {"ldap port",        P_INTEGER, P_GLOBAL, &Globals.ldap_port,     NULL,  0},
+  {"ldap root",        P_STRING,  P_GLOBAL, &Globals.ldap_root,     NULL,  0},
+  {"ldap root passwd", P_STRING,  P_GLOBAL, &Globals.ldap_root_passwd,NULL,0},
+  {"ldap suffix",      P_STRING,  P_GLOBAL, &Globals.ldap_suffix,   NULL,  0},
 
   {"timeout",          P_INTEGER, P_LOCAL,  &sDefault.timeout,     NULL,  0},
   {"max connections",  P_INTEGER, P_LOCAL,  &sDefault.max_connections,NULL, 0},
@@ -279,6 +310,13 @@
   {"log format",       P_STRING,  P_LOCAL,  &sDefault.log_format,  NULL,   0},
   {"refuse options",   P_STRING,  P_LOCAL,  &sDefault.refuse_options,NULL, 0},
   {"dont compress",    P_STRING,  P_LOCAL,  &sDefault.dont_compress,NULL,  0},
+  {"ldap filter",      P_STRING,  P_LOCAL,  &sDefault.ldap_filter, NULL,   0},
+  {"ldap passwd attribute",P_STRING,P_LOCAL,&sDefault.ldap_passwd_attribute,NULL,0},
+  {"ldap auth usergroup",P_STRING,P_LOCAL,  &sDefault.ldap_auth_usergroup, NULL,0},
+  {"ldap auth users attribute",P_STRING,P_LOCAL,&sDefault.ldap_auth_users_attribute, NULL,0},
+#ifdef WITH_MANGLE_USER
+  {"mangle user",      P_STRING,  P_LOCAL,  &sDefault.mangle_user, NULL,   0},
+#endif
   {NULL,               P_BOOL,    P_NONE,   NULL,                  NULL,   0}
 };
 
@@ -292,6 +330,9 @@
 #ifdef LOG_DAEMON
 	Globals.syslog_facility = LOG_DAEMON;
 #endif
+#ifdef WITH_LDAP
+	Globals.ldap_port = LDAP_PORT;
+#endif
 }
 
 /***************************************************************************
@@ -358,6 +399,21 @@
 FN_LOCAL_STRING(lp_dont_compress, dont_compress)
 FN_LOCAL_INTEGER(lp_timeout, timeout)
 FN_LOCAL_INTEGER(lp_max_connections, max_connections)
+
+FN_GLOBAL_STRING(lp_ldap_server, &Globals.ldap_server)
+FN_GLOBAL_INTEGER(lp_ldap_port, &Globals.ldap_port)
+FN_GLOBAL_STRING(lp_ldap_root, &Globals.ldap_root)
+FN_GLOBAL_STRING(lp_ldap_root_passwd, &Globals.ldap_root_passwd)
+FN_GLOBAL_STRING(lp_ldap_suffix, &Globals.ldap_suffix)
+
+FN_LOCAL_STRING(lp_ldap_filter, ldap_filter)
+FN_LOCAL_STRING(lp_ldap_passwd_attribute, ldap_passwd_attribute)
+FN_LOCAL_STRING(lp_ldap_auth_usergroup, ldap_auth_usergroup)
+FN_LOCAL_STRING(lp_ldap_auth_users_attribute, ldap_auth_users_attribute)
+
+#ifdef WITH_MANGLE_USER
+FN_LOCAL_STRING(lp_mangle_user, mangle_user)
+#endif
 
 /* local prototypes */
 static int    strwicmp( char *psz1, char *psz2 );
Common subdirectories: rsync-2.4.6/packaging and rsync-2.4.6-ldapauth/packaging
Common subdirectories: rsync-2.4.6/zlib and rsync-2.4.6-ldapauth/zlib

--T4sUOijqQbZv57TR--



More information about the rsync mailing list