Duncan McGreggor wrote:
You biggest problem is actually going to be getting PyPAM working. As far as I know, and as far as tummy.com knows (the original sponsors of PyPAM), there's been no release since 1999. I toyed with the idea of using it at one point, but the amount of work necessary in updating the python was too onerous. Perhaps you have a stronger stomach than I :-)
Agreed that PyPAM has bitrotted. FWIW, I circumvented this by using Cyrus SASLs "saslauthd" unix socket protocol, with saslauthd configured to talk to PAM. /usr/sbin/saslauthd -m /var/run/saslauthd -a pam -c -n 0 def encode_short(s): i = socket.htons(s) return chr(i & 0xff) + chr((i >> 8) & 0xff) def encode_str(s): l = encode_short(len(s)) return l+s def decode_short(s): return socket.ntohs( (ord(s[1]) << 8) + ord(s[0]) ) class SaslAuthdProtocol(protocol.Protocol): def connectionMade(self): self.data = '' # we're going to check this lot username = encode_str(self.factory.username) password = encode_str(self.factory.password) service = encode_str(PAMSERVICENAME) realm = encode_str(YOURREALM) # ok message = username + password + service + realm self.transport.write(message) def dataReceived(self, data): # ok, we've an outstanding request - where are we? # we're expecting 2 bytes of length, then length bytes of # data which is "code<SP>reason" self.data = self.data + data dl = len(self.data) if dl < 2: # we don't have the length yet return l = decode_short(self.data[:2]) if dl < l + 2: # we don't have the rest of the reply yet return if dl > l + 2: # wtf? self.transport.loseConnection() # Ok, we can reply resp = self.data[2:2+l] if ' ' in resp: resp, reason = resp.split(' ', 1) else: reason = '' if resp=='OK': self.factory.deferred.callback(reason) else: self.factory.deferred.errback(Exception(reason)) class SaslChecker: # We are an ICredentialsChecker implementor interface.implements(checkers.ICredentialsChecker) # We can only check plaintext username/password combos credentialInterfaces = ( credentials.IUsernamePassword, ) # return the "avatar ID" - username def ok(self, matched, username): return username def err(self, f, username): raise error.UnauthorizedLogin(f.getErrorMessage()) def requestAvatarId(self, creds): # Adapt the credentials to a username/password pair up = credentials.IUsernamePassword(creds, default=None) # It's going to be a deferred reply d = defer.Deferred() d.addCallbacks( self.ok, self.err, (up.username,), {}, (up.username,), {} ) # Send the reply off to saslauthd via unix socket f = protocol.ClientFactory() f.username = up.username f.password = up.password f.deferred = d f.protocol = SaslAuthdProtocol reactor.connectUNIX('/var/run/saslauthd/mux', f) return d Works like a charm.