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