2 __version__ = "Beta 0.95.1"
3 from Plugins.Plugin import PluginDescriptor
4 from Components.config import config, ConfigSubsection, ConfigInteger,ConfigYesNo,ConfigText
5 from Components.Network import Network
7 from twisted.internet import reactor, defer
8 from twisted.web2 import server, channel, http
9 from twisted.web2.auth import digest, basic, wrapper
10 #from twisted.python import util
11 from twisted.python.log import startLogging
12 from twisted.cred.portal import Portal, IRealm
13 from twisted.cred import checkers, credentials, error
14 from zope.interface import Interface, implements
16 from WebIfConfig import WebIfConfigScreen
18 from WebChilds.Toplevel import Toplevel
19 config.plugins.Webinterface = ConfigSubsection()
20 config.plugins.Webinterface.enable = ConfigYesNo(default = True)
21 config.plugins.Webinterface.port = ConfigInteger(80,limits = (1, 65536))
22 config.plugins.Webinterface.includehdd = ConfigYesNo(default = False)
23 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
24 config.plugins.Webinterface.autowritetimer = ConfigYesNo(default = False)
25 config.plugins.Webinterface.loadmovielength = ConfigYesNo(default = False)
26 config.plugins.Webinterface.debug = ConfigYesNo(default = False) # False by default, not confgurable in GUI. Edit settingsfile directly if needed
27 config.plugins.Webinterface.version = ConfigText(__version__) # used to make the versioninfo accessible enigma2-wide, not confgurable in GUI.
31 set DEBUG to True, if twisted should write logoutput to a file.
32 in normal console output, twisted will print only the first Traceback.
33 is this a bug in twisted or a conflict with enigma2?
34 with this option enabled, twisted will print all TB to the logfile
35 use tail -f <file> to view this log
39 DEBUGFILE= "/tmp/twisted.log"
41 def stopWebserver(session):
42 reactor.disconnectAll()
43 try:# got BS with "global name session is not defined" so i catched it, but please make it safe if you use globals! thx
44 del session.mediaplayer
45 del session.messageboxanswer
49 def restartWebserver(session):
50 stopWebserver(session)
51 startWebserver(session)
53 def startWebserver(session):
54 # variables, that are needed in the process
55 session.mediaplayer = None
56 session.messageboxanswer = None
58 if config.plugins.Webinterface.enable.value is not True:
59 print "not starting Werbinterface"
61 if config.plugins.Webinterface.debug.value:
62 print "start twisted logfile, writing to %s" % DEBUGFILE
63 startLogging(open(DEBUGFILE,'w'))
65 toplevel = Toplevel(session)
66 if config.plugins.Webinterface.useauth.value is False:
67 site = server.Site(toplevel)
69 portal = Portal(HTTPAuthRealm())
70 portal.registerChecker(PasswordDatabase())
71 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
72 site = server.Site(root)
74 # here we start the Toplevel without any username or password
75 # this allows access to all request over the iface 127.0.0.1 without any auth
76 localsite = server.Site(toplevel)
77 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(localsite),interface='127.0.0.1')
79 # and here we make the Toplevel public to our external ifaces
80 # it depends on the config, if this is with auth support
81 # keep in mind, if we have a second external ip (like a wlan device), we have to do it in the same way for this iface too
83 for adaptername in nw.ifaces:
84 extip = nw.ifaces[adaptername]['ip']
85 if nw.ifaces[adaptername]['up'] is True:
86 extip = "%i.%i.%i.%i"%(extip[0],extip[1],extip[2],extip[3])
87 print "[WebIf] starting Webinterface on port %s on interface %s with address %s"%(str(config.plugins.Webinterface.port.value),adaptername,extip)
89 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site),interface=extip)
91 print "[WebIf] Error starting Webinterface on port %s on interface %s with address %s,because \n%s"%(str(config.plugins.Webinterface.port.value),adaptername,extip,e)
93 print "[WebIf] found configured interface %s, but it is not running. so not starting a server on it ..." % adaptername
96 def autostart(reason, **kwargs):
97 if "session" in kwargs:
99 startWebserver(kwargs["session"])
100 except ImportError,e:
101 print "[WebIf] twisted not available, not starting web services",e
103 def openconfig(session, **kwargs):
104 session.openWithCallback(configCB,WebIfConfigScreen)
106 def configCB(result,session):
108 print "[WebIf] config changed"
109 restartWebserver(session)
111 print "[WebIf] config not changed"
114 def Plugins(**kwargs):
115 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
116 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
119 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
121 set it only to True, if you have a patched wrapper.py
122 see http://twistedmatrix.com/trac/ticket/2041
123 so, the solution for us is to make a new class an override ne faulty func
126 def locateChild(self, req, seg):
127 return self.authenticate(req), seg
129 class PasswordDatabase:
131 this checks webiflogins agains /etc/passwd
133 passwordfile = "/etc/passwd"
134 implements(checkers.ICredentialsChecker)
135 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
137 def _cbPasswordMatch(self, matched, username):
141 return failure.Failure(error.UnauthorizedLogin())
143 def requestAvatarId(self, credentials):
144 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
145 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
147 return defer.fail(error.UnauthorizedLogin())
149 class IHTTPUser(Interface):
152 class HTTPUser(object):
153 implements(IHTTPUser)
155 class HTTPAuthRealm(object):
157 def requestAvatar(self, avatarId, mind, *interfaces):
158 if IHTTPUser in interfaces:
159 return IHTTPUser, HTTPUser()
160 raise NotImplementedError("Only IHTTPUser interface is supported")
163 from string import find, split
164 from md5 import new as md5_new
165 from crypt import crypt
167 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
168 def getpwnam(name, pwfile=None):
169 """Return pasword database entry for the given user name.
171 Example from the Python Library Reference.
175 pwfile = '/etc/passwd'
183 entry = tuple(line.strip().split(':', 6))
188 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
189 """Encrypt a string according to rules in crypt(3)."""
190 if method.lower() == 'des':
191 return crypt(passwd, salt)
192 elif method.lower() == 'md5':
193 return passcrypt_md5(passwd, salt, magic)
194 elif method.lower() == 'clear':
197 def check_passwd(name, passwd, pwfile=None):
198 """Validate given user, passwd pair against password database."""
200 if not pwfile or type(pwfile) == type(''):
201 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
203 getuser = pwfile.get_passwd
206 enc_passwd = getuser(name)
207 except (KeyError, IOError):
211 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
212 salt = enc_passwd[3:find(enc_passwd, '$', 3)]
213 return enc_passwd == passcrypt(passwd, salt, 'md5')
216 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
221 r = r + DES_SALT[v & 0x3F]
226 def passcrypt_md5(passwd, salt=None, magic='$1$'):
227 """Encrypt passwd with MD5 algorithm."""
231 elif salt[:len(magic)] == magic:
232 # remove magic from salt if present
233 salt = salt[len(magic):]
235 # salt only goes up to first '$'
236 salt = split(salt, '$')[0]
237 # limit length of salt to 8
240 ctx = md5_new(passwd)
244 ctx1 = md5_new(passwd)
248 final = ctx1.digest()
250 for i in range(len(passwd), 0 , -16):
254 ctx.update(final[:i])
261 ctx.update(passwd[:1])
265 for i in range(1000):
271 if i % 3: ctx1.update(salt)
272 if i % 7: ctx1.update(passwd)
277 final = ctx1.digest()
279 rv = magic + salt + '$'
280 final = map(ord, final)
281 l = (final[0] << 16) + (final[6] << 8) + final[12]
282 rv = rv + _to64(l, 4)
283 l = (final[1] << 16) + (final[7] << 8) + final[13]
284 rv = rv + _to64(l, 4)
285 l = (final[2] << 16) + (final[8] << 8) + final[14]
286 rv = rv + _to64(l, 4)
287 l = (final[3] << 16) + (final[9] << 8) + final[15]
288 rv = rv + _to64(l, 4)
289 l = (final[4] << 16) + (final[10] << 8) + final[5]
290 rv = rv + _to64(l, 4)
292 rv = rv + _to64(l, 2)