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.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"
42 reactor.disconnectAll()
43 del session.mediaplayer
44 #del session.messageboxanswer
46 def restartWebserver(session):
48 startWebserver(session)
50 def startWebserver(session):
51 # variables, that are needed in the process
52 session.mediaplayer = None
53 #session.messageboxanswer = None
55 if config.plugins.Webinterface.enable.value is not True:
56 print "not starting Werbinterface"
58 if config.plugins.Webinterface.debug.value:
59 print "start twisted logfile, writing to %s" % DEBUGFILE
61 startLogging(open(DEBUGFILE,'w'))
63 toplevel = Toplevel(session)
64 if config.plugins.Webinterface.useauth.value is False:
65 site = server.Site(toplevel)
67 portal = Portal(HTTPAuthRealm())
68 portal.registerChecker(PasswordDatabase())
69 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
70 site = server.Site(root)
72 # here we start the Toplevel without any username or password
73 # this allows access to all request over the iface 127.0.0.1 without any auth
74 localsite = server.Site(toplevel)
75 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(localsite),interface='127.0.0.1')
77 # and here we make the Toplevel public to our external ifaces
78 # it depends on the config, if this is with auth support
79 # 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
81 for adaptername in nw.ifaces:
82 extip = nw.ifaces[adaptername]['ip']
83 if nw.ifaces[adaptername]['up'] is True:
84 extip = "%i.%i.%i.%i"%(extip[0],extip[1],extip[2],extip[3])
85 print "[WebIf] starting Webinterface on port %s on interface %s with address %s"%(str(config.plugins.Webinterface.port.value),adaptername,extip)
87 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site),interface=extip)
89 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)
91 print "[WebIf] found configured interface %s, but it is not running. so not starting a server on it ..." % adaptername
94 def autostart(reason, **kwargs):
95 if "session" in kwargs:
97 startWebserver(kwargs["session"])
99 print "[WebIf] twisted not available, not starting web services",e
101 def openconfig(session, **kwargs):
102 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
104 def configCB(result,session):
106 print "[WebIf] config changed"
107 restartWebserver(session)
109 print "[WebIf] config not changed"
112 def Plugins(**kwargs):
113 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
114 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
117 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
119 set it only to True, if you have a patched wrapper.py
120 see http://twistedmatrix.com/trac/ticket/2041
121 so, the solution for us is to make a new class an override ne faulty func
124 def locateChild(self, req, seg):
125 return self.authenticate(req), seg
127 class PasswordDatabase:
129 this checks webiflogins agains /etc/passwd
131 passwordfile = "/etc/passwd"
132 implements(checkers.ICredentialsChecker)
133 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
135 def _cbPasswordMatch(self, matched, username):
139 return failure.Failure(error.UnauthorizedLogin())
141 def requestAvatarId(self, credentials):
142 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
143 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
145 return defer.fail(error.UnauthorizedLogin())
147 class IHTTPUser(Interface):
150 class HTTPUser(object):
151 implements(IHTTPUser)
153 class HTTPAuthRealm(object):
155 def requestAvatar(self, avatarId, mind, *interfaces):
156 if IHTTPUser in interfaces:
157 return IHTTPUser, HTTPUser()
158 raise NotImplementedError("Only IHTTPUser interface is supported")
161 import md5,time,string,crypt
162 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
163 def getpwnam(name, pwfile=None):
164 """Return pasword database entry for the given user name.
166 Example from the Python Library Reference.
170 pwfile = '/etc/passwd'
178 entry = tuple(line.strip().split(':', 6))
183 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
184 """Encrypt a string according to rules in crypt(3)."""
185 if method.lower() == 'des':
186 return crypt.crypt(passwd, salt)
187 elif method.lower() == 'md5':
188 return passcrypt_md5(passwd, salt, magic)
189 elif method.lower() == 'clear':
192 def check_passwd(name, passwd, pwfile=None):
193 """Validate given user, passwd pair against password database."""
195 if not pwfile or type(pwfile) == type(''):
196 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
198 getuser = pwfile.get_passwd
201 enc_passwd = getuser(name)
202 except (KeyError, IOError):
206 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
207 salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
208 return enc_passwd == passcrypt(passwd, salt, 'md5')
211 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
216 r = r + DES_SALT[v & 0x3F]
221 def passcrypt_md5(passwd, salt=None, magic='$1$'):
222 """Encrypt passwd with MD5 algorithm."""
226 elif salt[:len(magic)] == magic:
227 # remove magic from salt if present
228 salt = salt[len(magic):]
230 # salt only goes up to first '$'
231 salt = string.split(salt, '$')[0]
232 # limit length of salt to 8
235 ctx = md5.new(passwd)
239 ctx1 = md5.new(passwd)
243 final = ctx1.digest()
245 for i in range(len(passwd), 0 , -16):
249 ctx.update(final[:i])
256 ctx.update(passwd[:1])
260 for i in range(1000):
266 if i % 3: ctx1.update(salt)
267 if i % 7: ctx1.update(passwd)
272 final = ctx1.digest()
274 rv = magic + salt + '$'
275 final = map(ord, final)
276 l = (final[0] << 16) + (final[6] << 8) + final[12]
277 rv = rv + _to64(l, 4)
278 l = (final[1] << 16) + (final[7] << 8) + final[13]
279 rv = rv + _to64(l, 4)
280 l = (final[2] << 16) + (final[8] << 8) + final[14]
281 rv = rv + _to64(l, 4)
282 l = (final[3] << 16) + (final[9] << 8) + final[15]
283 rv = rv + _to64(l, 4)
284 l = (final[4] << 16) + (final[10] << 8) + final[5]
285 rv = rv + _to64(l, 4)
287 rv = rv + _to64(l, 2)