#!/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())
