#!/usr/bin/env python3 # Flexisip, a flexible SIP proxy server with media capabilities. # Copyright (C) 2010-2024 Belledonne Communications SARL. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import argparse import os import os.path import sys # Check interpreter version. if sys.version_info[0] != 3: raise RuntimeError('Python v3 is required for this script') def parse_args(): parser = argparse.ArgumentParser(description="A command line interface to manage Flexisip servers.") parser.add_argument( '-p', '--pid', type=int, default=0, help="""Process ID (PID) of the process to communicate with. If 0 is given, the PID will be automatically found from /var/run/flexisip-.pid. If no PID file has been found, selects the PID of the first process which name matches flexisip-. (default: 0)""" ) parser.add_argument( '-s', '--server', choices=('proxy', 'presence', 'b2bua'), default='proxy', help="""Type of the server to communicate with. (default: proxy)""" ) commands = { 'CONFIG_GET': { 'help': 'Get the value of an internal variable of Flexisip.' }, 'CONFIG_SET': { 'help': 'Set the value of an internal variable of Flexisip.' }, 'CONFIG_LIST': { 'help': 'List all the available parameters of a section.' }, 'REGISTRAR_GET': { 'help': 'List all bindings under an address of record (AOR) from the registrar database.' }, 'REGISTRAR_UPSERT': { 'help': 'Update or insert a binding for an address of record (AOR) in the registrar database.' }, 'REGISTRAR_DELETE': { 'help': 'Remove a specific binding of an address of record (AOR) from the registrar database.' }, 'REGISTRAR_CLEAR': { 'help': 'Remove an address of record (AOR) from the registrar database.' }, 'REGISTRAR_DUMP': { 'help': 'Dump the list of registered address of records (AORs) for this proxy instance only (not all the cluster!).' }, 'SIP_BRIDGE': { 'help': 'Send commands to the external SIP provider bridge (if active).' }, } kargs = { 'dest': 'command', 'metavar': 'command', 'help': f"""Action to do on the server. Type `{os.path.basename(sys.argv[0])} --help` for detailed documentation about the given command.""" } if sys.version_info[:2] >= (3, 7): kargs['required'] = True cmdSubparser = parser.add_subparsers(**kargs) for cmdName in commands.keys(): desc = commands[cmdName]['help'] commands[cmdName]['parser'] = cmdSubparser.add_parser(cmdName, help=desc, description=desc) pathDocumentation = "Parameter name formatted as '/'." commands['CONFIG_GET']['parser'].add_argument( 'path', help=pathDocumentation ) commands['CONFIG_SET']['parser'].add_argument( 'path', help=pathDocumentation ) commands['CONFIG_SET']['parser'].add_argument( 'value', help="The new value." ) commands['CONFIG_LIST']['parser'].add_argument( 'section', nargs='?', default='all', help='The name of the section. Returns the list of all available sections if empty.' ) commands['REGISTRAR_CLEAR']['parser'].add_argument( 'aor', help='Address of record (AOR).' ) commands['REGISTRAR_GET']['parser'].add_argument( 'aor', help='Address of record (AOR).' ) commands['REGISTRAR_UPSERT']['parser'].add_argument( 'aor', help='Address of record (AOR).' ) commands['REGISTRAR_UPSERT']['parser'].add_argument( 'uri', help='SIP URI of the binding to update or insert.' ) commands['REGISTRAR_UPSERT']['parser'].add_argument( 'expire', help='Time to live for the new binding (in seconds).' ) commands['REGISTRAR_UPSERT']['parser'].add_argument( 'uuid', help='Unique identifier of the binding. Get it from REGISTRAR_GET for updates, leave it out to be ' 'autogenerated on insertions.', default=None, nargs='?' ) commands['REGISTRAR_DELETE']['parser'].add_argument( 'aor', help='Address of record (AOR).' ) commands['REGISTRAR_DELETE']['parser'].add_argument( 'uuid', help='Unique identifier of the binding: +sip.instance value.' ) commands['SIP_BRIDGE']['parser'].add_argument( 'subcommand', help='The command to send to the bridge. Valid commands: INFO' ) return parser.parse_args() def getpid(procName): from subprocess import check_output, CalledProcessError pidFile = '/var/run/{procName}.pid'.format(procName=procName) try: return int(check_output(['cat', pidFile])) except CalledProcessError: pass try: return int(check_output(['pidof', '-s', procName])) except CalledProcessError: print('error: could not find flexisip process pid', file=sys.stderr) sys.exit(1) def formatMessage(args): if args.command is None: print('error: no command specified', file=sys.stderr) sys.exit(2) messageArgs = [args.command] if args.command == 'CONFIG_GET': messageArgs.append(args.path) elif args.command == 'CONFIG_SET': messageArgs += [args.path, args.value] elif args.command == 'CONFIG_LIST': messageArgs.append(args.section) elif args.command == 'REGISTRAR_CLEAR': messageArgs.append(args.aor) elif args.command == 'REGISTRAR_GET': messageArgs.append(args.aor) elif args.command == 'REGISTRAR_UPSERT': messageArgs.append(args.aor) messageArgs.append(args.uri) messageArgs.append(args.expire) if (args.uuid): messageArgs.append(args.uuid) elif args.command == 'REGISTRAR_DELETE': messageArgs.append(args.aor) messageArgs.append(args.uuid) elif args.command == 'SIP_BRIDGE': messageArgs.append(args.subcommand) return ' '.join(messageArgs) def sendMessage(remote_socket, message): import socket with socket.socket(socket.AF_UNIX) as s: timeout_seconds = 3 s.settimeout(timeout_seconds) try: s.connect(remote_socket) s.send(message.encode()) received = s.recv(65535).decode() print(received) if received.startswith('Error'): # The server reports an error, it probably has to do with the arguments passed, so USAGE seems appropriate return os.EX_USAGE else: return os.EX_OK # https://docs.python.org/3/library/os.html#os.EX_OK except socket.error as err: print('error: could not connect to socket {!r}: {!r}'.format(remote_socket, err), file=sys.stderr) # error: could not connect to socket '/tmp/flexisip-proxy-15150': PermissionError(13, 'Permission denied') return os.EX_UNAVAILABLE def main(): args = parse_args() pid = args.pid proc_name = 'flexisip-{}'.format(args.server) if pid == 0: pid = getpid(proc_name) socket = '/tmp/{}-{}'.format(proc_name, pid) message = formatMessage(args) return sendMessage(socket, message) if __name__ == '__main__': sys.exit(main())