enable/disable autowrite of timer to flash after change/add via config
[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.autowritetimer = ConfigYesNo(default = False)
27 config.plugins.Webinterface.debug = ConfigYesNo(default = False) # False by default, not confgurable in GUI. Edit settingsfile directly if needed
28 config.plugins.Webinterface.version = ConfigText(__version__) # used to make the versioninfo accessible enigma2-wide, not confgurable in GUI. 
29  
30
31 """
32  set DEBUG to True, if twisted should write logoutput to a file.
33  in normal console output, twisted will print only the first Traceback.
34  is this a bug in twisted or a conflict with enigma2?
35  with this option enabled, twisted will print all TB to the logfile
36  use tail -f <file> to view this log
37 """
38                         
39
40 DEBUGFILE= "/tmp/twisted.log"
41
42 def stopWebserver():
43         reactor.disconnectAll()
44         del session.mediaplayer
45         #del session.messageboxanswer
46
47 def restartWebserver(session):
48         stopWebserver()
49         startWebserver(session)
50
51 def startWebserver(session):
52         # variables, that are needed in the process
53         session.mediaplayer = None
54         #session.messageboxanswer = None
55         
56         if config.plugins.Webinterface.enable.value is not True:
57                 print "not starting Werbinterface"
58                 return False
59         if config.plugins.Webinterface.debug.value:
60                 print "start twisted logfile, writing to %s" % DEBUGFILE 
61                 import sys
62                 startLogging(open(DEBUGFILE,'w'))
63
64         toplevel = Toplevel(session)
65         if config.plugins.Webinterface.useauth.value is False:
66                 site = server.Site(toplevel)
67         else:
68                 portal = Portal(HTTPAuthRealm())
69                 portal.registerChecker(PasswordDatabase())
70                 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory('DM7025'),),portal, (IHTTPUser,))
71                 site = server.Site(root)
72         
73         # here we start the Toplevel without any username or password
74         # this allows access to all request over the iface 127.0.0.1 without any auth
75         localsite = server.Site(toplevel)
76         reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(localsite),interface='127.0.0.1')
77         
78         # and here we make the Toplevel public to our external ifaces
79         # it depends on the config, if this is with auth support
80         # 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         nw = Network()
82         for adaptername in nw.ifaces:
83                 extip = nw.ifaces[adaptername]['ip']
84                 if nw.ifaces[adaptername]['up'] is True:
85                         extip = "%i.%i.%i.%i"%(extip[0],extip[1],extip[2],extip[3])
86                         print "[WebIf] starting Webinterface on port %s on interface %s with address %s"%(str(config.plugins.Webinterface.port.value),adaptername,extip)
87                         try:
88                                 reactor.listenTCP(config.plugins.Webinterface.port.value, channel.HTTPFactory(site),interface=extip)
89                         except Exception,e:
90                                 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                 else:
92                         print "[WebIf] found configured interface %s, but it is not running. so not starting a server on it ..." % adaptername
93         
94 ####            
95 def autostart(reason, **kwargs):
96         if "session" in kwargs:
97                 try:
98                         startWebserver(kwargs["session"])
99                 except ImportError,e:
100                         print "[WebIf] twisted not available, not starting web services",e
101                         
102 def openconfig(session, **kwargs):
103         session.openWithCallback(configCB,WebIfConfig.WebIfConfigScreen)
104
105 def configCB(result,session):
106         if result is True:
107                 print "[WebIf] config changed"
108                 restartWebserver(session)
109         else:
110                 print "[WebIf] config not changed"
111                 
112
113 def Plugins(**kwargs):
114         return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
115                     PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
116         
117         
118 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
119         """
120                 set it only to True, if you have a patched wrapper.py
121                 see http://twistedmatrix.com/trac/ticket/2041
122                 so, the solution for us is to make a new class an override ne faulty func
123         """
124
125         def locateChild(self, req, seg):
126                 return self.authenticate(req), seg
127         
128 class PasswordDatabase:
129     """
130         this checks webiflogins agains /etc/passwd
131     """
132     passwordfile = "/etc/passwd"
133     implements(checkers.ICredentialsChecker)
134     credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
135
136     def _cbPasswordMatch(self, matched, username):
137         if matched:
138             return username
139         else:
140             return failure.Failure(error.UnauthorizedLogin())
141
142     def requestAvatarId(self, credentials):     
143         if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
144                 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
145         else:
146                 return defer.fail(error.UnauthorizedLogin())
147
148 class IHTTPUser(Interface):
149         pass
150
151 class HTTPUser(object):
152         implements(IHTTPUser)
153
154 class HTTPAuthRealm(object):
155         implements(IRealm)
156         def requestAvatar(self, avatarId, mind, *interfaces):
157                 if IHTTPUser in interfaces:
158                         return IHTTPUser, HTTPUser()
159                 raise NotImplementedError("Only IHTTPUser interface is supported")
160
161         
162 import md5,time,string,crypt
163 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz') 
164 def getpwnam(name, pwfile=None):
165     """Return pasword database entry for the given user name.
166     
167     Example from the Python Library Reference.
168     """
169     
170     if not pwfile:
171         pwfile = '/etc/passwd'
172
173     f = open(pwfile)
174     while 1:
175         line = f.readline()
176         if not line:
177             f.close()
178             raise KeyError, name
179         entry = tuple(line.strip().split(':', 6))
180         if entry[0] == name:
181             f.close()
182             return entry
183
184 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
185     """Encrypt a string according to rules in crypt(3)."""
186     if method.lower() == 'des':
187             return crypt.crypt(passwd, salt)
188     elif method.lower() == 'md5':
189         return passcrypt_md5(passwd, salt, magic)
190     elif method.lower() == 'clear':
191         return passwd
192
193 def check_passwd(name, passwd, pwfile=None):
194     """Validate given user, passwd pair against password database."""
195     
196     if not pwfile or type(pwfile) == type(''):
197         getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
198     else:
199         getuser = pwfile.get_passwd
200
201     try:
202         enc_passwd = getuser(name)
203     except (KeyError, IOError):
204         return 0
205     if not enc_passwd:
206         return 0
207     elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
208         salt = enc_passwd[3:string.find(enc_passwd, '$', 3)]
209         return enc_passwd == passcrypt(passwd, salt, 'md5')
210        
211     else:
212         return enc_passwd == passcrypt(passwd, enc_passwd[:2])
213
214 def _to64(v, n):
215     r = ''
216     while (n-1 >= 0):
217         r = r + DES_SALT[v & 0x3F]
218         v = v >> 6
219         n = n - 1
220     return r
221                         
222 def passcrypt_md5(passwd, salt=None, magic='$1$'):
223     """Encrypt passwd with MD5 algorithm."""
224     
225     if not salt:
226         pass
227     elif salt[:len(magic)] == magic:
228         # remove magic from salt if present
229         salt = salt[len(magic):]
230
231     # salt only goes up to first '$'
232     salt = string.split(salt, '$')[0]
233     # limit length of salt to 8
234     salt = salt[:8]
235
236     ctx = md5.new(passwd)
237     ctx.update(magic)
238     ctx.update(salt)
239     
240     ctx1 = md5.new(passwd)
241     ctx1.update(salt)
242     ctx1.update(passwd)
243     
244     final = ctx1.digest()
245     
246     for i in range(len(passwd), 0 , -16):
247         if i > 16:
248             ctx.update(final)
249         else:
250             ctx.update(final[:i])
251     
252     i = len(passwd)
253     while i:
254         if i & 1:
255             ctx.update('\0')
256         else:
257             ctx.update(passwd[:1])
258         i = i >> 1
259     final = ctx.digest()
260     
261     for i in range(1000):
262         ctx1 = md5.new()
263         if i & 1:
264             ctx1.update(passwd)
265         else:
266             ctx1.update(final)
267         if i % 3: ctx1.update(salt)
268         if i % 7: ctx1.update(passwd)
269         if i & 1:
270             ctx1.update(final)
271         else:
272             ctx1.update(passwd)
273         final = ctx1.digest()
274     
275     rv = magic + salt + '$'
276     final = map(ord, final)
277     l = (final[0] << 16) + (final[6] << 8) + final[12]
278     rv = rv + _to64(l, 4)
279     l = (final[1] << 16) + (final[7] << 8) + final[13]
280     rv = rv + _to64(l, 4)
281     l = (final[2] << 16) + (final[8] << 8) + final[14]
282     rv = rv + _to64(l, 4)
283     l = (final[3] << 16) + (final[9] << 8) + final[15]
284     rv = rv + _to64(l, 4)
285     l = (final[4] << 16) + (final[10] << 8) + final[5]
286     rv = rv + _to64(l, 4)
287     l = final[11]
288     rv = rv + _to64(l, 2)
289     
290     return rv
291
292