2 __version__ = "Beta 0.6a"
3 from Plugins.Plugin import PluginDescriptor
4 from Components.config import config, ConfigSubsection, ConfigInteger,ConfigYesNo,ConfigText
6 from twisted.internet import reactor, defer
7 from twisted.web2 import server, channel, static, resource, stream, http_headers, responsecode, http
8 from twisted.web2.auth import digest, basic, wrapper
9 #from twisted.python import util
10 from twisted.python.log import startLogging,discardLogs
11 from twisted.cred.portal import Portal, IRealm
12 from twisted.cred import checkers, credentials, error
13 from zope.interface import Interface, implements
18 #print "WEEE"*20,__module__
20 from WebChilds.Toplevel import Toplevel
21 config.plugins.Webinterface = ConfigSubsection()
22 config.plugins.Webinterface.enable = ConfigYesNo(default = True)
23 config.plugins.Webinterface.port = ConfigInteger(80,limits = (1, 65536))
24 config.plugins.Webinterface.includehdd = ConfigYesNo(default = False)
25 config.plugins.Webinterface.useauth = ConfigYesNo(default = False) # False, because a std. images hasnt a rootpasswd set and so no login. and a login with a empty pwd makes no sense
26 config.plugins.Webinterface.debug = ConfigYesNo(default = False) # False by default, not confgurable in GUI. Edit settingsfile directly if needed
27 config.plugins.Webinterface.version = ConfigText(__version__) # used to make the versioninfo accessible enigma2-wide, not confgurable in GUI.
31 set DEBUG to True, if twisted should write logoutput to a file.
32 in normal console output, twisted will print only the first Traceback.
33 is this a bug in twisted or a conflict with enigma2?
34 with this option enabled, twisted will print all TB to the logfile
35 use tail -f <file> to view this log
39 DEBUGFILE= "/tmp/twisted.log"
42 reactor.disconnectAll()
44 def restartWebserver(session):
46 startWebserver(session)
48 def startWebserver(session):
49 print "SESSION"*10,session
50 if config.plugins.Webinterface.enable.value is not True:
51 print "not starting Werbinterface"
53 if config.plugins.Webinterface.debug.value:
54 print "start twisted logfile, writing to %s" % DEBUGFILE
56 startLogging(open(DEBUGFILE,'w'))
58 toplevel = Toplevel(session)
59 if config.plugins.Webinterface.useauth.value is False:
60 site = server.Site(toplevel)
62 portal = Portal(HTTPAuthRealm())
63 portal.registerChecker(PasswordDatabase())
64 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
65 site = server.Site(root)
66 print "[WebIf] starting Webinterface on port",config.plugins.Webinterface.port.value
67 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site))
71 def autostart(reason, **kwargs):
72 if "session" in kwargs:
74 startWebserver(kwargs["session"])
76 print "[WebIf] twisted not available, not starting web services",e
78 def openconfig(session, **kwargs):
79 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
81 def configCB(result,session):
83 print "[WebIf] config changed"
84 restartWebserver(session)
86 print "[WebIf] config not changed"
89 def Plugins(**kwargs):
90 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
91 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
94 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
96 set it only to True, if you have a patched wrapper.py
97 see http://twistedmatrix.com/trac/ticket/2041
98 so, the solution for us is to make a new class an override ne faulty func
101 def locateChild(self, req, seg):
102 return self.authenticate(req), seg
104 class PasswordDatabase:
106 this checks webiflogins agains /etc/passwd
108 passwordfile = "/etc/passwd"
109 implements(checkers.ICredentialsChecker)
110 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
112 def _cbPasswordMatch(self, matched, username):
116 return failure.Failure(error.UnauthorizedLogin())
118 def requestAvatarId(self, credentials):
119 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
120 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
122 return defer.fail(error.UnauthorizedLogin())
124 class IHTTPUser(Interface):
127 class HTTPUser(object):
128 implements(IHTTPUser)
130 class HTTPAuthRealm(object):
132 def requestAvatar(self, avatarId, mind, *interfaces):
133 if IHTTPUser in interfaces:
134 return IHTTPUser, HTTPUser()
135 raise NotImplementedError("Only IHTTPUser interface is supported")
138 import md5,time,string,crypt
139 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
140 def getpwnam(name, pwfile=None):
141 """Return pasword database entry for the given user name.
143 Example from the Python Library Reference.
147 pwfile = '/etc/passwd'
155 entry = tuple(line.strip().split(':', 6))
160 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
161 """Encrypt a string according to rules in crypt(3)."""
162 if method.lower() == 'des':
163 return crypt.crypt(passwd, salt)
164 elif method.lower() == 'md5':
165 return passcrypt_md5(passwd, salt, magic)
166 elif method.lower() == 'clear':
169 def check_passwd(name, passwd, pwfile=None):
170 """Validate given user, passwd pair against password database."""
172 if not pwfile or type(pwfile) == type(''):
173 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
175 getuser = pwfile.get_passwd
178 enc_passwd = getuser(name)
179 except (KeyError, IOError):
183 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
184 salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
185 return enc_passwd == passcrypt(passwd, salt, 'md5')
188 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
193 r = r + DES_SALT[v & 0x3F]
198 def passcrypt_md5(passwd, salt=None, magic='$1$'):
199 """Encrypt passwd with MD5 algorithm."""
203 elif salt[:len(magic)] == magic:
204 # remove magic from salt if present
205 salt = salt[len(magic):]
207 # salt only goes up to first '$'
208 salt = string.split(salt, '$')[0]
209 # limit length of salt to 8
212 ctx = md5.new(passwd)
216 ctx1 = md5.new(passwd)
220 final = ctx1.digest()
222 for i in range(len(passwd), 0 , -16):
226 ctx.update(final[:i])
233 ctx.update(passwd[:1])
237 for i in range(1000):
243 if i % 3: ctx1.update(salt)
244 if i % 7: ctx1.update(passwd)
249 final = ctx1.digest()
251 rv = magic + salt + '$'
252 final = map(ord, final)
253 l = (final[0] << 16) + (final[6] << 8) + final[12]
254 rv = rv + _to64(l, 4)
255 l = (final[1] << 16) + (final[7] << 8) + final[13]
256 rv = rv + _to64(l, 4)
257 l = (final[2] << 16) + (final[8] << 8) + final[14]
258 rv = rv + _to64(l, 4)
259 l = (final[3] << 16) + (final[9] << 8) + final[15]
260 rv = rv + _to64(l, 4)
261 l = (final[4] << 16) + (final[10] << 8) + final[5]
262 rv = rv + _to64(l, 4)
264 rv = rv + _to64(l, 2)