fa8eb984300c35d35a4406e445dde182ad980ca6
[vuplus_dvbapp-plugin] / webinterface / src / plugin.py
1 Version = '$Header$';
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
14
15 DEBUG_TO_FILE=False # PLEASE DONT ENABLE LOGGING BY DEFAULT (OR COMMIT TO PLUGIN CVS)
16
17 DEBUGFILE= "/tmp/twisted.log"
18
19 global running_defered,waiting_shutdown
20 running_defered = []
21 waiting_shutdown = 0
22
23 class Closer:
24         counter = 0
25         def __init__(self,session, callback):
26                 self.callback = callback
27                 self.session = session
28                 
29         def stop(self):
30                 global running_defered
31                 for d in running_defered:
32                         print "[Webinterface] stopping interface on ",d.interface," with port",d.port
33                         x = d.stopListening()
34                         try:
35                                 x.addCallback(self.isDown)
36                                 self.counter +=1
37                         except AttributeError:
38                                 pass
39                 running_defered = []
40                 if self.counter <1:
41                         self.callback(self.session)
42                 
43         def isDown(self,s):
44                 self.counter-=1
45                 if self.counter <1:
46                         self.callback(self.session)
47                         
48                 
49 def restartWebserver(session):
50         try:
51                 del session.mediaplayer
52                 del session.messageboxanswer
53         except NameError:
54                 pass
55         except AttributeError:
56                 pass
57
58         global running_defered
59         if len(running_defered) >0:
60                 Closer(session,startWebserver).stop()
61         else:
62                 startWebserver(session)
63
64 def startWebserver(session):
65         global running_defered
66         session.mediaplayer = None
67         session.messageboxanswer = None
68         
69         if config.plugins.Webinterface.enable.value is not True:
70                 print "not starting Werbinterface"
71                 return False
72         if DEBUG_TO_FILE:
73                 print "start twisted logfile, writing to %s" % DEBUGFILE 
74                 startLogging(open(DEBUGFILE,'w'))
75         
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)
80                 else:
81                         print "[Webinterface] not starting disabled interface on %s:%i"%(c.adress.value,c.port.value)
82                         
83 def startServerInstance(session,ipadress,port,useauth=False):
84         try:
85                 toplevel = Toplevel(session)
86                 if useauth:
87                         portal = Portal(HTTPAuthRealm())
88                         portal.registerChecker(PasswordDatabase())
89                         root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
90                         site = server.Site(root)        
91                 else:
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
96         except Exception,e:
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)
99
100 def autostart(reason, **kwargs):
101         if "session" in kwargs:
102                 try:
103                         startWebserver(kwargs["session"])
104                 except ImportError,e:
105                         print "[Webinterface] twisted not available, not starting web services",e
106                         
107 def openconfig(session, **kwargs):
108         session.openWithCallback(configCB,WebIfConfigScreen)
109
110 def configCB(result,session):
111         if result is True:
112                 print "[WebIf] config changed"
113                 restartWebserver(session)
114         else:
115                 print "[WebIf] config not changed"
116                 
117
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)]
121         
122         
123 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
124         """
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
128         """
129
130         def locateChild(self, req, seg):
131                 return self.authenticate(req), seg
132         
133 class PasswordDatabase:
134     """
135         this checks webiflogins agains /etc/passwd
136     """
137     passwordfile = "/etc/passwd"
138     implements(checkers.ICredentialsChecker)
139     credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
140
141     def _cbPasswordMatch(self, matched, username):
142         if matched:
143             return username
144         else:
145             return failure.Failure(error.UnauthorizedLogin())
146
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))
150         else:
151                 return defer.fail(error.UnauthorizedLogin())
152
153 class IHTTPUser(Interface):
154         pass
155
156 class HTTPUser(object):
157         implements(IHTTPUser)
158
159 class HTTPAuthRealm(object):
160         implements(IRealm)
161         def requestAvatar(self, avatarId, mind, *interfaces):
162                 if IHTTPUser in interfaces:
163                         return IHTTPUser, HTTPUser()
164                 raise NotImplementedError("Only IHTTPUser interface is supported")
165
166
167 from string import find, split  
168 from md5 import new as md5_new
169 from crypt import crypt
170
171 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz') 
172 def getpwnam(name, pwfile=None):
173     """Return pasword database entry for the given user name.
174     
175     Example from the Python Library Reference.
176     """
177     
178     if not pwfile:
179         pwfile = '/etc/passwd'
180
181     f = open(pwfile)
182     while 1:
183         line = f.readline()
184         if not line:
185             f.close()
186             raise KeyError, name
187         entry = tuple(line.strip().split(':', 6))
188         if entry[0] == name:
189             f.close()
190             return entry
191
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':
199         return passwd
200
201 def check_passwd(name, passwd, pwfile=None):
202     """Validate given user, passwd pair against password database."""
203     
204     if not pwfile or type(pwfile) == type(''):
205         getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
206     else:
207         getuser = pwfile.get_passwd
208
209     try:
210         enc_passwd = getuser(name)
211     except (KeyError, IOError):
212         return 0
213     if not enc_passwd:
214         return 0
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')
218        
219     else:
220         return enc_passwd == passcrypt(passwd, enc_passwd[:2])
221
222 def _to64(v, n):
223     r = ''
224     while (n-1 >= 0):
225         r = r + DES_SALT[v & 0x3F]
226         v = v >> 6
227         n = n - 1
228     return r
229                         
230 def passcrypt_md5(passwd, salt=None, magic='$1$'):
231     """Encrypt passwd with MD5 algorithm."""
232     
233     if not salt:
234         pass
235     elif salt[:len(magic)] == magic:
236         # remove magic from salt if present
237         salt = salt[len(magic):]
238
239     # salt only goes up to first '$'
240     salt = split(salt, '$')[0]
241     # limit length of salt to 8
242     salt = salt[:8]
243
244     ctx = md5_new(passwd)
245     ctx.update(magic)
246     ctx.update(salt)
247     
248     ctx1 = md5_new(passwd)
249     ctx1.update(salt)
250     ctx1.update(passwd)
251     
252     final = ctx1.digest()
253     
254     for i in range(len(passwd), 0 , -16):
255         if i > 16:
256             ctx.update(final)
257         else:
258             ctx.update(final[:i])
259     
260     i = len(passwd)
261     while i:
262         if i & 1:
263             ctx.update('\0')
264         else:
265             ctx.update(passwd[:1])
266         i = i >> 1
267     final = ctx.digest()
268     
269     for i in range(1000):
270         ctx1 = md5_new()
271         if i & 1:
272             ctx1.update(passwd)
273         else:
274             ctx1.update(final)
275         if i % 3: ctx1.update(salt)
276         if i % 7: ctx1.update(passwd)
277         if i & 1:
278             ctx1.update(final)
279         else:
280             ctx1.update(passwd)
281         final = ctx1.digest()
282     
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)
295     l = final[11]
296     rv = rv + _to64(l, 2)
297     
298     return rv
299
300