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 = True)
24 set DEBUG to True, if twisted should write logoutput to a file.
25 in normal console output, twisted will print only the first Traceback.
26 is this a bug in twisted or a conflict with enigma2?
27 with this option enabled, twisted will print all TB to the logfile
28 use tail -f <file> to view this log
32 DEBUGFILE= "/tmp/twisted.log"
34 from twisted.cred.portal import Portal
35 from twisted.cred import checkers
36 from twisted.web2.auth import digest, basic, wrapper
37 from zope.interface import Interface, implements
38 from twisted.cred import portal
39 from twisted.cred import credentials, error
40 from twisted.internet import defer
41 from zope import interface
45 reactor.disconnectAll()
47 def restartWebserver():
52 if config.plugins.Webinterface.enable.value is not True:
53 print "not starting Werbinterface"
56 print "start twisted logfile, writing to %s" % DEBUGFILE
58 startLogging(open(DEBUGFILE,'w'))
60 class ScreenPage(resource.Resource):
61 def __init__(self, path):
65 def render(self, req):
68 return http.Response(responsecode.OK, stream="please wait until enigma has booted")
70 class myProducerStream(stream.ProducerStream):
71 closed_callback = None
74 if self.closed_callback:
75 self.closed_callback()
76 stream.ProducerStream.close(self)
78 if os.path.isfile(self.path):
80 webif.renderPage(s, self.path, req, sessions[0]) # login?
81 return http.Response(responsecode.OK,{'Content-type': http_headers.MimeType('application', 'xhtml+xml', (('charset', 'UTF-8'),))},stream=s)
83 return http.Response(responsecode.NOT_FOUND)
85 def locateChild(self, request, segments):
86 path = self.path+'/'+'/'.join(segments)
90 return ScreenPage(path), ()
92 class Toplevel(resource.Resource):
94 child_web = ScreenPage(util.sibpath(__file__, "web")) # "/web/*"
95 child_webdata = static.File(util.sibpath(__file__, "web-data")) # FIXME: web-data appears as webdata
97 def render(self, req):
98 fp = open(util.sibpath(__file__, "web-data")+"/index.html")
101 return http.Response(responsecode.OK, {'Content-type': http_headers.MimeType('text', 'html')},stream=s)
104 if config.plugins.Webinterface.includehdd.value:
105 toplevel.putChild("hdd",static.File("/hdd"))
107 if config.plugins.Webinterface.useauth.value is False:
108 site = server.Site(Toplevel())
110 portal = Portal(HTTPAuthRealm())
111 portal.registerChecker(PasswordDatabase())
112 root = ModifiedHTTPAuthResource(Toplevel(),(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
113 site = server.Site(root)
114 print "[WebIf] starting Webinterface on port",config.plugins.Webinterface.port.value
115 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site))
118 def autostart(reason, **kwargs):
119 if "session" in kwargs:
121 sessions.append(kwargs["session"])
126 except ImportError,e:
127 print "[WebIf] twisted not available, not starting web services",e
129 def openconfig(session, **kwargs):
130 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
132 def configCB(result):
134 print "[WebIf] config changed"
137 print "[WebIf] config not changed"
140 def Plugins(**kwargs):
141 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
142 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
145 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
147 set it only to True, if you have a patched wrapper.py
148 see http://twistedmatrix.com/trac/ticket/2041
149 so, the solution for us is to make a new class an override ne faulty func
152 def locateChild(self, req, seg):
153 return self.authenticate(req), seg
155 class PasswordDatabase:
157 this checks webiflogins agains /etc/passwd
159 passwordfile = "/etc/passwd"
160 interface.implements(checkers.ICredentialsChecker)
161 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
163 def _cbPasswordMatch(self, matched, username):
167 return failure.Failure(error.UnauthorizedLogin())
169 def requestAvatarId(self, credentials):
171 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
172 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
174 return defer.fail(error.UnauthorizedLogin())
178 class IHTTPUser(Interface):
181 class HTTPUser(object):
182 implements(IHTTPUser)
184 class HTTPAuthRealm(object):
185 implements(portal.IRealm)
186 def requestAvatar(self, avatarId, mind, *interfaces):
187 if IHTTPUser in interfaces:
188 return IHTTPUser, HTTPUser()
189 raise NotImplementedError("Only IHTTPUser interface is supported")
192 import md5,time,string,crypt
193 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
194 def getpwnam(name, pwfile=None):
195 """Return pasword database entry for the given user name.
197 Example from the Python Library Reference.
201 pwfile = '/etc/passwd'
209 entry = tuple(line.strip().split(':', 6))
214 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
215 """Encrypt a string according to rules in crypt(3)."""
216 if method.lower() == 'des':
218 salt = str(whrandom.choice(DES_SALT)) + str(whrandom.choice(DES_SALT))
219 return crypt.crypt(passwd, salt)
220 elif method.lower() == 'md5':
221 return passcrypt_md5(passwd, salt, magic)
222 elif method.lower() == 'clear':
225 def check_passwd(name, passwd, pwfile=None):
226 """Validate given user, passwd pair against password database."""
228 if not pwfile or type(pwfile) == type(''):
229 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
231 getuser = pwfile.get_passwd
234 enc_passwd = getuser(name)
235 except (KeyError, IOError):
239 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
240 salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
241 return enc_passwd == passcrypt(passwd, salt=salt, method='md5')
243 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
248 r = r + DES_SALT[v & 0x3F]
253 def passcrypt_md5(passwd, salt=None, magic='$1$'):
254 """Encrypt passwd with MD5 algorithm."""
257 salt = repr(int(time.time()))[-8:]
258 elif salt[:len(magic)] == magic:
259 # remove magic from salt if present
260 salt = salt[len(magic):]
262 # salt only goes up to first '$'
263 salt = string.split(salt, '$')[0]
264 # limit length of salt to 8
267 ctx = md5.new(passwd)
271 ctx1 = md5.new(passwd)
275 final = ctx1.digest()
277 for i in range(len(passwd), 0 , -16):
281 ctx.update(final[:i])
288 ctx.update(passwd[:1])
292 for i in range(1000):
298 if i % 3: ctx1.update(salt)
299 if i % 7: ctx1.update(passwd)
304 final = ctx1.digest()
306 rv = magic + salt + '$'
307 final = map(ord, final)
308 l = (final[0] << 16) + (final[6] << 8) + final[12]
309 rv = rv + _to64(l, 4)
310 l = (final[1] << 16) + (final[7] << 8) + final[13]
311 rv = rv + _to64(l, 4)
312 l = (final[2] << 16) + (final[8] << 8) + final[14]
313 rv = rv + _to64(l, 4)
314 l = (final[3] << 16) + (final[9] << 8) + final[15]
315 rv = rv + _to64(l, 4)
316 l = (final[4] << 16) + (final[10] << 8) + final[5]
317 rv = rv + _to64(l, 4)
319 rv = rv + _to64(l, 2)