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, ssl
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
18 from __init__ import _
20 DEBUG_TO_FILE=False # PLEASE DONT ENABLE LOGGING BY DEFAULT (OR COMMIT TO PLUGIN CVS)
22 DEBUGFILE= "/tmp/twisted.log"
24 global running_defered,waiting_shutdown
27 server.VERSION = "Enigma2 WebInterface Server $Revision$".replace("$Revi","").replace("sion: ","").replace("$","")
31 def __init__(self,session, callback):
32 self.callback = callback
33 self.session = session
36 global running_defered
37 for d in running_defered:
38 print "[Webinterface] stopping interface on ",d.interface," with port",d.port
41 x.addCallback(self.isDown)
43 except AttributeError:
47 self.callback(self.session)
52 self.callback(self.session)
55 def restartWebserver(session):
57 del session.mediaplayer
58 del session.messageboxanswer
61 except AttributeError:
64 global running_defered
65 if len(running_defered) >0:
66 Closer(session,startWebserver).stop()
68 startWebserver(session)
70 def startWebserver(session):
71 global running_defered
72 session.mediaplayer = None
73 session.messageboxanswer = None
75 if config.plugins.Webinterface.enable.value is not True:
76 print "not starting Werbinterface"
79 print "start twisted logfile, writing to %s" % DEBUGFILE
80 startLogging(open(DEBUGFILE,'w'))
82 for i in range(0, config.plugins.Webinterface.interfacecount.value):
83 c = config.plugins.Webinterface.interfaces[i]
84 if c.disabled.value is False:
85 startServerInstance(session,c.address.value,c.port.value,c.useauth.value,c.usessl.value)
87 print "[Webinterface] not starting disabled interface on %s:%i"%(c.address.value,c.port.value)
89 def startServerInstance(session,ipaddress,port,useauth=False,usessl=False):
91 toplevel = Toplevel(session)
93 portal = Portal(HTTPAuthRealm())
94 portal.registerChecker(PasswordDatabase())
95 root = wrapper.HTTPAuthResource(toplevel,(basic.BasicCredentialFactory(socket_gethostname()),),portal, (IHTTPUser,))
96 site = server.Site(root)
98 site = server.Site(toplevel)
101 ctx = ssl.DefaultOpenSSLContextFactory('/etc/enigma2/server.pem','/etc/enigma2/cacert.pem',sslmethod=SSL.SSLv23_METHOD)
102 d = reactor.listenSSL(port, channel.HTTPFactory(site),ctx,interface=ipaddress)
104 d = reactor.listenTCP(port, channel.HTTPFactory(site),interface=ipaddress)
105 running_defered.append(d)
106 print "[Webinterface] started on %s:%i"%(ipaddress,port),"auth=",useauth,"ssl=",usessl
107 except CannotListenError, e:
108 print "[Webinterface] Could not Listen on %s:%i!"%(ipaddress,port)
109 session.open(MessageBox,'Could not Listen on %s:%i!\n\n%s'%(ipaddress,port,str(e)), MessageBox.TYPE_ERROR)
111 print "[Webinterface] starting FAILED on %s:%i!"%(ipaddress,port),e
112 session.open(MessageBox,'starting FAILED on %s:%i!\n\n%s'%(ipaddress,port,str(e)), MessageBox.TYPE_ERROR)
114 def autostart(reason, **kwargs):
115 if "session" in kwargs:
117 startWebserver(kwargs["session"])
118 except ImportError,e:
119 print "[Webinterface] twisted not available, not starting web services",e
121 def openconfig(session, **kwargs):
122 session.openWithCallback(configCB,WebIfConfigScreen)
124 def configCB(result,session):
126 print "[WebIf] config changed"
127 restartWebserver(session)
129 print "[WebIf] config not changed"
132 def Plugins(**kwargs):
133 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
134 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
138 class PasswordDatabase:
140 this checks webiflogins agains /etc/passwd
142 passwordfile = "/etc/passwd"
143 implements(checkers.ICredentialsChecker)
144 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
146 def _cbPasswordMatch(self, matched, username):
150 return failure.Failure(error.UnauthorizedLogin())
152 def requestAvatarId(self, credentials):
153 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
154 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
156 return defer.fail(error.UnauthorizedLogin())
158 class IHTTPUser(Interface):
161 class HTTPUser(object):
162 implements(IHTTPUser)
164 def __init__(self,username):
165 self.username = username
167 class HTTPAuthRealm(object):
169 def requestAvatar(self, avatarId, mind, *interfaces):
170 if IHTTPUser in interfaces:
171 return IHTTPUser, HTTPUser(avatarId)
172 raise NotImplementedError("Only IHTTPUser interface is supported")
174 from hashlib import md5 as md5_new
175 from crypt import crypt
177 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
178 def getpwnam(name, pwfile=None):
179 """Return pasword database entry for the given user name.
181 Example from the Python Library Reference.
185 pwfile = '/etc/passwd'
193 entry = tuple(line.strip().split(':', 6))
198 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
199 """Encrypt a string according to rules in crypt(3)."""
200 if method.lower() == 'des':
201 return crypt(passwd, salt)
202 elif method.lower() == 'md5':
203 return passcrypt_md5(passwd, salt, magic)
204 elif method.lower() == 'clear':
207 def check_passwd(name, passwd, pwfile=None):
208 """Validate given user, passwd pair against password database."""
210 if not pwfile or type(pwfile) == type(''):
211 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
213 getuser = pwfile.get_passwd
216 enc_passwd = getuser(name)
217 except (KeyError, IOError):
221 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
222 salt = enc_passwd[3:enc_passwd.find('$', 3)]
223 return enc_passwd == passcrypt(passwd, salt, 'md5')
226 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
231 r = r + DES_SALT[v & 0x3F]
236 def passcrypt_md5(passwd, salt=None, magic='$1$'):
237 """Encrypt passwd with MD5 algorithm."""
241 elif salt[:len(magic)] == magic:
242 # remove magic from salt if present
243 salt = salt[len(magic):]
245 # salt only goes up to first '$'
246 salt = salt.split('$')[0]
247 # limit length of salt to 8
250 ctx = md5_new(passwd)
254 ctx1 = md5_new(passwd)
258 final = ctx1.digest()
260 for i in range(len(passwd), 0 , -16):
264 ctx.update(final[:i])
271 ctx.update(passwd[:1])
275 for i in range(1000):
281 if i % 3: ctx1.update(salt)
282 if i % 7: ctx1.update(passwd)
287 final = ctx1.digest()
289 rv = magic + salt + '$'
290 final = map(ord, final)
291 l = (final[0] << 16) + (final[6] << 8) + final[12]
292 rv = rv + _to64(l, 4)
293 l = (final[1] << 16) + (final[7] << 8) + final[13]
294 rv = rv + _to64(l, 4)
295 l = (final[2] << 16) + (final[8] << 8) + final[14]
296 rv = rv + _to64(l, 4)
297 l = (final[3] << 16) + (final[9] << 8) + final[15]
298 rv = rv + _to64(l, 4)
299 l = (final[4] << 16) + (final[10] << 8) + final[5]
300 rv = rv + _to64(l, 4)
302 rv = rv + _to64(l, 2)
306 #### stuff for SSL Support
307 def makeSSLContext(myKey,trustedCA):
308 '''Returns an ssl Context Object
309 @param myKey a pem formated key and certifcate with for my current host
310 the other end of this connection must have the cert from the CA
312 @param trustedCA a pem formated certificat from a CA you trust
313 you will only allow connections from clients signed by this CA
314 and you will only allow connections to a server signed by this CA
317 # our goal in here is to make a SSLContext object to pass to connectSSL
320 # Why these functioins... Not sure...
323 theCert = ssl.PrivateCertificate.loadPEM(ss)
325 fd = open(trustedCA,'r')
326 theCA = ssl.Certificate.loadPEM(fd.read())
328 #ctx = theCert.options(theCA)
329 ctx = theCert.options()
331 # Now the options you can set look like Standard OpenSSL Library options
333 # The SSL protocol to use, one of SSLv23_METHOD, SSLv2_METHOD,
334 # SSLv3_METHOD, TLSv1_METHOD. Defaults to TLSv1_METHOD.
335 ctx.method = ssl.SSL.TLSv1_METHOD
337 # If True, verify certificates received from the peer and fail
338 # the handshake if verification fails. Otherwise, allow anonymous
339 # sessions and sessions with certificates which fail validation.
342 # Depth in certificate chain down to which to verify.
345 # If True, do not allow anonymous sessions.
346 ctx.requireCertification = True
348 # If True, do not re-verify the certificate on session resumption.
349 ctx.verifyOnce = True
351 # If True, generate a new key whenever ephemeral DH parameters are used
352 # to prevent small subgroup attacks.
353 ctx.enableSingleUseKeys = True
355 # If True, set a session ID on each context. This allows a shortened
356 # handshake to be used when a known client reconnects.
357 ctx.enableSessions = True
359 # If True, enable various non-spec protocol fixes for broken
360 # SSL implementations.
361 ctx.fixBrokenPeers = False