I am working on a project that connects an Android device with a Raspberry Pi. The RPi needs to be treated like a deployable device that the user never needs to touch. For this reason, I am trying to write a startup batch script on the RPi that will allow the user to pair their Android with the PI.
My idea is that when you startup, this script will run, the user on their phone will try and connect to the RPi, and the RPi will automatically accept this connection.
Here is what I have so far
#!/bin/bash
bluetoothctl -- discoverable on
bluetoothctl -- pairable on
bluetoothctl -- agent on
bluetoothctl -- default-agent
The issue is, when I do it this way I don't get into the [bluetoothctl] prompt that I need to communicate with the Android.
When I run these commands (Without batch script) and try and pair with my Android I get
Request confirmation
[agent] Confirm passkey 861797 (yes/no): yes
And from here I simply need to input yes to instantiate the connection. The issue I'm seeing is 1: I don't know how to stay in the [bluetoothctl] prompt within the command line to communicate with the device and 2: I don't know how to send "Yes" to the prompt.
Again, the important thing for me is that the user never needs to do anything more to the RPi than start it up for deployment purposes. Is there a fix for my problem or perhaps a better way to do it all together?
For those interested, the bluetooth startup connection is in place so that I can send network information to the RPi and it can automatically connect itself to the network so that the main application communication will take place that way.
Here is the desired result of the script which I was able to do manually.
Using bluetoothctl in that manner can be problematic as it is not designed to be interactive in that way. As you have put Python as one of the tags, the intended way of accessing this functionality from Python (and other languages) is through the D-Bus API.
These are documented at: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc
And there are examples at: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/test
The confirmation is the RequestConfirmation in the agent API. You can also set discoverable and pairable with the adapter API. Using the API will also allow you to stop discoverable from timing out.
Once the phone has connected, you typically want to mark it as trusted so that it doesn't need to pair again. This is done with the device API.
Below is an example of setting these properties on the adapter with Python. I have left all of the Agent functions in although it is only the RequestConfirmation that is used. I have set it to always agree to whatever code it is sent which is what you asked for in your question.
This example Python script would replace your batch script
import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib
BUS_NAME = 'org.bluez'
ADAPTER_IFACE = 'org.bluez.Adapter1'
ADAPTER_ROOT = '/org/bluez/hci'
AGENT_IFACE = 'org.bluez.Agent1'
AGNT_MNGR_IFACE = 'org.bluez.AgentManager1'
AGENT_PATH = '/my/app/agent'
AGNT_MNGR_PATH = '/org/bluez'
CAPABILITY = 'KeyboardDisplay'
DEVICE_IFACE = 'org.bluez.Device1'
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
def set_trusted(path):
props = dbus.Interface(bus.get_object(BUS_NAME, path), dbus.PROPERTIES_IFACE)
props.Set(DEVICE_IFACE, "Trusted", True)
class Agent(dbus.service.Object):
#dbus.service.method(AGENT_IFACE,
in_signature="", out_signature="")
def Release(self):
print("Release")
#dbus.service.method(AGENT_IFACE,
in_signature='o', out_signature='s')
def RequestPinCode(self, device):
print(f'RequestPinCode {device}')
return '0000'
#dbus.service.method(AGENT_IFACE,
in_signature="ou", out_signature="")
def RequestConfirmation(self, device, passkey):
print("RequestConfirmation (%s, %06d)" % (device, passkey))
set_trusted(device)
return
#dbus.service.method(AGENT_IFACE,
in_signature="o", out_signature="")
def RequestAuthorization(self, device):
print("RequestAuthorization (%s)" % (device))
auth = input("Authorize? (yes/no): ")
if (auth == "yes"):
return
raise Rejected("Pairing rejected")
#dbus.service.method(AGENT_IFACE,
in_signature="o", out_signature="u")
def RequestPasskey(self, device):
print("RequestPasskey (%s)" % (device))
set_trusted(device)
passkey = input("Enter passkey: ")
return dbus.UInt32(passkey)
#dbus.service.method(AGENT_IFACE,
in_signature="ouq", out_signature="")
def DisplayPasskey(self, device, passkey, entered):
print("DisplayPasskey (%s, %06u entered %u)" %
(device, passkey, entered))
#dbus.service.method(AGENT_IFACE,
in_signature="os", out_signature="")
def DisplayPinCode(self, device, pincode):
print("DisplayPinCode (%s, %s)" % (device, pincode))
class Adapter:
def __init__(self, idx=0):
bus = dbus.SystemBus()
self.path = f'{ADAPTER_ROOT}{idx}'
self.adapter_object = bus.get_object(BUS_NAME, self.path)
self.adapter_props = dbus.Interface(self.adapter_object,
dbus.PROPERTIES_IFACE)
self.adapter_props.Set(ADAPTER_IFACE,
'DiscoverableTimeout', dbus.UInt32(0))
self.adapter_props.Set(ADAPTER_IFACE,
'Discoverable', True)
self.adapter_props.Set(ADAPTER_IFACE,
'PairableTimeout', dbus.UInt32(0))
self.adapter_props.Set(ADAPTER_IFACE,
'Pairable', True)
if __name__ == '__main__':
agent = Agent(bus, AGENT_PATH)
agnt_mngr = dbus.Interface(bus.get_object(BUS_NAME, AGNT_MNGR_PATH),
AGNT_MNGR_IFACE)
agnt_mngr.RegisterAgent(AGENT_PATH, CAPABILITY)
agnt_mngr.RequestDefaultAgent(AGENT_PATH)
adapter = Adapter()
mainloop = GLib.MainLoop()
try:
mainloop.run()
except KeyboardInterrupt:
agnt_mngr.UnregisterAgent(AGENT_PATH)
mainloop.quit()
On my raspberry pi 4 with bluez the following will accept my android phones pairing without the need to type anything on the raspberry.
sudo apt install bluez-tools
sudo bt-agent -c DisplayOnly -p ~/pins.txt &
pins.txt:
00:00:00:00:00:00 *
* *
Note that adding -d to bt-agent did not work for, no matter the -c arguments. Hence the ampersand at the end.
Related
I implemented a Twisted SSH server to test a component that uses fabric to run commands on a remote machine via SSH. I have found this example but I don't understand how I have to implement the execCommand method to be compatible with fabric. Here is my implementation of the SSH server:
from pathlib import Path
from twisted.conch import avatar, recvline
from twisted.conch.insults import insults
from twisted.conch.interfaces import ISession
from twisted.conch.ssh import factory, keys, session
from twisted.cred import checkers, portal
from twisted.internet import reactor
from zope.interface import implementer
SSH_KEYS_FOLDER = Path(__file__).parent.parent / "resources" / "ssh_keys"
#implementer(ISession)
class SSHDemoAvatar(avatar.ConchUser):
def __init__(self, username: str):
avatar.ConchUser.__init__(self)
self.username = username
self.channelLookup.update({b"session": session.SSHSession})
def openShell(self, protocol):
pass
def getPty(self, terminal, windowSize, attrs):
return None
def execCommand(self, protocol: session.SSHSessionProcessProtocol, cmd: bytes):
protocol.write("Some text to return")
protocol.session.conn.sendEOF(protocol.session)
def eofReceived(self):
pass
def closed(self):
pass
#implementer(portal.IRealm)
class SSHDemoRealm(object):
def requestAvatar(self, avatarId, _, *interfaces):
return interfaces[0], SSHDemoAvatar(avatarId), lambda: None
def getRSAKeys():
with open(SSH_KEYS_FOLDER / "ssh_key") as private_key_file:
private_key = keys.Key.fromString(data=private_key_file.read())
with open(SSH_KEYS_FOLDER / "ssh_key.pub") as public_key_file:
public_key = keys.Key.fromString(data=public_key_file.read())
return public_key, private_key
if __name__ == "__main__":
sshFactory = factory.SSHFactory()
sshFactory.portal = portal.Portal(SSHDemoRealm())
users = {
"admin": b"aaa",
"guest": b"bbb",
}
sshFactory.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))
pubKey, privKey = getRSAKeys()
sshFactory.publicKeys = {b"ssh-rsa": pubKey}
sshFactory.privateKeys = {b"ssh-rsa": privKey}
reactor.listenTCP(22222, sshFactory)
reactor.run()
Trying to execute a command via fabric yields the following output:
[paramiko.transport ][INFO ] Connected (version 2.0, client Twisted_22.4.0)
[paramiko.transport ][INFO ] Authentication (password) successful!
Some text to return
This looks promising but the program execution hangs after this line. Do I have to close the connection from the server side after executing the command? How do I implement that properly?
In a traditional UNIX server, it's still the server's responsibility to close the connection if it was told to execute a command. It's up to the server's discretion where and how to do this.
I believe you just want to change protocol.session.conn.sendEOF(protocol.session) to protocol.loseConnection(). I apologize for not testing this myself to be sure, setting up an environment to properly test a full-size conch setup like this is a bit tedious (and the example isn't self-contained, requiring key generation and moduli, etc)
I am working on a program where connecting over SSH to a raspberry pi is necessary to run the program through my GUI app window in python, here is what I have right now to "test" ssh connections on all devices but i have to type in the actual IP of the pi itself. I need to make this where it just tests connection of everything on the network and connects to the available device.
Any help?
def raspi_connecter():
for n in range(1, 255):
server_ip= "10.0.0.153".format(n)
subprocess.Popen('ssh' + ' ' + 'dev#' + server_ip, shell= True)
Here's python2 program (I've never reworked it for python3) that finds your Raspberries. It runs nmap to create XML output then crudely parses that.
It needs the iproute module sudo apt install python{,3}-pyroute
#!/usr/bin/python2
from pyroute2 import IPRoute
import socket
import subprocess
import xml.etree.ElementTree as ET
ip = IPRoute()
for x in ip.get_addr(label='eth0',family=socket.AF_INET):
ipa =x.get_attr('IFA_ADDRESS')+"/"+str(x['prefixlen'])
print ipa
process = subprocess.Popen(['sudo','nmap','-oX', '/tmp/nmap.xml','-sn',ipa],stdout=subprocess.PIPE)
process.wait()
tree = ET.parse('/tmp/nmap.xml')
for node in tree.iter('address'):
try:
if node.attrib['addrtype'] == "ipv4":
ip = node.attrib['addr']
except:
pass
try:
if node.attrib['vendor'] == "Raspberry Pi Foundation":
print "IP:", ip
except:
pass
Once you have an IP address for a Raspberry then you can run ssh with subprocess (or ping or netcat). It will need additional testing for Raspberry Pi 4 and Pi400 devices (as I don't have either of those (which use the new MAC OUI).
I need all connected bluetooth devices to my computer.
I found library, but i can't get connected devices
Simple inquiry example:
import bluetooth
nearby_devices = bluetooth.discover_devices(lookup_names=True)
print("Found {} devices.".format(len(nearby_devices)))
for addr, name in nearby_devices:
print(" {} - {}".format(addr, name))
The snippet of code in the question is doing a scan for new devices rather than reporting on connected devices.
The PyBluez library is not under active development so I tend to avoid it.
BlueZ (the Bluetooth stack on Linux) offers a set of API's through D-Bus that are accessible with Python using D-Bus bindings. I prefer pydbus for most situations.
The BlueZ API is documented at:
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt
As an example of how to implement this in Python3:
import pydbus
bus = pydbus.SystemBus()
adapter = bus.get('org.bluez', '/org/bluez/hci0')
mngr = bus.get('org.bluez', '/')
def list_connected_devices():
mngd_objs = mngr.GetManagedObjects()
for path in mngd_objs:
con_state = mngd_objs[path].get('org.bluez.Device1', {}).get('Connected', False)
if con_state:
addr = mngd_objs[path].get('org.bluez.Device1', {}).get('Address')
name = mngd_objs[path].get('org.bluez.Device1', {}).get('Name')
print(f'Device {name} [{addr}] is connected')
if __name__ == '__main__':
list_connected_devices()
I found a solution, but it uses terminal.
Before using you need to install dependencies
Bluez
Code
def get_connected_devices():
bounded_devices = check_output(['bt-device', '-l']).decode().split("\n")[1:-1]
connected_devices = list()
for device in bounded_devices:
name = device[:device.rfind(' ')]
#mac_address regex
regex = '([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\\.[0-9a-fA-F]{4}\\.[0-9a-fA-F]{4})$'
mac_address = re.search(regex, device).group(0)
device_info = check_output(['bt-device', '-i', mac_address]).decode()
connection_state = device_info[device_info.find('Connected: ') + len('Connected: ')]
if connection_state == '1':
connected_devices.append({"name": name, "address": mac_address})
return connected_devices
Can anyone recommend something for making a ssh connection in python?
I need it to be compatible with any OS.
I've already tried pyssh only to get an error with SIGCHLD, which I've read is because Windows lacks this.
I've tried getting paramiko to work, but I've had errors between paramiko and Crypto to the point where the latest versions of each won't work together.
Python 2.6.1 currently on a Windows machine.
Notice that this doesn't work in Windows.
The module pxssh does exactly what you want:
For example, to run 'ls -l' and to print the output, you need to do something like that :
from pexpect import pxssh
s = pxssh.pxssh()
if not s.login ('localhost', 'myusername', 'mypassword'):
print "SSH session failed on login."
print str(s)
else:
print "SSH session login successful"
s.sendline ('ls -l')
s.prompt() # match the prompt
print s.before # print everything before the prompt.
s.logout()
Some links :
Pxssh docs : http://dsnra.jpl.nasa.gov/software/Python/site-packages/Contrib/pxssh.html
Pexpect (pxssh is based on pexpect) : http://pexpect.readthedocs.io/en/stable/
Twisted has SSH support : http://www.devshed.com/c/a/Python/SSH-with-Twisted/
The twisted.conch package adds SSH support to Twisted. This chapter shows how you can use the modules in twisted.conch to build SSH servers and clients.
Setting Up a Custom SSH Server
The command line is an incredibly efficient interface for certain tasks. System administrators love the ability to manage applications by typing commands without having to click through a graphical user interface. An SSH shell is even better, as it’s accessible from anywhere on the Internet.
You can use twisted.conch to create an SSH server that provides access to a custom shell with commands you define. This shell will even support some extra features like command history, so that you can scroll through the commands you’ve already typed.
How Do I Do That?
Write a subclass of twisted.conch.recvline.HistoricRecvLine that implements your shell protocol. HistoricRecvLine is similar to twisted.protocols.basic.LineReceiver , but with higher-level features for controlling the terminal.
Write a subclass of twisted.conch.recvline.HistoricRecvLine that implements your shell protocol. HistoricRecvLine is similar to twisted.protocols.basic.LineReceiver, but with higher-level features for controlling the terminal.
To make your shell available through SSH, you need to implement a few different classes that twisted.conch needs to build an SSH server. First, you need the twisted.cred authentication classes: a portal, credentials checkers, and a realm that returns avatars. Use twisted.conch.avatar.ConchUser as the base class for your avatar. Your avatar class should also implement twisted.conch.interfaces.ISession , which includes an openShell method in which you create a Protocol to manage the user’s interactive session. Finally, create a twisted.conch.ssh.factory.SSHFactory object and set its portal attribute to an instance of your portal.
Example 10-1 demonstrates a custom SSH server that authenticates users by their username and password. It gives each user a shell that provides several commands.
Example 10-1. sshserver.py
from twisted.cred import portal, checkers, credentials
from twisted.conch import error, avatar, recvline, interfaces as conchinterfaces
from twisted.conch.ssh import factory, userauth, connection, keys, session, common from twisted.conch.insults import insults from twisted.application import service, internet
from zope.interface import implements
import os
class SSHDemoProtocol(recvline.HistoricRecvLine):
def __init__(self, user):
self.user = user
def connectionMade(self) :
recvline.HistoricRecvLine.connectionMade(self)
self.terminal.write("Welcome to my test SSH server.")
self.terminal.nextLine()
self.do_help()
self.showPrompt()
def showPrompt(self):
self.terminal.write("$ ")
def getCommandFunc(self, cmd):
return getattr(self, ‘do_’ + cmd, None)
def lineReceived(self, line):
line = line.strip()
if line:
cmdAndArgs = line.split()
cmd = cmdAndArgs[0]
args = cmdAndArgs[1:]
func = self.getCommandFunc(cmd)
if func:
try:
func(*args)
except Exception, e:
self.terminal.write("Error: %s" % e)
self.terminal.nextLine()
else:
self.terminal.write("No such command.")
self.terminal.nextLine()
self.showPrompt()
def do_help(self, cmd=”):
"Get help on a command. Usage: help command"
if cmd:
func = self.getCommandFunc(cmd)
if func:
self.terminal.write(func.__doc__)
self.terminal.nextLine()
return
publicMethods = filter(
lambda funcname: funcname.startswith(‘do_’), dir(self))
commands = [cmd.replace(‘do_’, ”, 1) for cmd in publicMethods]
self.terminal.write("Commands: " + " ".join(commands))
self.terminal.nextLine()
def do_echo(self, *args):
"Echo a string. Usage: echo my line of text"
self.terminal.write(" ".join(args))
self.terminal.nextLine()
def do_whoami(self):
"Prints your user name. Usage: whoami"
self.terminal.write(self.user.username)
self.terminal.nextLine()
def do_quit(self):
"Ends your session. Usage: quit"
self.terminal.write("Thanks for playing!")
self.terminal.nextLine()
self.terminal.loseConnection()
def do_clear(self):
"Clears the screen. Usage: clear"
self.terminal.reset()
class SSHDemoAvatar(avatar.ConchUser):
implements(conchinterfaces.ISession)
def __init__(self, username):
avatar.ConchUser.__init__(self)
self.username = username
self.channelLookup.update({‘session’:session.SSHSession})
def openShell(self, protocol):
serverProtocol = insults.ServerProtocol(SSHDemoProtocol, self)
serverProtocol.makeConnection(protocol)
protocol.makeConnection(session.wrapProtocol(serverProtocol))
def getPty(self, terminal, windowSize, attrs):
return None
def execCommand(self, protocol, cmd):
raise NotImplementedError
def closed(self):
pass
class SSHDemoRealm:
implements(portal.IRealm)
def requestAvatar(self, avatarId, mind, *interfaces):
if conchinterfaces.IConchUser in interfaces:
return interfaces[0], SSHDemoAvatar(avatarId), lambda: None
else:
raise Exception, "No supported interfaces found."
def getRSAKeys():
if not (os.path.exists(‘public.key’) and os.path.exists(‘private.key’)):
# generate a RSA keypair
print "Generating RSA keypair…"
from Crypto.PublicKey import RSA
KEY_LENGTH = 1024
rsaKey = RSA.generate(KEY_LENGTH, common.entropy.get_bytes)
publicKeyString = keys.makePublicKeyString(rsaKey)
privateKeyString = keys.makePrivateKeyString(rsaKey)
# save keys for next time
file(‘public.key’, ‘w+b’).write(publicKeyString)
file(‘private.key’, ‘w+b’).write(privateKeyString)
print "done."
else:
publicKeyString = file(‘public.key’).read()
privateKeyString = file(‘private.key’).read()
return publicKeyString, privateKeyString
if __name__ == "__main__":
sshFactory = factory.SSHFactory()
sshFactory.portal = portal.Portal(SSHDemoRealm())
users = {‘admin’: ‘aaa’, ‘guest’: ‘bbb’}
sshFactory.portal.registerChecker(
checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))
pubKeyString, privKeyString =
getRSAKeys()
sshFactory.publicKeys = {
‘ssh-rsa’: keys.getPublicKeyString(data=pubKeyString)}
sshFactory.privateKeys = {
‘ssh-rsa’: keys.getPrivateKeyObject(data=privKeyString)}
from twisted.internet import reactor
reactor.listenTCP(2222, sshFactory)
reactor.run()
{mospagebreak title=Setting Up a Custom SSH Server continued}
sshserver.py will run an SSH server on port 2222. Connect to this server with an SSH client using the username admin and password aaa, and try typing some commands:
$ ssh admin#localhost -p 2222
admin#localhost’s password: aaa
>>> Welcome to my test SSH server.
Commands: clear echo help quit whoami
$ whoami
admin
$ help echo
Echo a string. Usage: echo my line of text
$ echo hello SSH world!
hello SSH world!
$ quit
Connection to localhost closed.
adding here to driquet's answer as the edit waitlist is full
for python3 :
from pexpect import pxssh
s = pxssh.pxssh()
if not s.login ('10.6.192.18', 'root', 'paloalto'):
print ("SSH session failed on login.")
print (str(s))
else:
print ("SSH session login successful")
s.sendline ('ls -l')
s.prompt() # match the prompt
print (s.before.decode('UTF-8')) # decode to string and print everything before the prompt.
s.logout()
I'm looking for a way to let my python program handle authentication through pam.
I'm using http://code.google.com/p/web2py/source/browse/gluon/contrib/pam.py for this, which works out great as long as my python program runs as root which is not ideal to my opinion.
How can I make use of pam for username/password validation without requiring root privs?
short: use a proper Python PAM implementation, setup PAM properly.
long: In a sane PAM setup, you do not need root privileges. In the end this is one of the things PAM provides, privilege separation.
pam_unix has a way to check the password for you. Seems the PAM implementation of web2py (note, it's from some contrib subdirectory...) is not doing the right thing. Maybe your PAM setup is not correct, which is hard to tell without further information; this also depends heavily on operating system and flavour/distribution.
There are multiple PAM bindings for Python out there (unfortunately nothing in the standard library), use these instead. And for configuration, there are tons of tutorials, find the right one for your system.
old/wrong, don't do this: You do not need to be root, you only need to be able to read /etc/shadow. This file has usually group shadow with read only access. So you simply need to add the user that is running the PAM check in the shadow group.
groupadd <user> shadow should do the trick.
I think the pam module is your best choice, but you don't have to embed it into your program directly. You could write a simple service which binds to a port on localhost, or listens on a UNIX domain socket, and fills PAM requests for other processes on the same host. Then have your web2py application connect to it for user/password validation.
For example:
import asyncore
import pam
import socket
class Client(asyncore.dispatcher_with_send):
def __init__(self, sock):
asyncore.dispatcher_with_send.__init__(self, sock)
self._buf = ''
def handle_read(self):
data = self._buf + self.recv(1024)
if not data:
self.close()
return
reqs, data = data.rsplit('\r\n', 1)
self._buf = data
for req in reqs.split('\r\n'):
try:
user, passwd = req.split()
except:
self.send('bad\r\n')
else:
if pam.authenticate(user, passwd):
self.send('ok\r\n')
else:
self.send('fail\r\n')
def handle_close(self):
self.close()
class Service(asyncore.dispatcher_with_send):
def __init__(self, addr):
asyncore.dispatcher_with_send.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind(addr)
self.listen(1)
def handle_accept(self):
conn, _ = self.accept()
Client(conn)
def main():
addr = ('localhost', 8317)
Service(addr)
try:
asyncore.loop()
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Usage:
% telnet localhost 8317
bob abc123
ok
larry badpass
fail
incomplete
bad
At the end I ended up using pexpect and trying to su - username.
It's a bit slow, but it works pretty good.
The below example isn't polished but you'll get the idea.
Cheers,
Jay
#!/usr/bin/python
import pexpect
def pam(username, password):
'''Accepts username and password and tried to use PAM for authentication'''
try:
child = pexpect.spawn('/bin/su - %s'%(username))
child.expect('Password:')
child.sendline(password)
result=child.expect(['su: Authentication failure',username])
child.close()
except Exception as err:
child.close()
print ("Error authenticating. Reason: "%(err))
return False
if result == 0:
print ("Authentication failed for user %s."%(username))
return False
else:
print ("Authentication succeeded for user %s."%(username))
return True
if __name__ == '__main__':
print pam(username='default',password='chandgeme')
Maybe python-pam can work for you.
Not if you use they usual system (unix style) login credentials. At some point the PAM library must read the shadow file which is only readable by root. However, if you use a PAM profile that authenticates with an alternate method, such as LDAP or a database, then it can work without needing root.
This is one reason I developed my own framework that runs different parts of the URL path space under different user credentials. The login part (only) can run as root to authenticate with PAM (system), other path subtree handlers run as different users.
I'm using the PyPAM module for this.