source: ipk/source.sh4/swapnetwork_webif/var/swap/extensions/WebInterface/plugin.py@ 14969

Last change on this file since 14969 was 14969, checked in by obi, 14 years ago

fix

File size: 18.1 KB
Line 
1Version = '$Header$';
2
3from enigma import eConsoleAppContainer
4from Plugins.Plugin import PluginDescriptor
5
6from Components.config import config, ConfigBoolean, ConfigSubsection, ConfigInteger, ConfigYesNo, ConfigText
7from Components.Network import iNetwork
8from Screens.MessageBox import MessageBox
9from WebIfConfig import WebIfConfigScreen
10from WebChilds.Toplevel import getToplevel
11
12from Tools.Directories import copyfile, resolveFilename, SCOPE_PLUGINS, SCOPE_CONFIG
13
14#from twisted.internet import reactor, ssl
15from twisted.internet import reactor
16from twisted.web import server, http, util, static, resource
17
18from zope.interface import Interface, implements
19from socket import gethostname as socket_gethostname
20#from OpenSSL import SSL
21
22from os.path import isfile as os_isfile
23
24
25
26from __init__ import _, __version__
27
28#CONFIG INIT
29
30#init the config
31config.plugins.Webinterface = ConfigSubsection()
32config.plugins.Webinterface.enabled = ConfigYesNo(default=True)
33config.plugins.Webinterface.allowzapping = ConfigYesNo(default=True)
34config.plugins.Webinterface.includemedia = ConfigYesNo(default=False)
35config.plugins.Webinterface.autowritetimer = ConfigYesNo(default=False)
36config.plugins.Webinterface.loadmovielength = ConfigYesNo(default=True)
37config.plugins.Webinterface.version = ConfigText(__version__) # used to make the versioninfo accessible enigma2-wide, not confgurable in GUI.
38
39config.plugins.Webinterface.http = ConfigSubsection()
40config.plugins.Webinterface.http.enabled = ConfigYesNo(default=True)
41config.plugins.Webinterface.http.port = ConfigInteger(default = 80, limits=(1, 65535) )
42config.plugins.Webinterface.http.auth = ConfigYesNo(default=False)
43
44config.plugins.Webinterface.https = ConfigSubsection()
45config.plugins.Webinterface.https.enabled = ConfigYesNo(default=False)
46config.plugins.Webinterface.https.port = ConfigInteger(default = 443, limits=(1, 65535) )
47config.plugins.Webinterface.https.auth = ConfigYesNo(default=False)
48
49config.plugins.Webinterface.streamauth = ConfigYesNo(default=False)
50
51global running_defered, waiting_shutdown, toplevel
52
53running_defered = []
54waiting_shutdown = 0
55toplevel = None
56server.VERSION = "Enigma2 WebInterface Server $Revision$".replace("$Revi", "").replace("sion: ", "").replace("$", "")
57
58#===============================================================================
59# Helperclass to close running Instances of the Webinterface
60#===============================================================================
61class Closer:
62 counter = 0
63 def __init__(self, session, callback=None):
64 self.callback = callback
65 self.session = session
66
67#===============================================================================
68# Closes all running Instances of the Webinterface
69#===============================================================================
70 def stop(self):
71 global running_defered
72 for d in running_defered:
73 print "[Webinterface] stopping interface on ", d.interface, " with port", d.port
74 x = d.stopListening()
75
76 try:
77 x.addCallback(self.isDown)
78 self.counter += 1
79 except AttributeError:
80 pass
81 running_defered = []
82 if self.counter < 1:
83 if self.callback is not None:
84 self.callback(self.session)
85
86#===============================================================================
87# #Is it already down?
88#===============================================================================
89 def isDown(self, s):
90 self.counter -= 1
91 if self.counter < 1:
92 if self.callback is not None:
93 self.callback(self.session)
94
95def checkCertificates():
96 print "[WebInterface] checking for SSL Certificates"
97 srvcert = '%sserver.pem' %resolveFilename(SCOPE_CONFIG)
98 cacert = '%scacert.pem' %resolveFilename(SCOPE_CONFIG)
99
100 # Check whether there are regular certificates, if not copy the default ones over
101 if not os_isfile(srvcert) or not os_isfile(cacert):
102 return False
103
104 else:
105 return True
106
107def installCertificates(session, callback = None):
108 print "[WebInterface] Installing SSL Certificates to %s" %resolveFilename(SCOPE_CONFIG)
109
110 srvcert = '%sserver.pem' %resolveFilename(SCOPE_CONFIG)
111 cacert = '%scacert.pem' %resolveFilename(SCOPE_CONFIG)
112 scope_webif = '%sExtensions/WebInterface/' %resolveFilename(SCOPE_PLUGINS)
113
114 source = '%setc/server.pem' %scope_webif
115 target = srvcert
116 ret = copyfile(source, target)
117
118 if ret == 0:
119 source = '%setc/cacert.pem' %scope_webif
120 target = cacert
121 ret = copyfile(source, target)
122
123 if ret == 0 and callback != None:
124 callback(session)
125
126 if ret < 0:
127 config.plugins.Webinterface.https.enabled.value = False
128 config.plugins.Webinterface.https.enabled.save()
129
130 # Start without https
131 callback(session)
132
133 #Inform the user
134 session.open(MessageBox, "Couldn't install SSL-Certifactes for https access\nHttps access is now disabled!", MessageBox.TYPE_ERROR)
135
136#===============================================================================
137# restart the Webinterface for all configured Interfaces
138#===============================================================================
139def restartWebserver(session):
140 try:
141 del session.mediaplayer
142 del session.messageboxanswer
143 except NameError:
144 pass
145 except AttributeError:
146 pass
147
148 global running_defered
149 if len(running_defered) > 0:
150 Closer(session, startWebserver).stop()
151 else:
152 startWebserver(session)
153
154#===============================================================================
155# start the Webinterface for all configured Interfaces
156#===============================================================================
157def startWebserver(session):
158 global running_defered
159 global toplevel
160
161 # Exception handling added by Topfi: Otherwise you get GS when changing Network Settings
162 try:
163 session.mediaplayer = None
164 session.messageboxanswer = None
165 except NameError:
166 pass
167 except AttributeError:
168 pass
169
170 if toplevel is None:
171 toplevel = getToplevel(session)
172
173 errors = ""
174
175 if config.plugins.Webinterface.enabled.value is not True:
176 print "[Webinterface] is disabled!"
177
178 else:
179 # IF SSL is enabled we need to check for the certs first
180 # If they're not there we'll exit via return here
181 # and get called after Certificates are installed properly
182 if config.plugins.Webinterface.https.enabled.value:
183 if not checkCertificates():
184 print "[Webinterface] Installing Webserver Certificates for SSL encryption"
185 installCertificates(session, startWebserver)
186 return
187
188 for adaptername in iNetwork.ifaces:
189 ip = '.'.join("%d" % d for d in iNetwork.ifaces[adaptername]['ip'])
190
191 #Only if it's up and has a "good" IP
192 if ip != '0.0.0.0' and iNetwork.ifaces[adaptername]['up'] == True:
193 #HTTP
194 if config.plugins.Webinterface.http.enabled.value is True:
195 ret = startServerInstance(session, ip, config.plugins.Webinterface.http.port.value, config.plugins.Webinterface.http.auth.value)
196 if ret == False:
197 errors = "%s%s:%i\n" %(errors, ip, config.plugins.Webinterface.http.port.value)
198 else:
199 registerBonjourService('http', config.plugins.Webinterface.http.port.value)
200 #HTTPS
201 if config.plugins.Webinterface.https.enabled.value is True:
202 ret = startServerInstance(session, ip, config.plugins.Webinterface.https.port.value, config.plugins.Webinterface.https.auth.value, True)
203 if ret == False:
204 errors = "%s%s:%i\n" %(errors, ip, config.plugins.Webinterface.https.port.value)
205 else:
206 registerBonjourService('https', config.plugins.Webinterface.https.port.value)
207
208 #LOCAL HTTP Connections (Streamproxy)
209 ret = startServerInstance(session, '127.0.0.1', 80, config.plugins.Webinterface.streamauth.value)
210 #if ret == False:
211 # errors = "%s%s:%i\n" %(errors, '127.0.0.1', 80)
212
213 #if errors != "":
214 # session.open(MessageBox, "Webinterface - Couldn't listen on:\n %s" % (errors), type=MessageBox.TYPE_ERROR, timeout=0)
215
216#===============================================================================
217# stop the Webinterface for all configured Interfaces
218#===============================================================================
219def stopWebserver(session):
220 try:
221 del session.mediaplayer
222 del session.messageboxanswer
223 except NameError:
224 pass
225 except AttributeError:
226 pass
227
228 global running_defered
229 if len(running_defered) > 0:
230 Closer(session).stop()
231
232#===============================================================================
233# startServerInstance
234# Starts an Instance of the Webinterface
235# on given ipaddress, port, w/o auth, w/o ssl
236#===============================================================================
237def startServerInstance(session, ipaddress, port, useauth=False, usessl=False):
238 try:
239 if useauth:
240# HTTPAuthResource handles the authentication for every Resource you want it to
241 root = HTTPAuthResource(toplevel, "Enigma2 WebInterface")
242 site = server.Site(root)
243 else:
244 site = server.Site(toplevel)
245
246 if usessl:
247
248 ctx = ssl.DefaultOpenSSLContextFactory('/etc/enigma2/server.pem', '/etc/enigma2/cacert.pem', sslmethod=SSL.SSLv23_METHOD)
249 d = reactor.listenSSL(port, site, ctx, interface=ipaddress)
250 else:
251 d = reactor.listenTCP(port, site, interface=ipaddress)
252 running_defered.append(d)
253 print "[Webinterface] started on %s:%i" % (ipaddress, port), "auth=", useauth, "ssl=", usessl
254 return True
255
256 except Exception, e:
257 print "[Webinterface] starting FAILED on %s:%i!" % (ipaddress, port), e
258 return False
259#===============================================================================
260# HTTPAuthResource
261# Handles HTTP Authorization for a given Resource
262#===============================================================================
263class HTTPAuthResource(resource.Resource):
264 def __init__(self, res, realm):
265 resource.Resource.__init__(self)
266 self.resource = res
267 self.realm = realm
268 self.authorized = False
269 self.tries = 0
270 self.unauthorizedResource = UnauthorizedResource(self.realm)
271
272 def unautorized(self, request):
273 request.setResponseCode(http.UNAUTHORIZED)
274 request.setHeader('WWW-authenticate', 'basic realm="%s"' % self.realm)
275
276 return self.unauthorizedResource
277
278 def isAuthenticated(self, request):
279 # get the Session from the Request
280 sessionNs = request.getSession().sessionNamespaces
281
282 # if the auth-information has not yet been stored to the session
283 if not sessionNs.has_key('authenticated'):
284 sessionNs['authenticated'] = check_passwd(request.getUser(), request.getPassword())
285
286 #if the auth-information already is in the session
287 else:
288 if sessionNs['authenticated'] is False:
289 sessionNs['authenticated'] = check_passwd(request.getUser(), request.getPassword() )
290
291 #return the current authentication status
292 return sessionNs['authenticated']
293
294#===============================================================================
295# Call render of self.resource (if authenticated)
296#===============================================================================
297 def render(self, request):
298 if self.isAuthenticated(request) is True:
299 return self.resource.render(request)
300
301 else:
302 return self.unautorized(request).render(request)
303
304#===============================================================================
305# Override to call getChildWithDefault of self.resource (if authenticated)
306#===============================================================================
307 def getChildWithDefault(self, path, request):
308 if self.isAuthenticated(request) is True:
309 return self.resource.getChildWithDefault(path, request)
310
311 else:
312 return self.unautorized(request)
313
314#===============================================================================
315# UnauthorizedResource
316# Returns a simple html-ified "Access Denied"
317#===============================================================================
318class UnauthorizedResource(resource.Resource):
319 def __init__(self, realm):
320 resource.Resource.__init__(self)
321 self.realm = realm
322 self.errorpage = static.Data('<html><body>Access Denied.</body></html>', 'text/html')
323
324 def getChild(self, path, request):
325 return self.errorpage
326
327 def render(self, request):
328 return self.errorpage.render(request)
329
330# Password verfication stuff
331
332from hashlib import md5 as md5_new
333from crypt import crypt
334
335#===============================================================================
336# getpwnam
337#
338# Get a password database entry for the given user name
339# Example from the Python Library Reference.
340#===============================================================================
341def getpwnam(name, pwfile=None):
342 if not pwfile:
343 pwfile = '/etc/passwd'
344
345 f = open(pwfile)
346 while 1:
347 line = f.readline()
348 if not line:
349 f.close()
350 raise KeyError, name
351 entry = tuple(line.strip().split(':', 6))
352 if entry[0] == name:
353 f.close()
354 return entry
355
356#===============================================================================
357# passcrypt
358#
359# Encrypt a password
360#===============================================================================
361def passcrypt(passwd, salt=None, method='des', magic='$1$'):
362 """Encrypt a string according to rules in crypt(3)."""
363 if method.lower() == 'des':
364 return crypt(passwd, salt)
365 elif method.lower() == 'md5':
366 return passcrypt_md5(passwd, salt, magic)
367 elif method.lower() == 'clear':
368 return passwd
369
370#===============================================================================
371# check_passwd
372#
373# Checks username and Password against a given Unix Password file
374# The default path is '/etc/passwd'
375#===============================================================================
376def check_passwd(name, passwd, pwfile='/etc/passwd'):
377 """Validate given user, passwd pair against password database."""
378
379 if not pwfile or type(pwfile) == type(''):
380 getuser = lambda x, pwfile = pwfile: getpwnam(x, pwfile)[1]
381 else:
382 getuser = pwfile.get_passwd
383
384 try:
385 enc_passwd = getuser(name)
386 except (KeyError, IOError):
387 print "!!! EXCEPT"
388 return False
389 if not enc_passwd:
390 "!!! NOT ENC_PASSWD"
391 return False
392 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
393 salt = enc_passwd[3:enc_passwd.find('$', 3)]
394 return enc_passwd == passcrypt(passwd, salt, 'md5')
395 else:
396 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
397
398def _to64(v, n):
399 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
400 r = ''
401 while (n - 1 >= 0):
402 r = r + DES_SALT[v & 0x3F]
403 v = v >> 6
404 n = n - 1
405 return r
406
407#===============================================================================
408# passcrypt_md5
409# Encrypt a password via md5
410#===============================================================================
411def passcrypt_md5(passwd, salt=None, magic='$1$'):
412 if not salt:
413 pass
414 elif salt[:len(magic)] == magic:
415 # remove magic from salt if present
416 salt = salt[len(magic):]
417
418 # salt only goes up to first '$'
419 salt = salt.split('$')[0]
420 # limit length of salt to 8
421 salt = salt[:8]
422
423 ctx = md5_new(passwd)
424 ctx.update(magic)
425 ctx.update(salt)
426
427 ctx1 = md5_new(passwd)
428 ctx1.update(salt)
429 ctx1.update(passwd)
430
431 final = ctx1.digest()
432
433 for i in range(len(passwd), 0 , -16):
434 if i > 16:
435 ctx.update(final)
436 else:
437 ctx.update(final[:i])
438
439 i = len(passwd)
440 while i:
441 if i & 1:
442 ctx.update('\0')
443 else:
444 ctx.update(passwd[:1])
445 i = i >> 1
446 final = ctx.digest()
447
448 for i in range(1000):
449 ctx1 = md5_new()
450 if i & 1:
451 ctx1.update(passwd)
452 else:
453 ctx1.update(final)
454 if i % 3: ctx1.update(salt)
455 if i % 7: ctx1.update(passwd)
456 if i & 1:
457 ctx1.update(final)
458 else:
459 ctx1.update(passwd)
460 final = ctx1.digest()
461
462 rv = magic + salt + '$'
463 final = map(ord, final)
464 l = (final[0] << 16) + (final[6] << 8) + final[12]
465 rv = rv + _to64(l, 4)
466 l = (final[1] << 16) + (final[7] << 8) + final[13]
467 rv = rv + _to64(l, 4)
468 l = (final[2] << 16) + (final[8] << 8) + final[14]
469 rv = rv + _to64(l, 4)
470 l = (final[3] << 16) + (final[9] << 8) + final[15]
471 rv = rv + _to64(l, 4)
472 l = (final[4] << 16) + (final[10] << 8) + final[5]
473 rv = rv + _to64(l, 4)
474 l = final[11]
475 rv = rv + _to64(l, 2)
476
477 return rv
478
479global_session = None
480
481#===============================================================================
482# sessionstart
483# Actions to take place on Session start
484#===============================================================================
485def sessionstart(reason, session):
486 global global_session
487 global_session = session
488
489
490def registerBonjourService(protocol, port):
491 try:
492 from Plugins.Extensions.Bonjour.Bonjour import bonjour
493
494 service = bonjour.buildService(protocol, port)
495 bonjour.registerService(service, True)
496 print "[WebInterface.registerBonjourService] Service for protocol '%s' with port '%i' registered!" %(protocol, port)
497 return True
498
499 except ImportError, e:
500 print "[WebInterface.registerBonjourService] %s" %e
501 return False
502
503def unregisterBonjourService(protocol):
504 try:
505 from Plugins.Extensions.Bonjour.Bonjour import bonjour
506
507 bonjour.unregisterService(protocol)
508 print "[WebInterface.unregisterBonjourService] Service for protocol '%s' unregistered!" %(protocol)
509 return True
510
511 except ImportError, e:
512 print "[WebInterface.unregisterBonjourService] %s" %e
513 return False
514
515def checkBonjour():
516 if ( not config.plugins.Webinterface.http.enabled.value ) or ( not config.plugins.Webinterface.enabled.value ):
517 unregisterBonjourService('http')
518 if ( not config.plugins.Webinterface.https.enabled.value ) or ( not config.plugins.Webinterface.enabled.value ):
519 unregisterBonjourService('https')
520
521#===============================================================================
522# networkstart
523# Actions to take place after Network is up (startup the Webserver)
524#===============================================================================
525def networkstart(reason, **kwargs):
526 if reason is True:
527 startWebserver(global_session)
528 checkBonjour()
529
530 elif reason is False:
531 stopWebserver(global_session)
532 checkBonjour()
533
534def openconfig(session, **kwargs):
535 session.openWithCallback(configCB, WebIfConfigScreen)
536
537def configCB(result, session):
538 if result is True:
539 print "[WebIf] config changed"
540 restartWebserver(session)
541 checkBonjour()
542 else:
543 print "[WebIf] config not changed"
544
545def Plugins(**kwargs):
546 return [PluginDescriptor(where=[PluginDescriptor.WHERE_SESSIONSTART], fnc=sessionstart),
547 PluginDescriptor(where=[PluginDescriptor.WHERE_NETWORKCONFIG_READ], fnc=networkstart),
548 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),
549 where=[PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png", fnc=openconfig)]
Note: See TracBrowser for help on using the repository browser.