[Twisted-Python] Question on pamauth.py

Phil Mayers p.mayers at imperial.ac.uk
Wed May 3 04:34:13 EDT 2006


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.




More information about the Twisted-Python mailing list