flexisip/scripts/flexisip_cli.py
2025-06-16 13:54:12 +07:00

240 lines
7.1 KiB
Python
Executable file

#!/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 <http://www.gnu.org/licenses/>.
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-<server_type>.pid. If no PID file has been found, selects the PID of the first
process which name matches flexisip-<server_type>. (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])} <command> --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 '<section_name>/<param_name>'."
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())