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','getpid.xml',]
29 set DEBUG to True, if twisted should write logoutput to a file.
30 in normal console output, twisted will print only the first Traceback.
31 is this a bug in twisted or a conflict with enigma2?
32 with this option enabled, twisted will print all TB to the logfile
33 use tail -f <file> to view this log
37 DEBUGFILE= "/tmp/twisted.log"
39 from twisted.cred.portal import Portal
40 from twisted.cred import checkers
41 from twisted.web2.auth import digest, basic, wrapper
42 from zope.interface import Interface, implements
43 from twisted.cred import portal
44 from twisted.cred import credentials, error
45 from twisted.internet import defer
46 from zope import interface
50 reactor.disconnectAll()
52 def restartWebserver():
57 if config.plugins.Webinterface.enable.value is not True:
58 print "not starting Werbinterface"
61 print "start twisted logfile, writing to %s" % DEBUGFILE
63 startLogging(open(DEBUGFILE,'w'))
65 class ScreenPage(resource.Resource):
66 def __init__(self, path):
69 def render(self, req):
72 return http.Response(responsecode.OK, stream="please wait until enigma has booted")
74 class myProducerStream(stream.ProducerStream):
76 stream.ProducerStream.__init__(self)
77 self.closed_callback = None
80 if self.closed_callback:
81 self.closed_callback()
82 self.closed_callback = None
83 stream.ProducerStream.close(self)
85 if os.path.isfile(self.path):
87 webif.renderPage(s, self.path, req, sessions[0]) # login?
88 if self.path.split("/")[-1] in AppTextHeaderFiles:
89 return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('application', 'text', (('charset', 'UTF-8'),))},stream=s)
91 return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('application', 'xhtml+xml', (('charset', 'UTF-8'),))},stream=s)
93 return http.Response(responsecode.NOT_FOUND)
95 def locateChild(self, request, segments):
96 path = self.path+'/'+'/'.join(segments)
100 return ScreenPage(path), ()
102 class Toplevel(resource.Resource):
104 child_web = ScreenPage(util.sibpath(__file__, "web")) # "/web/*"
105 child_webdata = static.File(util.sibpath(__file__, "web-data")) # FIXME: web-data appears as webdata
107 def render(self, req):
108 fp = open(util.sibpath(__file__, "web-data")+"/index.html")
111 return http.Response(responsecode.OK, {'Content-type': http_headers.MimeType('text', 'html')},stream=s)
113 toplevel = Toplevel()
114 if config.plugins.Webinterface.includehdd.value:
115 toplevel.putChild("hdd",static.File("/hdd"))
117 if config.plugins.Webinterface.useauth.value is False:
118 site = server.Site(toplevel)
120 portal = Portal(HTTPAuthRealm())
121 portal.registerChecker(PasswordDatabase())
122 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
123 site = server.Site(root)
124 print "[WebIf] starting Webinterface on port",config.plugins.Webinterface.port.value
125 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site))
128 def autostart(reason, **kwargs):
129 if "session" in kwargs:
131 sessions.append(kwargs["session"])
136 except ImportError,e:
137 print "[WebIf] twisted not available, not starting web services",e
139 def openconfig(session, **kwargs):
140 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
142 def configCB(result):
144 print "[WebIf] config changed"
147 print "[WebIf] config not changed"
150 def Plugins(**kwargs):
151 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
152 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
155 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
157 set it only to True, if you have a patched wrapper.py
158 see http://twistedmatrix.com/trac/ticket/2041
159 so, the solution for us is to make a new class an override ne faulty func
162 def locateChild(self, req, seg):
163 return self.authenticate(req), seg
165 class PasswordDatabase:
167 this checks webiflogins agains /etc/passwd
169 passwordfile = "/etc/passwd"
170 interface.implements(checkers.ICredentialsChecker)
171 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
173 def _cbPasswordMatch(self, matched, username):
177 return failure.Failure(error.UnauthorizedLogin())
179 def requestAvatarId(self, credentials):
180 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
181 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
183 return defer.fail(error.UnauthorizedLogin())
185 class IHTTPUser(Interface):
188 class HTTPUser(object):
189 implements(IHTTPUser)
191 class HTTPAuthRealm(object):
192 implements(portal.IRealm)
193 def requestAvatar(self, avatarId, mind, *interfaces):
194 if IHTTPUser in interfaces:
195 return IHTTPUser, HTTPUser()
196 raise NotImplementedError("Only IHTTPUser interface is supported")
199 import md5,time,string,crypt
200 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
201 def getpwnam(name, pwfile=None):
202 """Return pasword database entry for the given user name.
204 Example from the Python Library Reference.
208 pwfile = '/etc/passwd'
216 entry = tuple(line.strip().split(':', 6))
221 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
222 """Encrypt a string according to rules in crypt(3)."""
223 if method.lower() == 'des':
224 return crypt.crypt(passwd, salt)
225 elif method.lower() == 'md5':
226 return passcrypt_md5(passwd, salt, magic)
227 elif method.lower() == 'clear':
230 def check_passwd(name, passwd, pwfile=None):
231 """Validate given user, passwd pair against password database."""
233 if not pwfile or type(pwfile) == type(''):
234 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
236 getuser = pwfile.get_passwd
239 enc_passwd = getuser(name)
240 except (KeyError, IOError):
244 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
245 salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
246 return enc_passwd == passcrypt(passwd, salt, 'md5')
249 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
254 r = r + DES_SALT[v & 0x3F]
259 def passcrypt_md5(passwd, salt=None, magic='$1$'):
260 """Encrypt passwd with MD5 algorithm."""
264 elif salt[:len(magic)] == magic:
265 # remove magic from salt if present
266 salt = salt[len(magic):]
268 # salt only goes up to first '$'
269 salt = string.split(salt, '$')[0]
270 # limit length of salt to 8
273 ctx = md5.new(passwd)
277 ctx1 = md5.new(passwd)
281 final = ctx1.digest()
283 for i in range(len(passwd), 0 , -16):
287 ctx.update(final[:i])
294 ctx.update(passwd[:1])
298 for i in range(1000):
304 if i % 3: ctx1.update(salt)
305 if i % 7: ctx1.update(passwd)
310 final = ctx1.digest()
312 rv = magic + salt + '$'
313 final = map(ord, final)
314 l = (final[0] << 16) + (final[6] << 8) + final[12]
315 rv = rv + _to64(l, 4)
316 l = (final[1] << 16) + (final[7] << 8) + final[13]
317 rv = rv + _to64(l, 4)
318 l = (final[2] << 16) + (final[8] << 8) + final[14]
319 rv = rv + _to64(l, 4)
320 l = (final[3] << 16) + (final[9] << 8) + final[15]
321 rv = rv + _to64(l, 4)
322 l = (final[4] << 16) + (final[10] << 8) + final[5]
323 rv = rv + _to64(l, 4)
325 rv = rv + _to64(l, 2)