Overview
With dnsmasq there is a very small, simple, powerful, and reliable combined dns server, dns cache, dhcp server, and tftp server. It's got everything you need to serve a small home/classroom/school sized network that can include thin clients. It's very easy to configure (dns just serves up the /etc/hosts file with auto-dns support for dhcp clients), and its dhcp server can easily be configured to serve anything like auto-proxy config or different boot options for different thin clients. It's small enough to run on a OpenWRT? router or QNAP NAS.
One of the things a small network like this really needs is an LDAP server for managing users. It needs to be small and efficient, with enough grunt to support at least 100 clients and 1000 users when running on something like a router or NAS. It must be powerful enough to support most commonly used functionality, but familiar and simple to configure. It absolutely must be reliable and secure enough for the home/classroom/school environment. I believe that they should be written in C (widely used and fast) using an event-loop design (threads suck).
Objectives
Primary objectives are;
- Support enough LDAP functionality for pam-ldap and nss-ldap to work for clients.
- Serve familiar /etc passwd group shadow files directly with no special file formats.
- Easily support ~100 clients and 1000 users on router level hardware.
- Keep the code small and simple, using existing libraries as much as possible.
Secondary objectives are;
- Support TLS and SASL for security in a slightly untrustworthy network.
- Support additional nss files like hosts networks netgroup etc.
- Support writes enough so that pam-ldap updates using passwd chfn chsh work.
- Support samba domain authentication and schemas.
- Support arbitrary schemas served from passwd like files.
- Support ACLs?.
Alternatives
Existing LDAP servers
- OpenLdap? - Feels a bit big and clunky. Support for serving /etc/passwd deprecated in favour of database backends. Seems to be targeting enterprise sized setups, not small home networks.
- https://github.com/vlm/ldap-server-example - OK example. Looks like it's a one-shot server; run it and it answers one query then terminates. Might be useful to pull code from.
- https://github.com/urbanserj/entente - uses libpam, libev, and asn1c to compile LDAP ASN definition directly. A single 500 line file gives a server that can be used as an ldap auth backend using pam, but that's it. Should be extendible to support more of the LDAP functionality.
- http://www.fefe.de/tinyldap/ - Written in response to OpenLdap? being slow. Uses a simple fork-per-connection model and claims to still beat OpenLdap?. I think this shows that forking is not the high overhead it once was. Much more code than entente, including it's own ASN parser etc.
Of these entente looks the most interesting. It uses libev already. Its use of asn1c to compile the LDAP ASN appeals to my "as little coding as possible" objective. I think I'll use this as my starting point, and borrow code from the others when/if it turns out to be useful.
Existing Libraries
There are plenty of libraries, but I don't believe it's worth picking obscure ones unless they provide some really significant advantage over the more popular alternatives. Popular libraries get more use, debugging, support, and are more likely to be already installed. The Debian repository is a good indicator of popularity; if it's not in there, it's probably not popular enough to use. If multiple alternatives are in there, then the list of other packages that depend on it is a good indicator of which one is more popular. Also interesting is its own dependencies; does it depend only on other popular libraries? Something that has no dependencies but which implements large amounts of functionality normally provided by other libraries has probably re-invented lots of stuff badly (or statically linked the other libs in).
I also think code and binary size is a surprisingly good indicator of quality; the smaller the better. Look at the Debian installed package sizes for hints, but be aware that sometimes larger packages include lots of documentation, which is a good thing.
- async event framework
- libevent - the original event loop library.
- libev - the new event loop library which is faster, with slightly more functionality.
- datastructures
- glib - good datastructures, but bundled with libs for eventloops, logging, memory, errors, threads, etc. If you drink this cool-aid you don't really need many other libraries, but your code will not look like normal C.
- TLS secure network transport
- openssl
- gnutls
- CyaSSL?
- stunnel
- stud
- SASL secure authentication
- cyrus-sasl
- gsasl
- cyrus saslauthd
I've decided that the libs to use are libev, glib, gnutls (or stud in the beginning), and gsasl.
Security Options
So that anyone can see users/groups, /etc/passwd and /etc/group are world readable. Historically these used to contain crypt passwords, relying on the crypt security to protect users passwords. This makes dictionary attacks too easy, so /etc/shadow and /etc/gshadow were created readable only by root to contain the crypted passwords. This way only root has access to the crypted passwords for doing auth.
LDAP can export data with or without TLS encryption. Originally TLS was done by serving ldaps:// through a secure tunnel on another port, but now LDAP supports a StartTLS? command to turn on encryption in the middle of an LDAP session.
When authenticating with an LDAP server there are several alternatives. The original simple BIND sends the password in plaintext to the LDAP server. This is a security hole without TLS on an untrusted network, so SASL was added, which supports several auth methods including challenge-response auth that doesn't send the plaintext passwd over the wire. The problem with challenge-response protocols is they require the server to know the plaintext passwd. See the following;
http://www.ldapguru.info/ldap/authentication-best-practices.html
We want to use /etc/shadow passwords which are crypted, so we can't use SASL challenge-response auths like DIGEST-MD5. Note that things like smtp auth uses SASL PLAIN over TLS, which is the same level of security as simple BIND over TLS.
This means SASL adds nothing over simple BIND for our purposes. The only reason to add SASL support would be for clients that insist on using SASL BIND, and then we can only support SASL PLAIN. Note that entente already supports simple BIND using pam.
Client machines can use ldap for users/groups 2 different ways;
- Serve /etc/shadow with ldap readable only by root, have client machines use libnss-ldap for user/group/shadow, and use normal pam-unix for auth. This means the ldap server needs read access to /etc/shadow, which means either running as root or as a member of group shadow. The client machines will have full access to /etc/shadow, so need to be trusted. The contents of /etc/shadow are sent over the network, so you need a trusted network, or TLS encryption.
- Don't serve /etc/shadow with ldap, use libnss-ldap for user/group (with empty shadow?), and use libpam-ldap for auth. This means that the ldap server doesn't need read access to /etc/shadow. The contents of /etc/shadow are never sent to clients so they don't need to be trusted. However, clients will send passwords to the ldap server for bind, so you still need a trusted network, or TLS encryption.
Note that to bind to the ldap port, the ldap server needs to start as root, but can drop privileges after that if it doesn't need /etc/shadow read access.
Implementation
See http://github.com/dbaarda/LightLdapd for the latest sources.
This was forked and renamed from http://github.com/urbanserj/entente after submitting several improvements and cleanups. The fork was done with the approval blessing of the entente author so that entente could remain a good example of a bare minimum LDAP server.