cred_unix.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. # -*- test-case-name: twisted.test.test_strcred -*-
  2. #
  3. # Copyright (c) Twisted Matrix Laboratories.
  4. # See LICENSE for details.
  5. """
  6. Cred plugin for UNIX user accounts.
  7. """
  8. from __future__ import absolute_import, division
  9. from zope.interface import implementer
  10. from twisted import plugin
  11. from twisted.cred.strcred import ICheckerFactory
  12. from twisted.cred.checkers import ICredentialsChecker
  13. from twisted.cred.credentials import IUsernamePassword
  14. from twisted.cred.error import UnauthorizedLogin
  15. from twisted.internet import defer
  16. from twisted.python.compat import StringType
  17. def verifyCryptedPassword(crypted, pw):
  18. """
  19. Use L{crypt.crypt} to Verify that an unencrypted
  20. password matches the encrypted password.
  21. @param crypted: The encrypted password, obtained from
  22. the Unix password database or Unix shadow
  23. password database.
  24. @param pw: The unencrypted password.
  25. @return: L{True} if there is successful match, else L{False}.
  26. @rtype: L{bool}
  27. """
  28. try:
  29. import crypt
  30. except ImportError:
  31. crypt = None
  32. if crypt is None:
  33. raise NotImplementedError("cred_unix not supported on this platform")
  34. if not isinstance(pw, StringType):
  35. pw = pw.decode('utf-8')
  36. if not isinstance(crypted, StringType):
  37. crypted = crypted.decode('utf-8')
  38. return crypt.crypt(pw, crypted) == crypted
  39. @implementer(ICredentialsChecker)
  40. class UNIXChecker(object):
  41. """
  42. A credentials checker for a UNIX server. This will check that
  43. an authenticating username/password is a valid user on the system.
  44. Does not work on Windows.
  45. Right now this supports Python's pwd and spwd modules, if they are
  46. installed. It does not support PAM.
  47. """
  48. credentialInterfaces = (IUsernamePassword,)
  49. def checkPwd(self, pwd, username, password):
  50. """
  51. Obtain the encrypted password for C{username} from the Unix password
  52. database using L{pwd.getpwnam}, and see if it it matches it matches
  53. C{password}.
  54. @param pwd: Module which provides functions which
  55. access to the Unix password database.
  56. @type pwd: C{module}
  57. @param username: The user to look up in the Unix password database.
  58. @type username: L{unicode}/L{str} or L{bytes}
  59. @param password: The password to compare.
  60. @type username: L{unicode}/L{str} or L{bytes}
  61. """
  62. try:
  63. if not isinstance(username, StringType):
  64. username = username.decode('utf-8')
  65. cryptedPass = pwd.getpwnam(username).pw_passwd
  66. except KeyError:
  67. return defer.fail(UnauthorizedLogin())
  68. else:
  69. if cryptedPass in ('*', 'x'):
  70. # Allow checkSpwd to take over
  71. return None
  72. elif verifyCryptedPassword(cryptedPass, password):
  73. return defer.succeed(username)
  74. def checkSpwd(self, spwd, username, password):
  75. """
  76. Obtain the encrypted password for C{username} from the
  77. Unix shadow password database using L{spwd.getspnam},
  78. and see if it it matches it matches C{password}.
  79. @param spwd: Module which provides functions which
  80. access to the Unix shadow password database.
  81. @type pwd: C{module}
  82. @param username: The user to look up in the Unix password database.
  83. @type username: L{unicode}/L{str} or L{bytes}
  84. @param password: The password to compare.
  85. @type username: L{unicode}/L{str} or L{bytes}
  86. """
  87. try:
  88. if not isinstance(username, StringType):
  89. username = username.decode('utf-8')
  90. if getattr(spwd.struct_spwd, "sp_pwdp", None):
  91. # Python 3
  92. cryptedPass = spwd.getspnam(username).sp_pwdp
  93. else:
  94. # Python 2
  95. cryptedPass = spwd.getspnam(username).sp_pwd
  96. except KeyError:
  97. return defer.fail(UnauthorizedLogin())
  98. else:
  99. if verifyCryptedPassword(cryptedPass, password):
  100. return defer.succeed(username)
  101. def requestAvatarId(self, credentials):
  102. username, password = credentials.username, credentials.password
  103. try:
  104. import pwd
  105. except ImportError:
  106. pwd = None
  107. if pwd is not None:
  108. checked = self.checkPwd(pwd, username, password)
  109. if checked is not None:
  110. return checked
  111. try:
  112. import spwd
  113. except ImportError:
  114. spwd = None
  115. if spwd is not None:
  116. checked = self.checkSpwd(spwd, username, password)
  117. if checked is not None:
  118. return checked
  119. # TODO: check_pam?
  120. # TODO: check_shadow?
  121. return defer.fail(UnauthorizedLogin())
  122. unixCheckerFactoryHelp = """
  123. This checker will attempt to use every resource available to
  124. authenticate against the list of users on the local UNIX system.
  125. (This does not support Windows servers for very obvious reasons.)
  126. Right now, this includes support for:
  127. * Python's pwd module (which checks /etc/passwd)
  128. * Python's spwd module (which checks /etc/shadow)
  129. Future versions may include support for PAM authentication.
  130. """
  131. @implementer(ICheckerFactory, plugin.IPlugin)
  132. class UNIXCheckerFactory(object):
  133. """
  134. A factory for L{UNIXChecker}.
  135. """
  136. authType = 'unix'
  137. authHelp = unixCheckerFactoryHelp
  138. argStringFormat = 'No argstring required.'
  139. credentialInterfaces = UNIXChecker.credentialInterfaces
  140. def generateChecker(self, argstring):
  141. """
  142. This checker factory ignores the argument string. Everything
  143. needed to generate a user database is pulled out of the local
  144. UNIX environment.
  145. """
  146. return UNIXChecker()
  147. theUnixCheckerFactory = UNIXCheckerFactory()