1 from Plugins.Plugin import PluginDescriptor
3 from twisted.internet import reactor
4 from twisted.web2 import server, channel, static, resource, stream, http_headers, responsecode, http
5 from twisted.python import util
6 from twisted.python.log import startLogging,discardLogs
12 from Components.config import config, ConfigSubsection, ConfigInteger,ConfigYesNo
14 config.plugins.Webinterface = ConfigSubsection()
15 config.plugins.Webinterface.enable = ConfigYesNo(default = True)
16 config.plugins.Webinterface.port = ConfigInteger(80,limits = (1, 999))
17 config.plugins.Webinterface.includehdd = ConfigYesNo(default = False)
18 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
23 define all files in /web to send no XML-HTTP-Headers here
24 all files not listed here will get an Content-Type: application/xhtml+xml charset: UTF-8
26 AppTextHeaderFiles = ['stream.m3u.xml',]
29 define all files in /web to send no XML-HTTP-Headers here
30 all files not listed here will get an Content-Type: text/html charset: UTF-8
32 NoExplicitHeaderFiles = ['getpid.xml','tvbrowser.xml',]
35 set DEBUG to True, if twisted should write logoutput to a file.
36 in normal console output, twisted will print only the first Traceback.
37 is this a bug in twisted or a conflict with enigma2?
38 with this option enabled, twisted will print all TB to the logfile
39 use tail -f <file> to view this log
44 DEBUGFILE= "/tmp/twisted.log"
46 from twisted.cred.portal import Portal
47 from twisted.cred import checkers
48 from twisted.web2.auth import digest, basic, wrapper
49 from zope.interface import Interface, implements
50 from twisted.cred import portal
51 from twisted.cred import credentials, error
52 from twisted.internet import defer
53 from zope import interface
57 reactor.disconnectAll()
59 def restartWebserver():
64 if config.plugins.Webinterface.enable.value is not True:
65 print "not starting Werbinterface"
68 print "start twisted logfile, writing to %s" % DEBUGFILE
70 startLogging(open(DEBUGFILE,'w'))
72 class ScreenPage(resource.Resource):
73 def __init__(self, path):
76 def render(self, req):
79 return http.Response(responsecode.OK, stream="please wait until enigma has booted")
81 class myProducerStream(stream.ProducerStream):
83 stream.ProducerStream.__init__(self)
84 self.closed_callback = None
87 if self.closed_callback:
88 self.closed_callback()
89 self.closed_callback = None
90 stream.ProducerStream.close(self)
92 if os.path.isfile(self.path):
94 webif.renderPage(s, self.path, req, sessions[0]) # login?
95 if self.path.split("/")[-1] in AppTextHeaderFiles:
96 return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('application', 'text', (('charset', 'UTF-8'),))},stream=s)
97 elif self.path.split("/")[-1] in NoExplicitHeaderFiles:
98 return http.Response(responsecode.OK,stream=s)
100 return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('application', 'xhtml+xml', (('charset', 'UTF-8'),))},stream=s)
102 return http.Response(responsecode.NOT_FOUND)
104 def locateChild(self, request, segments):
105 path = self.path+'/'+'/'.join(segments)
109 return ScreenPage(path), ()
111 class Toplevel(resource.Resource):
113 child_web = ScreenPage(util.sibpath(__file__, "web")) # "/web/*"
114 child_webdata = static.File(util.sibpath(__file__, "web-data")) # FIXME: web-data appears as webdata
116 def render(self, req):
117 fp = open(util.sibpath(__file__, "web-data")+"/index.html")
120 return http.Response(responsecode.OK, {'Content-type': http_headers.MimeType('text', 'html')},stream=s)
122 toplevel = Toplevel()
123 if config.plugins.Webinterface.includehdd.value:
124 toplevel.putChild("hdd",static.File("/hdd"))
126 if config.plugins.Webinterface.useauth.value is False:
127 site = server.Site(toplevel)
129 portal = Portal(HTTPAuthRealm())
130 portal.registerChecker(PasswordDatabase())
131 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
132 site = server.Site(root)
133 print "[WebIf] starting Webinterface on port",config.plugins.Webinterface.port.value
134 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site))
137 def autostart(reason, **kwargs):
138 if "session" in kwargs:
140 sessions.append(kwargs["session"])
145 except ImportError,e:
146 print "[WebIf] twisted not available, not starting web services",e
148 def openconfig(session, **kwargs):
149 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
151 def configCB(result):
153 print "[WebIf] config changed"
156 print "[WebIf] config not changed"
159 def Plugins(**kwargs):
160 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
161 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
164 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
166 set it only to True, if you have a patched wrapper.py
167 see http://twistedmatrix.com/trac/ticket/2041
168 so, the solution for us is to make a new class an override ne faulty func
171 def locateChild(self, req, seg):
172 return self.authenticate(req), seg
174 class PasswordDatabase:
176 this checks webiflogins agains /etc/passwd
178 passwordfile = "/etc/passwd"
179 interface.implements(checkers.ICredentialsChecker)
180 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
182 def _cbPasswordMatch(self, matched, username):
186 return failure.Failure(error.UnauthorizedLogin())
188 def requestAvatarId(self, credentials):
189 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
190 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
192 return defer.fail(error.UnauthorizedLogin())
194 class IHTTPUser(Interface):
197 class HTTPUser(object):
198 implements(IHTTPUser)
200 class HTTPAuthRealm(object):
201 implements(portal.IRealm)
202 def requestAvatar(self, avatarId, mind, *interfaces):
203 if IHTTPUser in interfaces:
204 return IHTTPUser, HTTPUser()
205 raise NotImplementedError("Only IHTTPUser interface is supported")
208 import md5,time,string,crypt
209 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
210 def getpwnam(name, pwfile=None):
211 """Return pasword database entry for the given user name.
213 Example from the Python Library Reference.
217 pwfile = '/etc/passwd'
225 entry = tuple(line.strip().split(':', 6))
230 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
231 """Encrypt a string according to rules in crypt(3)."""
232 if method.lower() == 'des':
233 return crypt.crypt(passwd, salt)
234 elif method.lower() == 'md5':
235 return passcrypt_md5(passwd, salt, magic)
236 elif method.lower() == 'clear':
239 def check_passwd(name, passwd, pwfile=None):
240 """Validate given user, passwd pair against password database."""
242 if not pwfile or type(pwfile) == type(''):
243 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
245 getuser = pwfile.get_passwd
248 enc_passwd = getuser(name)
249 except (KeyError, IOError):
253 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
254 salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
255 return enc_passwd == passcrypt(passwd, salt, 'md5')
258 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
263 r = r + DES_SALT[v & 0x3F]
268 def passcrypt_md5(passwd, salt=None, magic='$1$'):
269 """Encrypt passwd with MD5 algorithm."""
273 elif salt[:len(magic)] == magic:
274 # remove magic from salt if present
275 salt = salt[len(magic):]
277 # salt only goes up to first '$'
278 salt = string.split(salt, '$')[0]
279 # limit length of salt to 8
282 ctx = md5.new(passwd)
286 ctx1 = md5.new(passwd)
290 final = ctx1.digest()
292 for i in range(len(passwd), 0 , -16):
296 ctx.update(final[:i])
303 ctx.update(passwd[:1])
307 for i in range(1000):
313 if i % 3: ctx1.update(salt)
314 if i % 7: ctx1.update(passwd)
319 final = ctx1.digest()
321 rv = magic + salt + '$'
322 final = map(ord, final)
323 l = (final[0] << 16) + (final[6] << 8) + final[12]
324 rv = rv + _to64(l, 4)
325 l = (final[1] << 16) + (final[7] << 8) + final[13]
326 rv = rv + _to64(l, 4)
327 l = (final[2] << 16) + (final[8] << 8) + final[14]
328 rv = rv + _to64(l, 4)
329 l = (final[3] << 16) + (final[9] << 8) + final[15]
330 rv = rv + _to64(l, 4)
331 l = (final[4] << 16) + (final[10] << 8) + final[5]
332 rv = rv + _to64(l, 4)
334 rv = rv + _to64(l, 2)