It seems we don't have the memory issue anymore so I removed at least the twisted...
[vuplus_dvbapp-plugin] / webinterface / src / plugin.py
1 from Plugins.Plugin import PluginDescriptor
2
3 from twisted.internet import reactor, defer
4
5 from twisted.web2 import server, channel, static, resource, stream, http_headers, responsecode, http
6 from twisted.web2.auth import digest, basic, wrapper
7
8 from twisted.python import util
9 from twisted.python.log import startLogging,discardLogs
10
11 from twisted.cred.portal import Portal, IRealm
12 from twisted.cred import checkers, credentials, error
13
14 from zope.interface import Interface, implements
15
16 import webif
17 import WebIfConfig  
18 import os
19
20 from Components.config import config, ConfigSubsection, ConfigInteger,ConfigYesNo
21
22 config.plugins.Webinterface = ConfigSubsection()
23 config.plugins.Webinterface.enable = ConfigYesNo(default = True)
24 config.plugins.Webinterface.port = ConfigInteger(80,limits = (1, 65536))
25 config.plugins.Webinterface.includehdd = ConfigYesNo(default = False)
26 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
27
28 sessions = [ ]
29
30 """
31         define all files in /web to send no  XML-HTTP-Headers here
32         all files not listed here will get an Content-Type: application/xhtml+xml charset: UTF-8
33 """
34 AppTextHeaderFiles = ['stream.m3u.xml','ts.m3u.xml','wapwrapper.xml',] 
35
36 """
37  Actualle, the TextHtmlHeaderFiles should contain the updates.html.xml, but the IE then
38  has problems with unicode-characters
39 """
40 TextHtmlHeaderFiles = [] 
41
42 """
43         define all files in /web to send no  XML-HTTP-Headers here
44         all files not listed here will get an Content-Type: text/html charset: UTF-8
45 """
46 NoExplicitHeaderFiles = ['getpid.xml','tvbrowser.xml',] 
47
48 """
49  set DEBUG to True, if twisted should write logoutput to a file.
50  in normal console output, twisted will print only the first Traceback.
51  is this a bug in twisted or a conflict with enigma2?
52  with this option enabled, twisted will print all TB to the logfile
53  use tail -f <file> to view this log
54 """
55                         
56
57 DEBUG = True
58 DEBUG = False
59 DEBUGFILE= "/tmp/twisted.log"
60
61 def stopWebserver():
62         reactor.disconnectAll()
63
64 def restartWebserver():
65         stopWebserver()
66         startWebserver()
67
68 def startWebserver():
69         if config.plugins.Webinterface.enable.value is not True:
70                 print "not starting Werbinterface"
71                 return False
72         if DEBUG:
73                 print "start twisted logfile, writing to %s" % DEBUGFILE 
74                 import sys
75                 startLogging(open(DEBUGFILE,'w'))
76
77         class ScreenPage(resource.Resource):
78                 def __init__(self, path):
79                         self.path = path
80
81                 def render(self, req):
82                         global sessions
83                         if sessions == [ ]:
84                                 return http.Response(responsecode.OK, stream="please wait until enigma has booted")
85
86                         class myProducerStream(stream.ProducerStream):
87                                 def __init__(self):
88                                         stream.ProducerStream.__init__(self)
89                                         self.closed_callback = None
90
91                                 def close(self):
92                                         if self.closed_callback:
93                                                 self.closed_callback()
94                                                 self.closed_callback = None
95                                         stream.ProducerStream.close(self)
96
97                         if os.path.isfile(self.path):
98                                 s=myProducerStream()
99                                 webif.renderPage(s, self.path, req, sessions[0])  # login?
100                                 if self.path.split("/")[-1] in AppTextHeaderFiles:
101                                         return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('application', 'text', (('charset', 'UTF-8'),))},stream=s)
102                                 elif self.path.split("/")[-1] in TextHtmlHeaderFiles:
103                                         return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('text', 'html', (('charset', 'UTF-8'),))},stream=s)
104                                 elif self.path.split("/")[-1] in NoExplicitHeaderFiles:
105                                         return http.Response(responsecode.OK,stream=s)
106                                 else:
107                                         return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('application', 'xhtml+xml', (('charset', 'UTF-8'),))},stream=s)
108                         else:
109                                 return http.Response(responsecode.NOT_FOUND)
110
111                 def locateChild(self, request, segments):
112                         path = self.path+'/'+'/'.join(segments)
113                         if path[-1:] == "/":
114                                 path += "index.html"
115                         path +=".xml"
116                         return ScreenPage(path), ()
117
118         class Toplevel(resource.Resource):
119                 addSlash = True
120                 child_web = ScreenPage(util.sibpath(__file__, "web")) # "/web/*"
121                 child_webdata = static.File(util.sibpath(__file__, "web-data")) # FIXME: web-data appears as webdata
122                 child_wap = static.File(util.sibpath(__file__, "wap")) # static pages for wap
123
124                 def render(self, req):
125                         fp = open(util.sibpath(__file__, "web-data")+"/index.html")
126                         s = fp.read()
127                         fp.close()
128                         return http.Response(responsecode.OK, {'Content-type': http_headers.MimeType('text', 'html')},stream=s)
129
130         toplevel = Toplevel()
131         if config.plugins.Webinterface.includehdd.value:
132                 toplevel.putChild("hdd",static.File("/hdd"))
133         
134         if config.plugins.Webinterface.useauth.value is False:
135                 site = server.Site(toplevel)
136         else:
137                 portal = Portal(HTTPAuthRealm())
138                 portal.registerChecker(PasswordDatabase())
139                 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
140                 site = server.Site(root)
141         print "[WebIf] starting Webinterface on port",config.plugins.Webinterface.port.value
142         reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site))
143
144
145 def autostart(reason, **kwargs):
146         if "session" in kwargs:
147                 global sessions
148                 sessions.append(kwargs["session"])
149                 return
150         if reason == 0:
151                 try:
152                         startWebserver()
153                 except ImportError,e:
154                         print "[WebIf] twisted not available, not starting web services",e
155                         
156 def openconfig(session, **kwargs):
157         session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
158
159 def configCB(result):
160         if result is True:
161                 print "[WebIf] config changed"
162                 restartWebserver()
163         else:
164                 print "[WebIf] config not changed"
165                 
166
167 def Plugins(**kwargs):
168         return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
169                     PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
170         
171         
172 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
173         """
174                 set it only to True, if you have a patched wrapper.py
175                 see http://twistedmatrix.com/trac/ticket/2041
176                 so, the solution for us is to make a new class an override ne faulty func
177         """
178
179         def locateChild(self, req, seg):
180                 return self.authenticate(req), seg
181         
182 class PasswordDatabase:
183     """
184         this checks webiflogins agains /etc/passwd
185     """
186     passwordfile = "/etc/passwd"
187     implements(checkers.ICredentialsChecker)
188     credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
189
190     def _cbPasswordMatch(self, matched, username):
191         if matched:
192             return username
193         else:
194             return failure.Failure(error.UnauthorizedLogin())
195
196     def requestAvatarId(self, credentials):     
197         if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
198                 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
199         else:
200                 return defer.fail(error.UnauthorizedLogin())
201
202 class IHTTPUser(Interface):
203         pass
204
205 class HTTPUser(object):
206         implements(IHTTPUser)
207
208 class HTTPAuthRealm(object):
209         implements(IRealm)
210         def requestAvatar(self, avatarId, mind, *interfaces):
211                 if IHTTPUser in interfaces:
212                         return IHTTPUser, HTTPUser()
213                 raise NotImplementedError("Only IHTTPUser interface is supported")
214
215         
216 import md5,time,string,crypt
217 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz') 
218 def getpwnam(name, pwfile=None):
219     """Return pasword database entry for the given user name.
220     
221     Example from the Python Library Reference.
222     """
223     
224     if not pwfile:
225         pwfile = '/etc/passwd'
226
227     f = open(pwfile)
228     while 1:
229         line = f.readline()
230         if not line:
231             f.close()
232             raise KeyError, name
233         entry = tuple(line.strip().split(':', 6))
234         if entry[0] == name:
235             f.close()
236             return entry
237
238 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
239     """Encrypt a string according to rules in crypt(3)."""
240     if method.lower() == 'des':
241             return crypt.crypt(passwd, salt)
242     elif method.lower() == 'md5':
243         return passcrypt_md5(passwd, salt, magic)
244     elif method.lower() == 'clear':
245         return passwd
246
247 def check_passwd(name, passwd, pwfile=None):
248     """Validate given user, passwd pair against password database."""
249     
250     if not pwfile or type(pwfile) == type(''):
251         getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
252     else:
253         getuser = pwfile.get_passwd
254
255     try:
256         enc_passwd = getuser(name)
257     except (KeyError, IOError):
258         return 0
259     if not enc_passwd:
260         return 0
261     elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
262         salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
263         return enc_passwd == passcrypt(passwd, salt, 'md5')
264        
265     else:
266         return enc_passwd == passcrypt(passwd, enc_passwd[:2])
267
268 def _to64(v, n):
269     r = ''
270     while (n-1 >= 0):
271         r = r + DES_SALT[v & 0x3F]
272         v = v >> 6
273         n = n - 1
274     return r
275                         
276 def passcrypt_md5(passwd, salt=None, magic='$1$'):
277     """Encrypt passwd with MD5 algorithm."""
278     
279     if not salt:
280         pass
281     elif salt[:len(magic)] == magic:
282         # remove magic from salt if present
283         salt = salt[len(magic):]
284
285     # salt only goes up to first '$'
286     salt = string.split(salt, '$')[0]
287     # limit length of salt to 8
288     salt = salt[:8]
289
290     ctx = md5.new(passwd)
291     ctx.update(magic)
292     ctx.update(salt)
293     
294     ctx1 = md5.new(passwd)
295     ctx1.update(salt)
296     ctx1.update(passwd)
297     
298     final = ctx1.digest()
299     
300     for i in range(len(passwd), 0 , -16):
301         if i > 16:
302             ctx.update(final)
303         else:
304             ctx.update(final[:i])
305     
306     i = len(passwd)
307     while i:
308         if i & 1:
309             ctx.update('\0')
310         else:
311             ctx.update(passwd[:1])
312         i = i >> 1
313     final = ctx.digest()
314     
315     for i in range(1000):
316         ctx1 = md5.new()
317         if i & 1:
318             ctx1.update(passwd)
319         else:
320             ctx1.update(final)
321         if i % 3: ctx1.update(salt)
322         if i % 7: ctx1.update(passwd)
323         if i & 1:
324             ctx1.update(final)
325         else:
326             ctx1.update(passwd)
327         final = ctx1.digest()
328     
329     rv = magic + salt + '$'
330     final = map(ord, final)
331     l = (final[0] << 16) + (final[6] << 8) + final[12]
332     rv = rv + _to64(l, 4)
333     l = (final[1] << 16) + (final[7] << 8) + final[13]
334     rv = rv + _to64(l, 4)
335     l = (final[2] << 16) + (final[8] << 8) + final[14]
336     rv = rv + _to64(l, 4)
337     l = (final[3] << 16) + (final[9] << 8) + final[15]
338     rv = rv + _to64(l, 4)
339     l = (final[4] << 16) + (final[10] << 8) + final[5]
340     rv = rv + _to64(l, 4)
341     l = final[11]
342     rv = rv + _to64(l, 2)
343     
344     return rv
345
346