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.debug = ConfigYesNo(default = False) # False by default, not confgurable in GUI. Edit settingsfile directly if needed
28 config.plugins.Webinterface.version = ConfigText(__version__) # used to make the versioninfo accessible enigma2-wide, not confgurable in GUI.
32 set DEBUG to True, if twisted should write logoutput to a file.
33 in normal console output, twisted will print only the first Traceback.
34 is this a bug in twisted or a conflict with enigma2?
35 with this option enabled, twisted will print all TB to the logfile
36 use tail -f <file> to view this log
40 DEBUGFILE= "/tmp/twisted.log"
42 def stopWebserver(session):
43 reactor.disconnectAll()
44 del session.mediaplayer
45 del session.messageboxanswer
47 def restartWebserver(session):
48 stopWebserver(session)
49 startWebserver(session)
51 def startWebserver(session):
52 # variables, that are needed in the process
53 session.mediaplayer = None
54 session.messageboxanswer = None
56 if config.plugins.Webinterface.enable.value is not True:
57 print "not starting Werbinterface"
59 if config.plugins.Webinterface.debug.value:
60 print "start twisted logfile, writing to %s" % DEBUGFILE
62 startLogging(open(DEBUGFILE,'w'))
64 toplevel = Toplevel(session)
65 if config.plugins.Webinterface.useauth.value is False:
66 site = server.Site(toplevel)
68 portal = Portal(HTTPAuthRealm())
69 portal.registerChecker(PasswordDatabase())
70 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
71 site = server.Site(root)
73 # here we start the Toplevel without any username or password
74 # this allows access to all request over the iface 127.0.0.1 without any auth
75 localsite = server.Site(toplevel)
76 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(localsite),interface='127.0.0.1')
78 # and here we make the Toplevel public to our external ifaces
79 # it depends on the config, if this is with auth support
80 # 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
82 for adaptername in nw.ifaces:
83 extip = nw.ifaces[adaptername]['ip']
84 if nw.ifaces[adaptername]['up'] is True:
85 extip = "%i.%i.%i.%i"%(extip[0],extip[1],extip[2],extip[3])
86 print "[WebIf] starting Webinterface on port %s on interface %s with address %s"%(str(config.plugins.Webinterface.port.value),adaptername,extip)
88 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site),interface=extip)
90 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)
92 print "[WebIf] found configured interface %s, but it is not running. so not starting a server on it ..." % adaptername
95 def autostart(reason, **kwargs):
96 if "session" in kwargs:
98 startWebserver(kwargs["session"])
100 print "[WebIf] twisted not available, not starting web services",e
102 def openconfig(session, **kwargs):
103 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
105 def configCB(result,session):
107 print "[WebIf] config changed"
108 restartWebserver(session)
110 print "[WebIf] config not changed"
113 def Plugins(**kwargs):
114 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
115 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
118 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
120 set it only to True, if you have a patched wrapper.py
121 see http://twistedmatrix.com/trac/ticket/2041
122 so, the solution for us is to make a new class an override ne faulty func
125 def locateChild(self, req, seg):
126 return self.authenticate(req), seg
128 class PasswordDatabase:
130 this checks webiflogins agains /etc/passwd
132 passwordfile = "/etc/passwd"
133 implements(checkers.ICredentialsChecker)
134 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
136 def _cbPasswordMatch(self, matched, username):
140 return failure.Failure(error.UnauthorizedLogin())
142 def requestAvatarId(self, credentials):
143 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
144 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
146 return defer.fail(error.UnauthorizedLogin())
148 class IHTTPUser(Interface):
151 class HTTPUser(object):
152 implements(IHTTPUser)
154 class HTTPAuthRealm(object):
156 def requestAvatar(self, avatarId, mind, *interfaces):
157 if IHTTPUser in interfaces:
158 return IHTTPUser, HTTPUser()
159 raise NotImplementedError("Only IHTTPUser interface is supported")
162 import md5,time,string,crypt
163 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
164 def getpwnam(name, pwfile=None):
165 """Return pasword database entry for the given user name.
167 Example from the Python Library Reference.
171 pwfile = '/etc/passwd'
179 entry = tuple(line.strip().split(':', 6))
184 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
185 """Encrypt a string according to rules in crypt(3)."""
186 if method.lower() == 'des':
187 return crypt.crypt(passwd, salt)
188 elif method.lower() == 'md5':
189 return passcrypt_md5(passwd, salt, magic)
190 elif method.lower() == 'clear':
193 def check_passwd(name, passwd, pwfile=None):
194 """Validate given user, passwd pair against password database."""
196 if not pwfile or type(pwfile) == type(''):
197 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
199 getuser = pwfile.get_passwd
202 enc_passwd = getuser(name)
203 except (KeyError, IOError):
207 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
208 salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
209 return enc_passwd == passcrypt(passwd, salt, 'md5')
212 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
217 r = r + DES_SALT[v & 0x3F]
222 def passcrypt_md5(passwd, salt=None, magic='$1$'):
223 """Encrypt passwd with MD5 algorithm."""
227 elif salt[:len(magic)] == magic:
228 # remove magic from salt if present
229 salt = salt[len(magic):]
231 # salt only goes up to first '$'
232 salt = string.split(salt, '$')[0]
233 # limit length of salt to 8
236 ctx = md5.new(passwd)
240 ctx1 = md5.new(passwd)
244 final = ctx1.digest()
246 for i in range(len(passwd), 0 , -16):
250 ctx.update(final[:i])
257 ctx.update(passwd[:1])
261 for i in range(1000):
267 if i % 3: ctx1.update(salt)
268 if i % 7: ctx1.update(passwd)
273 final = ctx1.digest()
275 rv = magic + salt + '$'
276 final = map(ord, final)
277 l = (final[0] << 16) + (final[6] << 8) + final[12]
278 rv = rv + _to64(l, 4)
279 l = (final[1] << 16) + (final[7] << 8) + final[13]
280 rv = rv + _to64(l, 4)
281 l = (final[2] << 16) + (final[8] << 8) + final[14]
282 rv = rv + _to64(l, 4)
283 l = (final[3] << 16) + (final[9] << 8) + final[15]
284 rv = rv + _to64(l, 4)
285 l = (final[4] << 16) + (final[10] << 8) + final[5]
286 rv = rv + _to64(l, 4)
288 rv = rv + _to64(l, 2)