mirror of
https://github.com/mumble-voip/mumble.git
synced 2025-10-26 11:19:16 +00:00
276 lines
11 KiB
Python
276 lines
11 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8
|
|
#
|
|
# mumble-auth.py - Sample script to show the basics of using server controlled
|
|
# context menu entries for in-client registration tickets.
|
|
#
|
|
# Requirements:
|
|
# * python 2.5 or higher
|
|
# * cherrypy 3 or higher
|
|
# * python ice bindings
|
|
# * murmur + mumble rev 1530 or higher
|
|
|
|
import cherrypy
|
|
import Ice
|
|
from threading import Semaphore
|
|
import random, time
|
|
|
|
#
|
|
#--- Config stuff
|
|
#
|
|
baseurl = "http://localhost:8080/register?id=" # Baseurl for registrations
|
|
storage_time = 10*24*60*60 # Time in seconds a reg entry is guaranteed to be valid
|
|
proxy = "Meta:tcp -h 127.0.0.1 -p 6502"
|
|
group = "admin" # ACL group which has the right to send regurls
|
|
host = "0.0.0.0" # Interface to listen on
|
|
port = 8080 # Cherrypy port
|
|
slice = "Murmur.ice"
|
|
production = False # Set this to true to surpress tracebacks
|
|
|
|
# Runtime generate bindings
|
|
Ice.loadSlice(slice)
|
|
import Murmur
|
|
|
|
# Global vars
|
|
sema_ids = Semaphore()
|
|
ids = {} #Contains list(sid, user, time)
|
|
|
|
|
|
class MetaCallbackI(Murmur.MetaCallback):
|
|
def started(self, s, current=None):
|
|
serverR = Murmur.ServerCallbackPrx.uncheckedCast(adapter.addWithUUID(ServerCallbackI(server, current.adapter)))
|
|
s.addCallback(serverR)
|
|
def stopped(self, s, current=None): pass # Unused callback
|
|
|
|
class ServerCallbackI(Murmur.ServerCallback):
|
|
def __init__(self, server, adapter):
|
|
self.server = server
|
|
self.contextR=Murmur.ServerContextCallbackPrx.uncheckedCast(adapter.addWithUUID(ServerContextCallbackI(server)))
|
|
|
|
def userConnected(self, p, current=None):
|
|
if p.userid != 0: # SuperUser is always allowed
|
|
# Check if the user is in the right acl class and add the context menu
|
|
allowed = False
|
|
for acl in self.server.getACL(0)[1]:
|
|
if acl.name == group and p.userid in acl.members:
|
|
allowed = True
|
|
break
|
|
if not allowed:
|
|
return
|
|
|
|
self.server.addContextCallback(p.session,
|
|
"sendregurl",
|
|
"Send registration URL",
|
|
self.contextR, Murmur.ContextUser)
|
|
|
|
def userDisconnected(self, p, current=None): pass # Unused callbacks
|
|
def userStateChanged(self, p, current=None): pass
|
|
def channelCreated(self, c, current=None): pass
|
|
def channelRemoved(self, c, current=None): pass
|
|
def channelStateChanged(self, c, current=None): pass
|
|
|
|
class ServerContextCallbackI(Murmur.ServerContextCallback):
|
|
#--- Server message template strings
|
|
err_notallowed = "You are not allowed to send registration urls"
|
|
msg_regurl = "You've been allowed to register with this mumble server, " \
|
|
"please <a href='%(url)s'>click here to register</a>."
|
|
|
|
|
|
def __init__(self, server):
|
|
self.server = server
|
|
|
|
def contextAction(self, action, p, session, chanid, current=None):
|
|
if action == "sendregurl" and p:
|
|
if p.userid != 0:
|
|
# If it isn't SuperUser check if he is in the right acl group
|
|
allowed = False
|
|
for acl in self.server.getACL(0)[1]:
|
|
if acl.name == group and p.userid in acl.members:
|
|
allowed = True
|
|
break
|
|
if not allowed:
|
|
self.server.sendMessage(p, self.err_notallowed)
|
|
return
|
|
|
|
sema_ids.acquire()
|
|
try:
|
|
# Take the chance to cleanup old entries
|
|
todel = []
|
|
for key, reg in ids.iteritems():
|
|
if int(time.time()) - reg[2] > storage_time:
|
|
todel.append(key)
|
|
for key in todel:
|
|
del ids[key]
|
|
|
|
# Ok, now generate his registration id (64bit)
|
|
rid = None
|
|
while not rid or rid in ids:
|
|
rid = random.randrange(0, pow(2,64))
|
|
sid = self.server.id()
|
|
|
|
# Remember his username
|
|
try:
|
|
username = self.server.getState(session).name
|
|
except Murmur.InvalidSessionException:
|
|
username = ""
|
|
|
|
# Save everything
|
|
ids[str(rid)] = (sid, username, int(time.time()))
|
|
# Send reg url to the user
|
|
url = baseurl + str(rid)
|
|
self.server.sendMessage(session,
|
|
self.msg_regurl % {'url':url})
|
|
finally:
|
|
sema_ids.release()
|
|
|
|
|
|
|
|
class mumble_auth(object):
|
|
#--- Web template strings
|
|
template = """<?xml version="1.0" encoding="utf-8" ?>
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
|
<head>
|
|
<title>%(title)s</title>
|
|
<style type="text/css">
|
|
label { display:block; font-size:smaller; }
|
|
fieldset { border:1px solid; width:250px; }
|
|
h1 { border-bottom:2px solid; }
|
|
h2 { border-bottom:2px solid red; color:red; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>%(title)s</h1>
|
|
%(body)s
|
|
</body>
|
|
</html>"""
|
|
|
|
err_id = """<h2>Invalid ID</h2><p>
|
|
The ID for your registration has already been used or is invalid.<br />
|
|
Please request a new registration link from a Mumble administrator.<br /></p>"""
|
|
|
|
err_username = """<h2>Invalid username</h2><p>
|
|
The username you chose contains characters which are not allowed.<br/>
|
|
Please only use normal characters without whitespaces.<br /></p>"""
|
|
|
|
err_username_existing = """<h2>Username already registered</h2><p>
|
|
The username you chose is already registered on this server.<br />
|
|
Please choose another one.<br /></p>"""
|
|
|
|
err_password = """<h2>Invalid password</h2><p>
|
|
The password you chose didn't match the criteria enforced by this server.<br/>
|
|
Your password should be at least 6 characters long.<br /></p>"""
|
|
|
|
err_password_mismatch = """<h2>Passwords do not match</h2><p>
|
|
The password you entered did not match the one in the confirmation box.<br/>
|
|
Make sure both boxes contain the same password.<br /></p>"""
|
|
|
|
snippet_retry = '<br/><a href="%(url)s">Click here to try again</a>'
|
|
|
|
body_index = """<p>This is the mumble-auth script, to be able to register yourself
|
|
an account please ask a admin on the mumble server.</p>"""
|
|
|
|
body_complete = """<h2>Success</h2><p>
|
|
You have been registered with the server successfully,<br />
|
|
please try to login to the server with your new login credentials!</p>"""
|
|
|
|
body_regform = """
|
|
<form action="doregister" method="post">
|
|
<fieldset>
|
|
<legend>Enter user information</legend>
|
|
<label for="username">Username</label>
|
|
<input id="username" type="text" name="username" value="%(username)s"
|
|
size="20" maxlength="40" /><br />
|
|
<label for="password">Password</label>
|
|
<input id="password" type="password" name="password" value=""
|
|
size="20" maxlength="40" /><br />
|
|
<label for="cpassword">Confirm password</label>
|
|
<input id="cpassword" type="password" name="cpassword" value=""
|
|
size="20" maxlength="40" /><br />
|
|
<input type="hidden" name="id" value="%(id)s" />
|
|
<input id="register" type="submit" value="Register" />
|
|
</fieldset>
|
|
</form>"""
|
|
|
|
def __init__(self, murmur):
|
|
self.murmur = murmur
|
|
|
|
def index(self):
|
|
title = "Mumble Auth - Index"
|
|
return self.template % {'title':title,
|
|
'body':self.body_index}
|
|
index.exposed = True
|
|
|
|
def register(self, id = None):
|
|
title = "Mumble Auth - Register";
|
|
if not id in ids:
|
|
body = self.err_id # Invalid ID
|
|
else:
|
|
# In case of a valid ID
|
|
body = self.body_regform % {'username':ids[id][1],
|
|
'id':id}
|
|
|
|
return self.template % {'title':title,
|
|
'body':body}
|
|
register.exposed = True
|
|
|
|
def doregister(self, id = None, username = None, password = None, cpassword = None):
|
|
title = "Mumble Auth - Register";
|
|
sema_ids.acquire()
|
|
try:
|
|
# Check if all parameters are ok
|
|
if not id in ids:
|
|
body = self.err_id # Invalid id
|
|
elif not username or " " in username: # Invalid username
|
|
body = self.err_username + \
|
|
self.snippet_retry % {'url':baseurl+id}
|
|
elif len(password) < 6: # Password criteria didn't match
|
|
body = self.err_password + \
|
|
self.snippet_retry % {'url':baseurl+id}
|
|
elif password != cpassword: # Password mismatch
|
|
body = self.err_password_mismatch + \
|
|
self.snippet_retry % {'url':baseurl+id}
|
|
else:
|
|
# Ok, try to register him
|
|
server = self.murmur.getServer(ids[id][0])
|
|
if (len(server.getRegisteredUsers(username))!=0):
|
|
body = self.err_username_existing + \
|
|
self.snippet_retry % {'url':baseurl+id}
|
|
else:
|
|
# Register user
|
|
pid = server.registerUser(username)
|
|
reg = server.getRegistration(pid)
|
|
reg.pw = password
|
|
server.updateregistration(reg)
|
|
# Void registration id
|
|
del ids[id]
|
|
# Success
|
|
body = self.body_complete
|
|
finally:
|
|
sema_ids.release()
|
|
return self.template % {'title':title,
|
|
'body':body}
|
|
doregister.exposed = True
|
|
|
|
if __name__ == "__main__":
|
|
random.seed()
|
|
ice = Ice.initialize()
|
|
try:
|
|
meta = Murmur.MetaPrx.checkedCast(ice.stringToProxy(proxy))
|
|
adapter = ice.createObjectAdapterWithEndpoints("Callback.Client", "tcp -h 127.0.0.1")
|
|
metaR=Murmur.MetaCallbackPrx.uncheckedCast(adapter.addWithUUID(MetaCallbackI()))
|
|
adapter.activate()
|
|
|
|
meta.addCallback(metaR)
|
|
for server in meta.getBootedServers():
|
|
serverR=Murmur.ServerCallbackPrx.uncheckedCast(adapter.addWithUUID(ServerCallbackI(server, adapter)))
|
|
server.addCallback(serverR)
|
|
|
|
if production: cherrypy.config.update({'environment': 'production'})
|
|
cherrypy.server.socket_host = host
|
|
cherrypy.server.socket_port = port
|
|
cherrypy.quickstart(mumble_auth(meta))
|
|
finally:
|
|
ice.shutdown()
|
|
ice.waitForShutdown()
|