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, static, resource, stream, http_headers, responsecode, http
9 from twisted.web2.auth import digest, basic, wrapper
10 #from twisted.python import util
11 from twisted.python.log import startLogging,discardLogs
12 from twisted.cred.portal import Portal, IRealm
13 from twisted.cred import checkers, credentials, error
14 from zope.interface import Interface, implements
20 from WebChilds.Toplevel import Toplevel
21 config.plugins.Webinterface = ConfigSubsection()
22 config.plugins.Webinterface.enable = ConfigYesNo(default = True)
23 config.plugins.Webinterface.port = ConfigInteger(80,limits = (1, 65536))
24 config.plugins.Webinterface.includehdd = ConfigYesNo(default = False)
25 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
26 config.plugins.Webinterface.autowritetimer = ConfigYesNo(default = False)
27 config.plugins.Webinterface.loadmovielength = ConfigYesNo(default = False)
28 config.plugins.Webinterface.debug = ConfigYesNo(default = False) # False by default, not confgurable in GUI. Edit settingsfile directly if needed
29 config.plugins.Webinterface.version = ConfigText(__version__) # used to make the versioninfo accessible enigma2-wide, not confgurable in GUI.
33 set DEBUG to True, if twisted should write logoutput to a file.
34 in normal console output, twisted will print only the first Traceback.
35 is this a bug in twisted or a conflict with enigma2?
36 with this option enabled, twisted will print all TB to the logfile
37 use tail -f <file> to view this log
41 DEBUGFILE= "/tmp/twisted.log"
43 def stopWebserver(session):
44 reactor.disconnectAll()
45 try:# got BS with "global name session is not defined" so i catched it, but please make it safe if you use globals! thx
46 del session.mediaplayer
47 del session.messageboxanswer
51 def restartWebserver(session):
52 stopWebserver(session)
53 startWebserver(session)
55 def startWebserver(session):
56 # variables, that are needed in the process
57 session.mediaplayer = None
58 session.messageboxanswer = None
60 if config.plugins.Webinterface.enable.value is not True:
61 print "not starting Werbinterface"
63 if config.plugins.Webinterface.debug.value:
64 print "start twisted logfile, writing to %s" % DEBUGFILE
66 startLogging(open(DEBUGFILE,'w'))
68 toplevel = Toplevel(session)
69 if config.plugins.Webinterface.useauth.value is False:
70 site = server.Site(toplevel)
72 portal = Portal(HTTPAuthRealm())
73 portal.registerChecker(PasswordDatabase())
74 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
75 site = server.Site(root)
77 # here we start the Toplevel without any username or password
78 # this allows access to all request over the iface 127.0.0.1 without any auth
79 localsite = server.Site(toplevel)
80 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(localsite),interface='127.0.0.1')
82 # and here we make the Toplevel public to our external ifaces
83 # it depends on the config, if this is with auth support
84 # 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
86 for adaptername in nw.ifaces:
87 extip = nw.ifaces[adaptername]['ip']
88 if nw.ifaces[adaptername]['up'] is True:
89 extip = "%i.%i.%i.%i"%(extip[0],extip[1],extip[2],extip[3])
90 print "[WebIf] starting Webinterface on port %s on interface %s with address %s"%(str(config.plugins.Webinterface.port.value),adaptername,extip)
92 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site),interface=extip)
94 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)
96 print "[WebIf] found configured interface %s, but it is not running. so not starting a server on it ..." % adaptername
99 def autostart(reason, **kwargs):
100 if "session" in kwargs:
102 startWebserver(kwargs["session"])
103 except ImportError,e:
104 print "[WebIf] twisted not available, not starting web services",e
106 def openconfig(session, **kwargs):
107 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
109 def configCB(result,session):
111 print "[WebIf] config changed"
112 restartWebserver(session)
114 print "[WebIf] config not changed"
117 def Plugins(**kwargs):
118 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
119 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
122 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
124 set it only to True, if you have a patched wrapper.py
125 see http://twistedmatrix.com/trac/ticket/2041
126 so, the solution for us is to make a new class an override ne faulty func
129 def locateChild(self, req, seg):
130 return self.authenticate(req), seg
132 class PasswordDatabase:
134 this checks webiflogins agains /etc/passwd
136 passwordfile = "/etc/passwd"
137 implements(checkers.ICredentialsChecker)
138 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
140 def _cbPasswordMatch(self, matched, username):
144 return failure.Failure(error.UnauthorizedLogin())
146 def requestAvatarId(self, credentials):
147 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
148 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
150 return defer.fail(error.UnauthorizedLogin())
152 class IHTTPUser(Interface):
155 class HTTPUser(object):
156 implements(IHTTPUser)
158 class HTTPAuthRealm(object):
160 def requestAvatar(self, avatarId, mind, *interfaces):
161 if IHTTPUser in interfaces:
162 return IHTTPUser, HTTPUser()
163 raise NotImplementedError("Only IHTTPUser interface is supported")
166 import md5,time,string,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.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:string.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 = string.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)