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
26 server.VERSION = "Enigma2 WebInterface Server $Revision$".replace("$Revi","").replace("sion: ","").replace("$","")
30 def __init__(self,session, callback):
31 self.callback = callback
32 self.session = session
35 global running_defered
36 for d in running_defered:
37 print "[Webinterface] stopping interface on ",d.interface," with port",d.port
40 x.addCallback(self.isDown)
42 except AttributeError:
46 self.callback(self.session)
51 self.callback(self.session)
54 def restartWebserver(session):
56 del session.mediaplayer
57 del session.messageboxanswer
60 except AttributeError:
63 global running_defered
64 if len(running_defered) >0:
65 Closer(session,startWebserver).stop()
67 startWebserver(session)
69 def startWebserver(session):
70 global running_defered
71 session.mediaplayer = None
72 session.messageboxanswer = None
74 if config.plugins.Webinterface.enable.value is not True:
75 print "not starting Werbinterface"
78 print "start twisted logfile, writing to %s" % DEBUGFILE
79 startLogging(open(DEBUGFILE,'w'))
81 for i in range(0, config.plugins.Webinterface.interfacecount.value):
82 c = config.plugins.Webinterface.interfaces[i]
83 if c.disabled.value is False:
84 startServerInstance(session,c.adress.value,c.port.value,c.useauth.value,c.usessl.value)
86 print "[Webinterface] not starting disabled interface on %s:%i"%(c.adress.value,c.port.value)
88 def startServerInstance(session,ipadress,port,useauth=False,usessl=False):
90 toplevel = Toplevel(session)
92 portal = Portal(HTTPAuthRealm())
93 portal.registerChecker(PasswordDatabase())
94 root = wrapper.HTTPAuthResource(toplevel,(basic.BasicCredentialFactory(socket_gethostname()),),portal, (IHTTPUser,))
95 site = server.Site(root)
97 site = server.Site(toplevel)
100 ctx = ssl.DefaultOpenSSLContextFactory('/etc/enigma2/server.pem','/etc/enigma2/cacert.pem',sslmethod=SSL.SSLv23_METHOD)
101 d = reactor.listenSSL(port, channel.HTTPFactory(site),ctx,interface=ipadress)
103 d = reactor.listenTCP(port, channel.HTTPFactory(site),interface=ipadress)
104 running_defered.append(d)
105 print "[Webinterface] started on %s:%i"%(ipadress,port),"auth=",useauth,"ssl=",usessl
106 except CannotListenError, e:
107 print "[Webinterface] Could not Listen on %s:%i!"%(ipadress,port)
108 session.open(MessageBox,'Could not Listen on %s:%i!\n\n%s'%(ipadress,port,str(e)), MessageBox.TYPE_ERROR)
110 print "[Webinterface] starting FAILED on %s:%i!"%(ipadress,port),e
111 session.open(MessageBox,'starting FAILED on %s:%i!\n\n%s'%(ipadress,port,str(e)), MessageBox.TYPE_ERROR)
113 def autostart(reason, **kwargs):
114 if "session" in kwargs:
116 startWebserver(kwargs["session"])
117 except ImportError,e:
118 print "[Webinterface] twisted not available, not starting web services",e
120 def openconfig(session, **kwargs):
121 session.openWithCallback(configCB,WebIfConfigScreen)
123 def configCB(result,session):
125 print "[WebIf] config changed"
126 restartWebserver(session)
128 print "[WebIf] config not changed"
131 def Plugins(**kwargs):
132 return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart),
133 PluginDescriptor(name=_("Webinterface"), description=_("Configuration for the Webinterface"),where = [PluginDescriptor.WHERE_PLUGINMENU], icon="plugin.png",fnc = openconfig)]
137 class PasswordDatabase:
139 this checks webiflogins agains /etc/passwd
141 passwordfile = "/etc/passwd"
142 implements(checkers.ICredentialsChecker)
143 credentialInterfaces = (credentials.IUsernamePassword,credentials.IUsernameHashedPassword)
145 def _cbPasswordMatch(self, matched, username):
149 return failure.Failure(error.UnauthorizedLogin())
151 def requestAvatarId(self, credentials):
152 if check_passwd(credentials.username,credentials.password,self.passwordfile) is True:
153 return defer.maybeDeferred(credentials.checkPassword,credentials.password).addCallback(self._cbPasswordMatch, str(credentials.username))
155 return defer.fail(error.UnauthorizedLogin())
157 class IHTTPUser(Interface):
160 class HTTPUser(object):
161 implements(IHTTPUser)
163 def __init__(self,username):
164 self.username = username
166 class HTTPAuthRealm(object):
168 def requestAvatar(self, avatarId, mind, *interfaces):
169 if IHTTPUser in interfaces:
170 return IHTTPUser, HTTPUser(avatarId)
171 raise NotImplementedError("Only IHTTPUser interface is supported")
174 from string import find, split
175 from md5 import new as md5_new
176 from crypt import crypt
178 DES_SALT = list('./0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz')
179 def getpwnam(name, pwfile=None):
180 """Return pasword database entry for the given user name.
182 Example from the Python Library Reference.
186 pwfile = '/etc/passwd'
194 entry = tuple(line.strip().split(':', 6))
199 def passcrypt(passwd, salt=None, method='des', magic='$1$'):
200 """Encrypt a string according to rules in crypt(3)."""
201 if method.lower() == 'des':
202 return crypt(passwd, salt)
203 elif method.lower() == 'md5':
204 return passcrypt_md5(passwd, salt, magic)
205 elif method.lower() == 'clear':
208 def check_passwd(name, passwd, pwfile=None):
209 """Validate given user, passwd pair against password database."""
211 if not pwfile or type(pwfile) == type(''):
212 getuser = lambda x,pwfile=pwfile: getpwnam(x,pwfile)[1]
214 getuser = pwfile.get_passwd
217 enc_passwd = getuser(name)
218 except (KeyError, IOError):
222 elif len(enc_passwd) >= 3 and enc_passwd[:3] == '$1$':
223 salt = enc_passwd[3:find(enc_passwd, '$', 3)]
224 return enc_passwd == passcrypt(passwd, salt, 'md5')
227 return enc_passwd == passcrypt(passwd, enc_passwd[:2])
232 r = r + DES_SALT[v & 0x3F]
237 def passcrypt_md5(passwd, salt=None, magic='$1$'):
238 """Encrypt passwd with MD5 algorithm."""
242 elif salt[:len(magic)] == magic:
243 # remove magic from salt if present
244 salt = salt[len(magic):]
246 # salt only goes up to first '$'
247 salt = split(salt, '$')[0]
248 # limit length of salt to 8
251 ctx = md5_new(passwd)
255 ctx1 = md5_new(passwd)
259 final = ctx1.digest()
261 for i in range(len(passwd), 0 , -16):
265 ctx.update(final[:i])
272 ctx.update(passwd[:1])
276 for i in range(1000):
282 if i % 3: ctx1.update(salt)
283 if i % 7: ctx1.update(passwd)
288 final = ctx1.digest()
290 rv = magic + salt + '$'
291 final = map(ord, final)
292 l = (final[0] << 16) + (final[6] << 8) + final[12]
293 rv = rv + _to64(l, 4)
294 l = (final[1] << 16) + (final[7] << 8) + final[13]
295 rv = rv + _to64(l, 4)
296 l = (final[2] << 16) + (final[8] << 8) + final[14]
297 rv = rv + _to64(l, 4)
298 l = (final[3] << 16) + (final[9] << 8) + final[15]
299 rv = rv + _to64(l, 4)
300 l = (final[4] << 16) + (final[10] << 8) + final[5]
301 rv = rv + _to64(l, 4)
303 rv = rv + _to64(l, 2)
307 #### stuff for SSL Support
308 def makeSSLContext(myKey,trustedCA):
309 '''Returns an ssl Context Object
310 @param myKey a pem formated key and certifcate with for my current host
311 the other end of this connection must have the cert from the CA
313 @param trustedCA a pem formated certificat from a CA you trust
314 you will only allow connections from clients signed by this CA
315 and you will only allow connections to a server signed by this CA
318 # our goal in here is to make a SSLContext object to pass to connectSSL
321 # Why these functioins... Not sure...
324 theCert = ssl.PrivateCertificate.loadPEM(ss)
326 fd = open(trustedCA,'r')
327 theCA = ssl.Certificate.loadPEM(fd.read())
329 #ctx = theCert.options(theCA)
330 ctx = theCert.options()
332 # Now the options you can set look like Standard OpenSSL Library options
334 # The SSL protocol to use, one of SSLv23_METHOD, SSLv2_METHOD,
335 # SSLv3_METHOD, TLSv1_METHOD. Defaults to TLSv1_METHOD.
336 ctx.method = ssl.SSL.TLSv1_METHOD
338 # If True, verify certificates received from the peer and fail
339 # the handshake if verification fails. Otherwise, allow anonymous
340 # sessions and sessions with certificates which fail validation.
343 # Depth in certificate chain down to which to verify.
346 # If True, do not allow anonymous sessions.
347 ctx.requireCertification = True
349 # If True, do not re-verify the certificate on session resumption.
350 ctx.verifyOnce = True
352 # If True, generate a new key whenever ephemeral DH parameters are used
353 # to prevent small subgroup attacks.
354 ctx.enableSingleUseKeys = True
356 # If True, set a session ID on each context. This allows a shortened
357 # handshake to be used when a known client reconnects.
358 ctx.enableSessions = True
360 # If True, enable various non-spec protocol fixes for broken
361 # SSL implementations.
362 ctx.fixBrokenPeers = False