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