2 from Plugins.Plugin import PluginDescriptor
3 from Components.config import config
4 from Screens.MessageBox import MessageBox
5 from WebIfConfig import WebIfConfigScreen
6 from WebChilds.Toplevel import Toplevel
7 from twisted.internet import reactor, defer
8 from twisted.internet.error import CannotListenError
9 from twisted.web2 import server, channel, http
10 from twisted.web2.auth import digest, basic, wrapper
11 from twisted.python.log import startLogging
12 from twisted.cred.portal import Portal, IRealm
13 from twisted.cred import checkers, credentials, error
14 from zope.interface import Interface, implements
15 from socket import gethostname as socket_gethostname
16 from OpenSSL import SSL
17 from twisted.internet import reactor, defer, ssl
19 DEBUG_TO_FILE=False # PLEASE DONT ENABLE LOGGING BY DEFAULT (OR COMMIT TO PLUGIN CVS)
21 DEBUGFILE= "/tmp/twisted.log"
23 global running_defered,waiting_shutdown
29 def __init__(self,session, callback):
30 self.callback = callback
31 self.session = session
34 global running_defered
35 for d in running_defered:
36 print "[Webinterface] stopping interface on ",d.interface," with port",d.port
39 x.addCallback(self.isDown)
41 except AttributeError:
45 self.callback(self.session)
50 self.callback(self.session)
53 def restartWebserver(session):
55 del session.mediaplayer
56 del session.messageboxanswer
59 except AttributeError:
62 global running_defered
63 if len(running_defered) >0:
64 Closer(session,startWebserver).stop()
66 startWebserver(session)
68 def startWebserver(session):
69 global running_defered
70 session.mediaplayer = None
71 session.messageboxanswer = None
73 if config.plugins.Webinterface.enable.value is not True:
74 print "not starting Werbinterface"
77 print "start twisted logfile, writing to %s" % DEBUGFILE
78 startLogging(open(DEBUGFILE,'w'))
80 for i in range(0, config.plugins.Webinterface.interfacecount.value):
81 c = config.plugins.Webinterface.interfaces[i]
82 if c.disabled.value is False:
83 startServerInstance(session,c.adress.value,c.port.value,c.useauth.value,c.usessl.value)
85 print "[Webinterface] not starting disabled interface on %s:%i"%(c.adress.value,c.port.value)
87 def startServerInstance(session,ipadress,port,useauth=False,usessl=False):
89 toplevel = Toplevel(session)
91 portal = Portal(HTTPAuthRealm())
92 portal.registerChecker(PasswordDatabase())
93 root = ModifiedHTTPAuthResource(toplevel,(basic.BasicCredentialFactory(socket_gethostname()),),portal, (IHTTPUser,))
94 site = server.Site(root)
96 site = server.Site(toplevel)
99 ctx = ssl.DefaultOpenSSLContextFactory('/etc/enigma2/server.pem','/etc/enigma2/cacert.pem',sslmethod=SSL.SSLv23_METHOD)
100 d = reactor.listenSSL(port, channel.HTTPFactory(site),ctx,interface=ipadress)
102 d = reactor.listenTCP(port, channel.HTTPFactory(site),interface=ipadress)
103 running_defered.append(d)
104 print "[Webinterface] started on %s:%i"%(ipadress,port),"auth=",useauth,"ssl=",usessl
105 except CannotListenError, e:
106 print "[Webinterface] Could not Listen on %s:%i!"%(ipadress,port)
107 session.open(MessageBox,'Could not Listen on %s:%i!\n\n%s'%(ipadress,port,str(e)), MessageBox.TYPE_ERROR)
109 print "[Webinterface] starting FAILED on %s:%i!"%(ipadress,port),e
110 session.open(MessageBox,'starting FAILED on %s:%i!\n\n%s'%(ipadress,port,str(e)), MessageBox.TYPE_ERROR)
112 def autostart(reason, **kwargs):
113 if "session" in kwargs:
115 startWebserver(kwargs["session"])
116 except ImportError,e:
117 print "[Webinterface] twisted not available, not starting web services",e
119 def openconfig(session, **kwargs):
120 session.openWithCallback(configCB,WebIfConfigScreen)
122 def configCB(result,session):
124 print "[WebIf] config changed"
125 restartWebserver(session)
127 print "[WebIf] config not changed"
130 def Plugins(**kwargs):
131 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
132 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
135 class ModifiedHTTPAuthResource(wrapper.HTTPAuthResource):
137 set it only to True, if you have a patched wrapper.py
138 see http://twistedmatrix.com/trac/ticket/2041
139 so, the solution for us is to make a new class an override ne faulty func
142 def locateChild(self, req, seg):
143 return self.authenticate(req), seg
145 class PasswordDatabase:
147 this checks webiflogins agains /etc/passwd
149 passwordfile = "/etc/passwd"
150 implements(checkers.ICredentialsChecker)
151 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
153 def _cbPasswordMatch(self, matched, username):
157 return failure.Failure(error.UnauthorizedLogin())
159 def requestAvatarId(self, credentials):
160 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
161 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
163 return defer.fail(error.UnauthorizedLogin())
165 class IHTTPUser(Interface):
168 class HTTPUser(object):
169 implements(IHTTPUser)
171 class HTTPAuthRealm(object):
173 def requestAvatar(self, avatarId, mind, *interfaces):
174 if IHTTPUser in interfaces:
175 return IHTTPUser, HTTPUser()
176 raise NotImplementedError("Only IHTTPUser interface is supported")
179 from string import find, split
180 from md5 import new as md5_new
181 from crypt import crypt
183 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
184 def getpwnam(name, pwfile=None):
185 """Return pasword database entry for the given user name.
187 Example from the Python Library Reference.
191 pwfile = '/etc/passwd'
199 entry = tuple(line.strip().split(':', 6))
204 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
205 """Encrypt a string according to rules in crypt(3)."""
206 if method.lower() == 'des':
207 return crypt(passwd, salt)
208 elif method.lower() == 'md5':
209 return passcrypt_md5(passwd, salt, magic)
210 elif method.lower() == 'clear':
213 def check_passwd(name, passwd, pwfile=None):
214 """Validate given user, passwd pair against password database."""
216 if not pwfile or type(pwfile) == type(''):
217 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
219 getuser = pwfile.get_passwd
222 enc_passwd = getuser(name)
223 except (KeyError, IOError):
227 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
228 salt = enc_passwd[3:find(enc_passwd, '$', 3)]
229 return enc_passwd == passcrypt(passwd, salt, 'md5')
232 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
237 r = r + DES_SALT[v & 0x3F]
242 def passcrypt_md5(passwd, salt=None, magic='$1$'):
243 """Encrypt passwd with MD5 algorithm."""
247 elif salt[:len(magic)] == magic:
248 # remove magic from salt if present
249 salt = salt[len(magic):]
251 # salt only goes up to first '$'
252 salt = split(salt, '$')[0]
253 # limit length of salt to 8
256 ctx = md5_new(passwd)
260 ctx1 = md5_new(passwd)
264 final = ctx1.digest()
266 for i in range(len(passwd), 0 , -16):
270 ctx.update(final[:i])
277 ctx.update(passwd[:1])
281 for i in range(1000):
287 if i % 3: ctx1.update(salt)
288 if i % 7: ctx1.update(passwd)
293 final = ctx1.digest()
295 rv = magic + salt + '$'
296 final = map(ord, final)
297 l = (final[0] << 16) + (final[6] << 8) + final[12]
298 rv = rv + _to64(l, 4)
299 l = (final[1] << 16) + (final[7] << 8) + final[13]
300 rv = rv + _to64(l, 4)
301 l = (final[2] << 16) + (final[8] << 8) + final[14]
302 rv = rv + _to64(l, 4)
303 l = (final[3] << 16) + (final[9] << 8) + final[15]
304 rv = rv + _to64(l, 4)
305 l = (final[4] << 16) + (final[10] << 8) + final[5]
306 rv = rv + _to64(l, 4)
308 rv = rv + _to64(l, 2)
312 #### stuff for SSL Support
313 def makeSSLContext(myKey,trustedCA):
314 '''Returns an ssl Context Object
315 @param myKey a pem formated key and certifcate with for my current host
316 the other end of this connection must have the cert from the CA
318 @param trustedCA a pem formated certificat from a CA you trust
319 you will only allow connections from clients signed by this CA
320 and you will only allow connections to a server signed by this CA
323 # our goal in here is to make a SSLContext object to pass to connectSSL
326 # Why these functioins... Not sure...
329 theCert = ssl.PrivateCertificate.loadPEM(ss)
331 fd = open(trustedCA,'r')
332 theCA = ssl.Certificate.loadPEM(fd.read())
334 #ctx = theCert.options(theCA)
335 ctx = theCert.options()
337 # Now the options you can set look like Standard OpenSSL Library options
339 # The SSL protocol to use, one of SSLv23_METHOD, SSLv2_METHOD,
340 # SSLv3_METHOD, TLSv1_METHOD. Defaults to TLSv1_METHOD.
341 ctx.method = ssl.SSL.TLSv1_METHOD
343 # If True, verify certificates received from the peer and fail
344 # the handshake if verification fails. Otherwise, allow anonymous
345 # sessions and sessions with certificates which fail validation.
348 # Depth in certificate chain down to which to verify.
351 # If True, do not allow anonymous sessions.
352 ctx.requireCertification = True
354 # If True, do not re-verify the certificate on session resumption.
355 ctx.verifyOnce = True
357 # If True, generate a new key whenever ephemeral DH parameters are used
358 # to prevent small subgroup attacks.
359 ctx.enableSingleUseKeys = True
361 # If True, set a session ID on each context. This allows a shortened
362 # handshake to be used when a known client reconnects.
363 ctx.enableSessions = True
365 # If True, enable various non-spec protocol fixes for broken
366 # SSL implementations.
367 ctx.fixBrokenPeers = False