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