2 __version__ = "Beta 0.6a"
3 from Plugins.Plugin import PluginDescriptor
4 from Components.config import config, ConfigSubsection, ConfigInteger,ConfigYesNo,ConfigText
6 from twisted.internet import reactor, defer
7 from twisted.web2 import server, channel, static, resource, stream, http_headers, responsecode, http
8 from twisted.web2.auth import digest, basic, wrapper
9 #from twisted.python import util
10 from twisted.python.log import startLogging,discardLogs
11 from twisted.cred.portal import Portal, IRealm
12 from twisted.cred import checkers, credentials, error
13 from zope.interface import Interface, implements
19 from WebChilds.Toplevel import Toplevel
20 config.plugins.Webinterface = ConfigSubsection()
21 config.plugins.Webinterface.enable = ConfigYesNo(default = True)
22 config.plugins.Webinterface.port = ConfigInteger(80,limits = (1, 65536))
23 config.plugins.Webinterface.includehdd = ConfigYesNo(default = False)
24 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
25 config.plugins.Webinterface.debug = ConfigYesNo(default = False) # False by default, not confgurable in GUI. Edit settingsfile directly if needed
26 config.plugins.Webinterface.version = ConfigText(__version__) # used to make the versioninfo accessible enigma2-wide, not confgurable in GUI.
30 set DEBUG to True, if twisted should write logoutput to a file.
31 in normal console output, twisted will print only the first Traceback.
32 is this a bug in twisted or a conflict with enigma2?
33 with this option enabled, twisted will print all TB to the logfile
34 use tail -f <file> to view this log
38 DEBUGFILE= "/tmp/twisted.log"
41 reactor.disconnectAll()
43 def restartWebserver(session):
45 startWebserver(session)
47 def startWebserver(session):
48 if config.plugins.Webinterface.enable.value is not True:
49 print "not starting Werbinterface"
51 if config.plugins.Webinterface.debug.value:
52 print "start twisted logfile, writing to %s" % DEBUGFILE
54 startLogging(open(DEBUGFILE,'w'))
56 toplevel = Toplevel(session)
57 if config.plugins.Webinterface.useauth.value is False:
58 site = server.Site(toplevel)
60 portal = Portal(HTTPAuthRealm())
61 portal.registerChecker(PasswordDatabase())
62 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
63 site = server.Site(root)
64 print "[WebIf] starting Webinterface on port",config.plugins.Webinterface.port.value
65 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site))
69 def autostart(reason, **kwargs):
70 if "session" in kwargs:
72 startWebserver(kwargs["session"])
74 print "[WebIf] twisted not available, not starting web services",e
76 def openconfig(session, **kwargs):
77 session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
79 def configCB(result,session):
81 print "[WebIf] config changed"
82 restartWebserver(session)
84 print "[WebIf] config not changed"
87 def Plugins(**kwargs):
88 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
89 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
92 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
94 set it only to True, if you have a patched wrapper.py
95 see http://twistedmatrix.com/trac/ticket/2041
96 so, the solution for us is to make a new class an override ne faulty func
99 def locateChild(self, req, seg):
100 return self.authenticate(req), seg
102 class PasswordDatabase:
104 this checks webiflogins agains /etc/passwd
106 passwordfile = "/etc/passwd"
107 implements(checkers.ICredentialsChecker)
108 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
110 def _cbPasswordMatch(self, matched, username):
114 return failure.Failure(error.UnauthorizedLogin())
116 def requestAvatarId(self, credentials):
117 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
118 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
120 return defer.fail(error.UnauthorizedLogin())
122 class IHTTPUser(Interface):
125 class HTTPUser(object):
126 implements(IHTTPUser)
128 class HTTPAuthRealm(object):
130 def requestAvatar(self, avatarId, mind, *interfaces):
131 if IHTTPUser in interfaces:
132 return IHTTPUser, HTTPUser()
133 raise NotImplementedError("Only IHTTPUser interface is supported")
136 import md5,time,string,crypt
137 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
138 def getpwnam(name, pwfile=None):
139 """Return pasword database entry for the given user name.
141 Example from the Python Library Reference.
145 pwfile = '/etc/passwd'
153 entry = tuple(line.strip().split(':', 6))
158 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
159 """Encrypt a string according to rules in crypt(3)."""
160 if method.lower() == 'des':
161 return crypt.crypt(passwd, salt)
162 elif method.lower() == 'md5':
163 return passcrypt_md5(passwd, salt, magic)
164 elif method.lower() == 'clear':
167 def check_passwd(name, passwd, pwfile=None):
168 """Validate given user, passwd pair against password database."""
170 if not pwfile or type(pwfile) == type(''):
171 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
173 getuser = pwfile.get_passwd
176 enc_passwd = getuser(name)
177 except (KeyError, IOError):
181 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
182 salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
183 return enc_passwd == passcrypt(passwd, salt, 'md5')
186 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
191 r = r + DES_SALT[v & 0x3F]
196 def passcrypt_md5(passwd, salt=None, magic='$1$'):
197 """Encrypt passwd with MD5 algorithm."""
201 elif salt[:len(magic)] == magic:
202 # remove magic from salt if present
203 salt = salt[len(magic):]
205 # salt only goes up to first '$'
206 salt = string.split(salt, '$')[0]
207 # limit length of salt to 8
210 ctx = md5.new(passwd)
214 ctx1 = md5.new(passwd)
218 final = ctx1.digest()
220 for i in range(len(passwd), 0 , -16):
224 ctx.update(final[:i])
231 ctx.update(passwd[:1])
235 for i in range(1000):
241 if i % 3: ctx1.update(salt)
242 if i % 7: ctx1.update(passwd)
247 final = ctx1.digest()
249 rv = magic + salt + '$'
250 final = map(ord, final)
251 l = (final[0] << 16) + (final[6] << 8) + final[12]
252 rv = rv + _to64(l, 4)
253 l = (final[1] << 16) + (final[7] << 8) + final[13]
254 rv = rv + _to64(l, 4)
255 l = (final[2] << 16) + (final[8] << 8) + final[14]
256 rv = rv + _to64(l, 4)
257 l = (final[3] << 16) + (final[9] << 8) + final[15]
258 rv = rv + _to64(l, 4)
259 l = (final[4] << 16) + (final[10] << 8) + final[5]
260 rv = rv + _to64(l, 4)
262 rv = rv + _to64(l, 2)