2 from Plugins.Plugin import PluginDescriptor
3 from Components.config import config
4 from Screens.MessageBox import MessageBox
5 from WebIfConfig import WebIfConfigScreen
6 from WebChilds.Toplevel import Toplevel
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.log import startLogging
11 from twisted.cred.portal import Portal, IRealm
12 from twisted.cred import checkers, credentials, error
13 from zope.interface import Interface, implements
15 DEBUG_TO_FILE=False # PLEASE DONT ENABLE LOGGING BY DEFAULT (OR COMMIT TO PLUGIN CVS)
17 DEBUGFILE= "/tmp/twisted.log"
19 global running_defered,waiting_shutdown
25 def __init__(self,session, callback):
26 self.callback = callback
27 self.session = session
30 global running_defered
31 for d in running_defered:
32 print "[Webinterface] stopping interface on ",d.interface," with port",d.port
35 x.addCallback(self.isDown)
37 except AttributeError:
41 self.callback(self.session)
46 self.callback(self.session)
49 def restartWebserver(session):
51 del session.mediaplayer
52 del session.messageboxanswer
55 except AttributeError:
58 global running_defered
59 if len(running_defered) >0:
60 Closer(session,startWebserver).stop()
62 startWebserver(session)
64 def startWebserver(session):
65 global running_defered
66 session.mediaplayer = None
67 session.messageboxanswer = None
69 if config.plugins.Webinterface.enable.value is not True:
70 print "not starting Werbinterface"
73 print "start twisted logfile, writing to %s" % DEBUGFILE
74 startLogging(open(DEBUGFILE,'w'))
76 for i in range(0, config.plugins.Webinterface.interfacecount.value):
77 c = config.plugins.Webinterface.interfaces[i]
78 if c.disabled.value is False:
79 startServerInstance(session,c.adress.value,c.port.value,c.useauth.value)
81 print "[Webinterface] not starting disabled interface on %s:%i"%(c.adress.value,c.port.value)
83 def startServerInstance(session,ipadress,port,useauth=False):
85 toplevel = Toplevel(session)
87 portal = Portal(HTTPAuthRealm())
88 portal.registerChecker(PasswordDatabase())
89 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
90 site = server.Site(root)
92 site = server.Site(toplevel)
93 d = reactor.listenTCP(port, channel.HTTPFactory(site),interface=ipadress)
94 running_defered.append(d)
95 print "[Webinterface] started on %s:%i"%(ipadress,port),"auth=",useauth
97 print "[Webinterface] starting FAILED on %s:%i!"%(ipadress,port),e
98 session.open(MessageBox,'starting FAILED on %s:%i!\n\n%s'%(ipadress,port,str(e)), MessageBox.TYPE_ERROR)
100 def autostart(reason, **kwargs):
101 if "session" in kwargs:
103 startWebserver(kwargs["session"])
104 except ImportError,e:
105 print "[Webinterface] twisted not available, not starting web services",e
107 def openconfig(session, **kwargs):
108 session.openWithCallback(configCB,WebIfConfigScreen)
110 def configCB(result,session):
112 print "[WebIf] config changed"
113 restartWebserver(session)
115 print "[WebIf] config not changed"
118 def Plugins(**kwargs):
119 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
120 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
123 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
125 set it only to True, if you have a patched wrapper.py
126 see http://twistedmatrix.com/trac/ticket/2041
127 so, the solution for us is to make a new class an override ne faulty func
130 def locateChild(self, req, seg):
131 return self.authenticate(req), seg
133 class PasswordDatabase:
135 this checks webiflogins agains /etc/passwd
137 passwordfile = "/etc/passwd"
138 implements(checkers.ICredentialsChecker)
139 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
141 def _cbPasswordMatch(self, matched, username):
145 return failure.Failure(error.UnauthorizedLogin())
147 def requestAvatarId(self, credentials):
148 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
149 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
151 return defer.fail(error.UnauthorizedLogin())
153 class IHTTPUser(Interface):
156 class HTTPUser(object):
157 implements(IHTTPUser)
159 class HTTPAuthRealm(object):
161 def requestAvatar(self, avatarId, mind, *interfaces):
162 if IHTTPUser in interfaces:
163 return IHTTPUser, HTTPUser()
164 raise NotImplementedError("Only IHTTPUser interface is supported")
167 from string import find, split
168 from md5 import new as md5_new
169 from crypt import crypt
171 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
172 def getpwnam(name, pwfile=None):
173 """Return pasword database entry for the given user name.
175 Example from the Python Library Reference.
179 pwfile = '/etc/passwd'
187 entry = tuple(line.strip().split(':', 6))
192 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
193 """Encrypt a string according to rules in crypt(3)."""
194 if method.lower() == 'des':
195 return crypt(passwd, salt)
196 elif method.lower() == 'md5':
197 return passcrypt_md5(passwd, salt, magic)
198 elif method.lower() == 'clear':
201 def check_passwd(name, passwd, pwfile=None):
202 """Validate given user, passwd pair against password database."""
204 if not pwfile or type(pwfile) == type(''):
205 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
207 getuser = pwfile.get_passwd
210 enc_passwd = getuser(name)
211 except (KeyError, IOError):
215 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
216 salt = enc_passwd[3:find(enc_passwd, '$', 3)]
217 return enc_passwd == passcrypt(passwd, salt, 'md5')
220 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
225 r = r + DES_SALT[v & 0x3F]
230 def passcrypt_md5(passwd, salt=None, magic='$1$'):
231 """Encrypt passwd with MD5 algorithm."""
235 elif salt[:len(magic)] == magic:
236 # remove magic from salt if present
237 salt = salt[len(magic):]
239 # salt only goes up to first '$'
240 salt = split(salt, '$')[0]
241 # limit length of salt to 8
244 ctx = md5_new(passwd)
248 ctx1 = md5_new(passwd)
252 final = ctx1.digest()
254 for i in range(len(passwd), 0 , -16):
258 ctx.update(final[:i])
265 ctx.update(passwd[:1])
269 for i in range(1000):
275 if i % 3: ctx1.update(salt)
276 if i % 7: ctx1.update(passwd)
281 final = ctx1.digest()
283 rv = magic + salt + '$'
284 final = map(ord, final)
285 l = (final[0] << 16) + (final[6] << 8) + final[12]
286 rv = rv + _to64(l, 4)
287 l = (final[1] << 16) + (final[7] << 8) + final[13]
288 rv = rv + _to64(l, 4)
289 l = (final[2] << 16) + (final[8] << 8) + final[14]
290 rv = rv + _to64(l, 4)
291 l = (final[3] << 16) + (final[9] << 8) + final[15]
292 rv = rv + _to64(l, 4)
293 l = (final[4] << 16) + (final[10] << 8) + final[5]
294 rv = rv + _to64(l, 4)
296 rv = rv + _to64(l, 2)