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 files_to_send_normal_http_headers = ['stream.m3u.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):
70 def render(self, req):
73 return http.Response(responsecode.OK, stream="please wait until enigma has booted")
75 class myProducerStream(stream.ProducerStream):
76 closed_callback = None
79 if self.closed_callback:
80 self.closed_callback()
81 stream.ProducerStream.close(self)
83 if os.path.isfile(self.path):
85 webif.renderPage(s, self.path, req, sessions[0]) # login?
86 if self.path.split("/")[-1] in files_to_send_normal_http_headers:
87 return http.Response(responsecode.OK,stream=s)
89 return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('application', 'xhtml+xml', (('charset', 'UTF-8'),))},stream=s)
91 return http.Response(responsecode.NOT_FOUND)
93 def locateChild(self, request, segments):
94 path = self.path+'/'+'/'.join(segments)
98 return ScreenPage(path), ()
100 class Toplevel(resource.Resource):
102 child_web = ScreenPage(util.sibpath(__file__, "web")) # "/web/*"
103 child_webdata = static.File(util.sibpath(__file__, "web-data")) # FIXME: web-data appears as webdata
105 def render(self, req):
106 fp = open(util.sibpath(__file__, "web-data")+"/index.html")
109 return http.Response(responsecode.OK, {'Content-type': http_headers.MimeType('text', 'html')},stream=s)
111 toplevel = Toplevel()
112 if config.plugins.Webinterface.includehdd.value:
113 toplevel.putChild("hdd",static.File("/hdd"))
115 if config.plugins.Webinterface.useauth.value is False:
116 site = server.Site(toplevel)
118 portal = Portal(HTTPAuthRealm())
119 portal.registerChecker(PasswordDatabase())
120 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
121 site = server.Site(root)
122 print "[WebIf] starting Webinterface on port",config.plugins.Webinterface.port.value
123 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site))
126 def autostart(reason, **kwargs):
127 if "session" in kwargs:
129 sessions.append(kwargs["session"])
134 except ImportError,e:
135 print "[WebIf] twisted not available, not starting web services",e
137 def openconfig(session, **kwargs):
138 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
140 def configCB(result):
142 print "[WebIf] config changed"
145 print "[WebIf] config not changed"
148 def Plugins(**kwargs):
149 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
150 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
153 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
155 set it only to True, if you have a patched wrapper.py
156 see http://twistedmatrix.com/trac/ticket/2041
157 so, the solution for us is to make a new class an override ne faulty func
160 def locateChild(self, req, seg):
161 return self.authenticate(req), seg
163 class PasswordDatabase:
165 this checks webiflogins agains /etc/passwd
167 passwordfile = "/etc/passwd"
168 interface.implements(checkers.ICredentialsChecker)
169 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
171 def _cbPasswordMatch(self, matched, username):
175 return failure.Failure(error.UnauthorizedLogin())
177 def requestAvatarId(self, credentials):
178 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
179 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
181 return defer.fail(error.UnauthorizedLogin())
183 class IHTTPUser(Interface):
186 class HTTPUser(object):
187 implements(IHTTPUser)
189 class HTTPAuthRealm(object):
190 implements(portal.IRealm)
191 def requestAvatar(self, avatarId, mind, *interfaces):
192 if IHTTPUser in interfaces:
193 return IHTTPUser, HTTPUser()
194 raise NotImplementedError("Only IHTTPUser interface is supported")
197 import md5,time,string,crypt
198 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
199 def getpwnam(name, pwfile=None):
200 """Return pasword database entry for the given user name.
202 Example from the Python Library Reference.
206 pwfile = '/etc/passwd'
214 entry = tuple(line.strip().split(':', 6))
219 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
220 """Encrypt a string according to rules in crypt(3)."""
221 if method.lower() == 'des':
223 salt = str(whrandom.choice(DES_SALT)) + str(whrandom.choice(DES_SALT))
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=salt, method='md5')
248 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
253 r = r + DES_SALT[v & 0x3F]
258 def passcrypt_md5(passwd, salt=None, magic='$1$'):
259 """Encrypt passwd with MD5 algorithm."""
262 salt = repr(int(time.time()))[-8:]
263 elif salt[:len(magic)] == magic:
264 # remove magic from salt if present
265 salt = salt[len(magic):]
267 # salt only goes up to first '$'
268 salt = string.split(salt, '$')[0]
269 # limit length of salt to 8
272 ctx = md5.new(passwd)
276 ctx1 = md5.new(passwd)
280 final = ctx1.digest()
282 for i in range(len(passwd), 0 , -16):
286 ctx.update(final[:i])
293 ctx.update(passwd[:1])
297 for i in range(1000):
303 if i % 3: ctx1.update(salt)
304 if i % 7: ctx1.update(passwd)
309 final = ctx1.digest()
311 rv = magic + salt + '$'
312 final = map(ord, final)
313 l = (final[0] << 16) + (final[6] << 8) + final[12]
314 rv = rv + _to64(l, 4)
315 l = (final[1] << 16) + (final[7] << 8) + final[13]
316 rv = rv + _to64(l, 4)
317 l = (final[2] << 16) + (final[8] << 8) + final[14]
318 rv = rv + _to64(l, 4)
319 l = (final[3] << 16) + (final[9] << 8) + final[15]
320 rv = rv + _to64(l, 4)
321 l = (final[4] << 16) + (final[10] << 8) + final[5]
322 rv = rv + _to64(l, 4)
324 rv = rv + _to64(l, 2)