2 __version__ = "Beta 0.6b"
3 from Plugins.Plugin import PluginDescriptor
4 from Components.config import config, ConfigSubsection, ConfigInteger,ConfigYesNo,ConfigText
5 from Components.Network import Network
7 from twisted.internet import reactor, defer
8 from twisted.web2 import server, channel, static, resource, stream, http_headers, responsecode, http
9 from twisted.web2.auth import digest, basic, wrapper
10 #from twisted.python import util
11 from twisted.python.log import startLogging,discardLogs
12 from twisted.cred.portal import Portal, IRealm
13 from twisted.cred import checkers, credentials, error
14 from zope.interface import Interface, implements
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 if config.plugins.Webinterface.enable.value is not True:
50 print "not starting Werbinterface"
52 if config.plugins.Webinterface.debug.value:
53 print "start twisted logfile, writing to %s" % DEBUGFILE
55 startLogging(open(DEBUGFILE,'w'))
57 toplevel = Toplevel(session)
58 if config.plugins.Webinterface.useauth.value is False:
59 site = server.Site(toplevel)
61 portal = Portal(HTTPAuthRealm())
62 portal.registerChecker(PasswordDatabase())
63 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
64 site = server.Site(root)
66 # here we start the Toplevel without any username or password
67 # this allows access to all request over the iface 127.0.0.1 without any auth
68 localsite = server.Site(toplevel)
69 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(localsite),interface='127.0.0.1')
71 # and here we make the Toplevel public to our external ifaces
72 # it depends on the config, if this is with auth support
73 # keep in mind, if we have a second external ip (like a wlan device), we have to do it in the same way for this iface too
75 for adaptername in nw.ifaces:
76 extip = nw.ifaces[adaptername]['ip']
77 if nw.ifaces[adaptername]['up'] is True:
78 extip = "%i.%i.%i.%i"%(extip[0],extip[1],extip[2],extip[3])
79 print "[WebIf] starting Webinterface on port %s on interface %s with address %s"%(str(config.plugins.Webinterface.port.value),adaptername,extip)
81 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site),interface=extip)
83 print "[WebIf] Error starting Webinterface on port %s on interface %s with address %s,because \n%s"%(str(config.plugins.Webinterface.port.value),adaptername,extip,e)
85 print "[WebIf] found configured interface %s, but it is not running. so not starting a server on it ..." % adaptername
88 def autostart(reason, **kwargs):
89 if "session" in kwargs:
91 startWebserver(kwargs["session"])
93 print "[WebIf] twisted not available, not starting web services",e
95 def openconfig(session, **kwargs):
96 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
98 def configCB(result,session):
100 print "[WebIf] config changed"
101 restartWebserver(session)
103 print "[WebIf] config not changed"
106 def Plugins(**kwargs):
107 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
108 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
111 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
113 set it only to True, if you have a patched wrapper.py
114 see http://twistedmatrix.com/trac/ticket/2041
115 so, the solution for us is to make a new class an override ne faulty func
118 def locateChild(self, req, seg):
119 return self.authenticate(req), seg
121 class PasswordDatabase:
123 this checks webiflogins agains /etc/passwd
125 passwordfile = "/etc/passwd"
126 implements(checkers.ICredentialsChecker)
127 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
129 def _cbPasswordMatch(self, matched, username):
133 return failure.Failure(error.UnauthorizedLogin())
135 def requestAvatarId(self, credentials):
136 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
137 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
139 return defer.fail(error.UnauthorizedLogin())
141 class IHTTPUser(Interface):
144 class HTTPUser(object):
145 implements(IHTTPUser)
147 class HTTPAuthRealm(object):
149 def requestAvatar(self, avatarId, mind, *interfaces):
150 if IHTTPUser in interfaces:
151 return IHTTPUser, HTTPUser()
152 raise NotImplementedError("Only IHTTPUser interface is supported")
155 import md5,time,string,crypt
156 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
157 def getpwnam(name, pwfile=None):
158 """Return pasword database entry for the given user name.
160 Example from the Python Library Reference.
164 pwfile = '/etc/passwd'
172 entry = tuple(line.strip().split(':', 6))
177 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
178 """Encrypt a string according to rules in crypt(3)."""
179 if method.lower() == 'des':
180 return crypt.crypt(passwd, salt)
181 elif method.lower() == 'md5':
182 return passcrypt_md5(passwd, salt, magic)
183 elif method.lower() == 'clear':
186 def check_passwd(name, passwd, pwfile=None):
187 """Validate given user, passwd pair against password database."""
189 if not pwfile or type(pwfile) == type(''):
190 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
192 getuser = pwfile.get_passwd
195 enc_passwd = getuser(name)
196 except (KeyError, IOError):
200 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
201 salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
202 return enc_passwd == passcrypt(passwd, salt, 'md5')
205 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
210 r = r + DES_SALT[v & 0x3F]
215 def passcrypt_md5(passwd, salt=None, magic='$1$'):
216 """Encrypt passwd with MD5 algorithm."""
220 elif salt[:len(magic)] == magic:
221 # remove magic from salt if present
222 salt = salt[len(magic):]
224 # salt only goes up to first '$'
225 salt = string.split(salt, '$')[0]
226 # limit length of salt to 8
229 ctx = md5.new(passwd)
233 ctx1 = md5.new(passwd)
237 final = ctx1.digest()
239 for i in range(len(passwd), 0 , -16):
243 ctx.update(final[:i])
250 ctx.update(passwd[:1])
254 for i in range(1000):
260 if i % 3: ctx1.update(salt)
261 if i % 7: ctx1.update(passwd)
266 final = ctx1.digest()
268 rv = magic + salt + '$'
269 final = map(ord, final)
270 l = (final[0] << 16) + (final[6] << 8) + final[12]
271 rv = rv + _to64(l, 4)
272 l = (final[1] << 16) + (final[7] << 8) + final[13]
273 rv = rv + _to64(l, 4)
274 l = (final[2] << 16) + (final[8] << 8) + final[14]
275 rv = rv + _to64(l, 4)
276 l = (final[3] << 16) + (final[9] << 8) + final[15]
277 rv = rv + _to64(l, 4)
278 l = (final[4] << 16) + (final[10] << 8) + final[5]
279 rv = rv + _to64(l, 4)
281 rv = rv + _to64(l, 2)