From 901bb42ba02aa9ae05ec4a32eba059801bb3d37f Mon Sep 17 00:00:00 2001 From: Rico Schulte Date: Sun, 28 Jan 2007 13:55:48 +0000 Subject: [PATCH] major changes for authmethods - dont need to modify the wrapper.py - fix for enter password twice - all browsers (Firefox,Opera,wget and at leat {really least} IE) - all User/Password are checked against /etc/passwd, so your default login is root/yourrootpass - Authentication is enabled by default, can be change in ConfigPlugin --- webinterface/src/WebIfConfig.py | 1 + webinterface/src/plugin.py | 247 ++++++++++++++++++++++++++++++++-------- 2 files changed, 200 insertions(+), 48 deletions(-) diff --git a/webinterface/src/WebIfConfig.py b/webinterface/src/WebIfConfig.py index 635e036..b986bef 100644 --- a/webinterface/src/WebIfConfig.py +++ b/webinterface/src/WebIfConfig.py @@ -17,6 +17,7 @@ class WebIfConfigScreen(ConfigListScreen,Screen): Screen.__init__(self, session) self.list = [] self.list.append(getConfigListEntry(_("start Webinterface"), config.plugins.Webinterface.enable)) + self.list.append(getConfigListEntry(_("use Authorization"), config.plugins.Webinterface.useauth)) self.list.append(getConfigListEntry(_("use Port"), config.plugins.Webinterface.port)) self.list.append(getConfigListEntry(_("enable /hdd"), config.plugins.Webinterface.includehdd)) ConfigListScreen.__init__(self, self.list) diff --git a/webinterface/src/plugin.py b/webinterface/src/plugin.py index a033d84..d8088d5 100644 --- a/webinterface/src/plugin.py +++ b/webinterface/src/plugin.py @@ -15,6 +15,7 @@ config.plugins.Webinterface = ConfigSubsection() config.plugins.Webinterface.enable = ConfigYesNo(default = True) config.plugins.Webinterface.port = ConfigInteger(80,limits = (1, 999)) config.plugins.Webinterface.includehdd = ConfigYesNo(default = False) +config.plugins.Webinterface.useauth = ConfigYesNo(default = True) sessions = [ ] @@ -27,26 +28,18 @@ sessions = [ ] use tail -f to view this log """ -DEBUG = False +DEBUG = True DEBUGFILE= "/tmp/twisted.log" -# Passwordprotection Test -# set it only to True, if you have a patched wrapper.py -# see http://twistedmatrix.com/trac/ticket/2041 -# in /usr/lib/python2.4/site-packages/twisted/web2/auth/wrapper.py -# The solution is to change this line -# -# return self.authenticate(req), seg[1:] -# into this -# return self.authenticate(req), seg -PASSWORDPROTECTION = False -PASSWORDPROTECTION_pwd = "root" -PASSWORDPROTECTION_mode = "sha"; -# twisted supports more than sha ('md5','md5-sess','sha') -# but only sha works for me, but IE -# sha, Firefox=ok, Opera=ok, wget=ok, ie=not ok -# md5-sess, firefox=not ok, opera=not ok,wget=ok, ie=not ok -# md5 same as md5-sess +from twisted.cred.portal import Portal +from twisted.cred import checkers +from twisted.web2.auth import digest, basic, wrapper +from zope.interface import Interface, implements +from twisted.cred import portal +from twisted.cred import credentials, error +from twisted.internet import defer +from zope import interface + def stopWebserver(): reactor.disconnectAll() @@ -86,7 +79,6 @@ def startWebserver(): s=myProducerStream() webif.renderPage(s, self.path, req, sessions[0]) # login? return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('application', 'xhtml+xml', (('charset', 'UTF-8'),))},stream=s) - #return http.Response(responsecode.OK,stream=s) else: return http.Response(responsecode.NOT_FOUND) @@ -99,6 +91,8 @@ def startWebserver(): class Toplevel(resource.Resource): addSlash = True + child_web = ScreenPage(util.sibpath(__file__, "web")) # "/web/*" + child_webdata = static.File(util.sibpath(__file__, "web-data")) # FIXME: web-data appears as webdata def render(self, req): fp = open(util.sibpath(__file__, "web-data")+"/index.html") @@ -106,45 +100,21 @@ def startWebserver(): fp.close() return http.Response(responsecode.OK, {'Content-type': http_headers.MimeType('text', 'html')},stream=s) - child_web = ScreenPage(util.sibpath(__file__, "web")) # "/web/*" - child_webdata = static.File(util.sibpath(__file__, "web-data")) # FIXME: web-data appears as webdata - toplevel = Toplevel() if config.plugins.Webinterface.includehdd.value: toplevel.putChild("hdd",static.File("/hdd")) - if PASSWORDPROTECTION is False: - site = server.Site(toplevel) + if config.plugins.Webinterface.useauth.value is False: + site = server.Site(Toplevel()) else: - from twisted.cred.portal import Portal - from twisted.cred import checkers - from twisted.web2.auth import digest, basic, wrapper - from zope.interface import Interface, implements - from twisted.cred import portal - class IHTTPUser(Interface): - pass - - class HTTPUser(object): - implements(IHTTPUser) - - class HTTPAuthRealm(object): - implements(portal.IRealm) - def requestAvatar(self, avatarId, mind, *interfaces): - if IHTTPUser in interfaces: - return IHTTPUser, HTTPUser() - raise NotImplementedError("Only IHTTPUser interface is supported") - portal = Portal(HTTPAuthRealm()) - checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(root=PASSWORDPROTECTION_pwd) - portal.registerChecker(checker) - root = wrapper.HTTPAuthResource(toplevel, - (basic.BasicCredentialFactory('DM7025'),digest.DigestCredentialFactory(PASSWORDPROTECTION_mode,'DM7025')), - portal, (IHTTPUser,)) + portal.registerChecker(PasswordDatabase()) + root = ModifiedHTTPAuthResource(Toplevel(),(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,)) site = server.Site(root) print "[WebIf] starting Webinterface on port",config.plugins.Webinterface.port.value reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site)) - + def autostart(reason, **kwargs): if "session" in kwargs: global sessions @@ -170,3 +140,184 @@ def configCB(result): def Plugins(**kwargs): return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart), PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)] + + +class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource): + """ + set it only to True, if you have a patched wrapper.py + see http://twistedmatrix.com/trac/ticket/2041 + so, the solution for us is to make a new class an override ne faulty func + """ + + def locateChild(self, req, seg): + return self.authenticate(req), seg + +class PasswordDatabase: + """ + this checks webiflogins agains /etc/passwd + """ + passwordfile = "/etc/passwd" + interface.implements(checkers.ICredentialsChecker) + credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword) + + def _cbPasswordMatch(self, matched, username): + if matched: + return username + else: + return failure.Failure(error.UnauthorizedLogin()) + + def requestAvatarId(self, credentials): + try: + if check_passwd(credentials.username,credentials.password,self.passwordfile) is True: + return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username)) + else: + return defer.fail(error.UnauthorizedLogin()) + except Exception,e: + print e + +class IHTTPUser(Interface): + pass + +class HTTPUser(object): + implements(IHTTPUser) + +class HTTPAuthRealm(object): + implements(portal.IRealm) + def requestAvatar(self, avatarId, mind, *interfaces): + if IHTTPUser in interfaces: + return IHTTPUser, HTTPUser() + raise NotImplementedError("Only IHTTPUser interface is supported") + + +import md5,time,string,crypt +DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz') +def getpwnam(name, pwfile=None): + """Return pasword database entry for the given user name. + + Example from the Python Library Reference. + """ + + if not pwfile: + pwfile = '/etc/passwd' + + f = open(pwfile) + while 1: + line = f.readline() + if not line: + f.close() + raise KeyError, name + entry = tuple(line.strip().split(':', 6)) + if entry[0] == name: + f.close() + return entry + +def passcrypt(passwd, salt=None, method='des', magic='$1$'): + """Encrypt a string according to rules in crypt(3).""" + if method.lower() == 'des': + if not salt: + salt = str(whrandom.choice(DES_SALT)) + str(whrandom.choice(DES_SALT)) + return crypt.crypt(passwd, salt) + elif method.lower() == 'md5': + return passcrypt_md5(passwd, salt, magic) + elif method.lower() == 'clear': + return passwd + +def check_passwd(name, passwd, pwfile=None): + """Validate given user, passwd pair against password database.""" + + if not pwfile or type(pwfile) == type(''): + getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1] + else: + getuser = pwfile.get_passwd + + try: + enc_passwd = getuser(name) + except (KeyError, IOError): + return 0 + if not enc_passwd: + return 0 + elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$': + salt = enc_passwd[3:string.find(enc_passwd, '$', 3)] + return enc_passwd == passcrypt(passwd, salt=salt, method='md5') + else: + return enc_passwd == passcrypt(passwd, enc_passwd[:2]) + +def _to64(v, n): + r = '' + while (n-1 >= 0): + r = r + DES_SALT[v & 0x3F] + v = v >> 6 + n = n - 1 + return r + +def passcrypt_md5(passwd, salt=None, magic='$1$'): + """Encrypt passwd with MD5 algorithm.""" + + if not salt: + salt = repr(int(time.time()))[-8:] + elif salt[:len(magic)] == magic: + # remove magic from salt if present + salt = salt[len(magic):] + + # salt only goes up to first '$' + salt = string.split(salt, '$')[0] + # limit length of salt to 8 + salt = salt[:8] + + ctx = md5.new(passwd) + ctx.update(magic) + ctx.update(salt) + + ctx1 = md5.new(passwd) + ctx1.update(salt) + ctx1.update(passwd) + + final = ctx1.digest() + + for i in range(len(passwd), 0 , -16): + if i > 16: + ctx.update(final) + else: + ctx.update(final[:i]) + + i = len(passwd) + while i: + if i & 1: + ctx.update('\0') + else: + ctx.update(passwd[:1]) + i = i >> 1 + final = ctx.digest() + + for i in range(1000): + ctx1 = md5.new() + if i & 1: + ctx1.update(passwd) + else: + ctx1.update(final) + if i % 3: ctx1.update(salt) + if i % 7: ctx1.update(passwd) + if i & 1: + ctx1.update(final) + else: + ctx1.update(passwd) + final = ctx1.digest() + + rv = magic + salt + '$' + final = map(ord, final) + l = (final[0] << 16) + (final[6] << 8) + final[12] + rv = rv + _to64(l, 4) + l = (final[1] << 16) + (final[7] << 8) + final[13] + rv = rv + _to64(l, 4) + l = (final[2] << 16) + (final[8] << 8) + final[14] + rv = rv + _to64(l, 4) + l = (final[3] << 16) + (final[9] << 8) + final[15] + rv = rv + _to64(l, 4) + l = (final[4] << 16) + (final[10] << 8) + final[5] + rv = rv + _to64(l, 4) + l = final[11] + rv = rv + _to64(l, 2) + + return rv + + -- 2.7.4