Message Answer now reaches webinterface
[enigma2-plugins.git] / webinterface / src / plugin.py
1 Version = '$Header$';
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
6
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
15
16 import webif
17 import WebIfConfig  
18 import os
19
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. 
28  
29
30 """
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
36 """
37                         
38
39 DEBUGFILE= "/tmp/twisted.log"
40
41 def stopWebserver():
42         reactor.disconnectAll()
43         del session.mediaplayer
44         #del session.messageboxanswer
45
46 def restartWebserver(session):
47         stopWebserver()
48         startWebserver(session)
49
50 def startWebserver(session):
51         # variables, that are needed in the process
52         session.mediaplayer = None
53         #session.messageboxanswer = None
54         
55         if config.plugins.Webinterface.enable.value is not True:
56                 print "not starting Werbinterface"
57                 return False
58         if config.plugins.Webinterface.debug.value:
59                 print "start twisted logfile, writing to %s" % DEBUGFILE 
60                 import sys
61                 startLogging(open(DEBUGFILE,'w'))
62
63         toplevel = Toplevel(session)
64         if config.plugins.Webinterface.useauth.value is False:
65                 site = server.Site(toplevel)
66         else:
67                 portal = Portal(HTTPAuthRealm())
68                 portal.registerChecker(PasswordDatabase())
69                 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
70                 site = server.Site(root)
71         
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')
76         
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
80         nw = Network()
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)
86                         try:
87                                 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site),interface=extip)
88                         except Exception,e:
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)
90                 else:
91                         print "[WebIf] found configured interface %s, but it is not running. so not starting a server on it ..." % adaptername
92         
93 ####            
94 def autostart(reason, **kwargs):
95         if "session" in kwargs:
96                 try:
97                         startWebserver(kwargs["session"])
98                 except ImportError,e:
99                         print "[WebIf] twisted not available, not starting web services",e
100                         
101 def openconfig(session, **kwargs):
102         session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
103
104 def configCB(result,session):
105         if result is True:
106                 print "[WebIf] config changed"
107                 restartWebserver(session)
108         else:
109                 print "[WebIf] config not changed"
110                 
111
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)]
115         
116         
117 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
118         """
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
122         """
123
124         def locateChild(self, req, seg):
125                 return self.authenticate(req), seg
126         
127 class PasswordDatabase:
128     """
129         this checks webiflogins agains /etc/passwd
130     """
131     passwordfile = "/etc/passwd"
132     implements(checkers.ICredentialsChecker)
133     credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
134
135     def _cbPasswordMatch(self, matched, username):
136         if matched:
137             return username
138         else:
139             return failure.Failure(error.UnauthorizedLogin())
140
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))
144         else:
145                 return defer.fail(error.UnauthorizedLogin())
146
147 class IHTTPUser(Interface):
148         pass
149
150 class HTTPUser(object):
151         implements(IHTTPUser)
152
153 class HTTPAuthRealm(object):
154         implements(IRealm)
155         def requestAvatar(self, avatarId, mind, *interfaces):
156                 if IHTTPUser in interfaces:
157                         return IHTTPUser, HTTPUser()
158                 raise NotImplementedError("Only IHTTPUser interface is supported")
159
160         
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.
165     
166     Example from the Python Library Reference.
167     """
168     
169     if not pwfile:
170         pwfile = '/etc/passwd'
171
172     f = open(pwfile)
173     while 1:
174         line = f.readline()
175         if not line:
176             f.close()
177             raise KeyError, name
178         entry = tuple(line.strip().split(':', 6))
179         if entry[0] == name:
180             f.close()
181             return entry
182
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':
190         return passwd
191
192 def check_passwd(name, passwd, pwfile=None):
193     """Validate given user, passwd pair against password database."""
194     
195     if not pwfile or type(pwfile) == type(''):
196         getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
197     else:
198         getuser = pwfile.get_passwd
199
200     try:
201         enc_passwd = getuser(name)
202     except (KeyError, IOError):
203         return 0
204     if not enc_passwd:
205         return 0
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')
209        
210     else:
211         return enc_passwd == passcrypt(passwd, enc_passwd[:2])
212
213 def _to64(v, n):
214     r = ''
215     while (n-1 >= 0):
216         r = r + DES_SALT[v & 0x3F]
217         v = v >> 6
218         n = n - 1
219     return r
220                         
221 def passcrypt_md5(passwd, salt=None, magic='$1$'):
222     """Encrypt passwd with MD5 algorithm."""
223     
224     if not salt:
225         pass
226     elif salt[:len(magic)] == magic:
227         # remove magic from salt if present
228         salt = salt[len(magic):]
229
230     # salt only goes up to first '$'
231     salt = string.split(salt, '$')[0]
232     # limit length of salt to 8
233     salt = salt[:8]
234
235     ctx = md5.new(passwd)
236     ctx.update(magic)
237     ctx.update(salt)
238     
239     ctx1 = md5.new(passwd)
240     ctx1.update(salt)
241     ctx1.update(passwd)
242     
243     final = ctx1.digest()
244     
245     for i in range(len(passwd), 0 , -16):
246         if i > 16:
247             ctx.update(final)
248         else:
249             ctx.update(final[:i])
250     
251     i = len(passwd)
252     while i:
253         if i & 1:
254             ctx.update('\0')
255         else:
256             ctx.update(passwd[:1])
257         i = i >> 1
258     final = ctx.digest()
259     
260     for i in range(1000):
261         ctx1 = md5.new()
262         if i & 1:
263             ctx1.update(passwd)
264         else:
265             ctx1.update(final)
266         if i % 3: ctx1.update(salt)
267         if i % 7: ctx1.update(passwd)
268         if i & 1:
269             ctx1.update(final)
270         else:
271             ctx1.update(passwd)
272         final = ctx1.digest()
273     
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)
286     l = final[11]
287     rv = rv + _to64(l, 2)
288     
289     return rv
290
291