├── .flake8 ├── .github ├── dependabot.yml └── workflows │ └── github-packages.yml ├── .gitignore ├── .pre-commit-config.yaml ├── ChangeLog ├── Dockerfile ├── LICENSE ├── README.md ├── docs └── Devel.md ├── kamcli ├── __init__.py ├── cli.py ├── commands │ ├── __init__.py │ ├── cmd_acc.py │ ├── cmd_address.py │ ├── cmd_aliasdb.py │ ├── cmd_apiban.py │ ├── cmd_avp.py │ ├── cmd_config.py │ ├── cmd_db.py │ ├── cmd_dialog.py │ ├── cmd_dialplan.py │ ├── cmd_dispatcher.py │ ├── cmd_dlgs.py │ ├── cmd_domain.py │ ├── cmd_group.py │ ├── cmd_htable.py │ ├── cmd_jsonrpc.py │ ├── cmd_moni.py │ ├── cmd_mtree.py │ ├── cmd_pike.py │ ├── cmd_ping.py │ ├── cmd_pipelimit.py │ ├── cmd_pkg.py │ ├── cmd_ps.py │ ├── cmd_pstrap.py │ ├── cmd_rpcmethods.py │ ├── cmd_rtpengine.py │ ├── cmd_shell.py │ ├── cmd_shm.py │ ├── cmd_shv.py │ ├── cmd_sipreq.py │ ├── cmd_speeddial.py │ ├── cmd_srv.py │ ├── cmd_stats.py │ ├── cmd_subscriber.py │ ├── cmd_tcp.py │ ├── cmd_tls.py │ ├── cmd_trap.py │ ├── cmd_uacreg.py │ ├── cmd_ul.py │ └── cmd_uptime.py ├── dbutils.py ├── iorpc.py ├── ioutils.py └── kamcli.ini ├── pkg └── deb │ ├── bionic │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ └── rules │ ├── bookworm │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ └── rules │ ├── bullseye │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ └── rules │ ├── buster │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ └── rules │ ├── debian │ ├── backports │ │ ├── bionic │ │ ├── bookworm │ │ ├── bullseye │ │ ├── buster │ │ ├── focal │ │ ├── jammy │ │ ├── noble │ │ ├── stretch │ │ └── xenial │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ └── rules │ ├── focal │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ └── rules │ ├── jammy │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ └── rules │ ├── noble │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ └── rules │ ├── stretch │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ └── rules │ └── xenial │ ├── changelog │ ├── compat │ ├── control │ ├── copyright │ ├── kamcli.examples │ ├── pydist-overrides │ └── rules ├── pyproject.toml ├── requirements ├── base.txt ├── requirements.txt └── requirements_dev.txt └── setup.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E203, E231, E266, E501, W503, F403, F401 3 | max-line-length = 79 4 | max-complexity = 18 5 | select = B,C,E,F,W,T4,B9 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | commit-message: 9 | prefix: "github: [skip ci]" 10 | -------------------------------------------------------------------------------- /.github/workflows/github-packages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: github-packages 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - "v*.*.*" 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | env: 12 | REGISTRY_NAME: ghcr.io 13 | IMAGE_NAME: kamcli 14 | REGISTRY_IMAGE: kamailio/kamcli 15 | jobs: 16 | docker: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: read 20 | packages: write 21 | steps: 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v3 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v3 26 | - name: Login to GitHub Registry 27 | uses: docker/login-action@v3 28 | with: 29 | registry: ${{ env.REGISTRY_NAME }} 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | - name: Docker meta 33 | id: meta 34 | uses: docker/metadata-action@v5 35 | with: 36 | images: ${{ env.REGISTRY_NAME }}/${{ env.REGISTRY_IMAGE }} 37 | tags: | 38 | type=schedule 39 | type=ref,event=branch 40 | type=ref,event=pr 41 | type=semver,pattern={{version}} 42 | type=semver,pattern={{major}}.{{minor}} 43 | type=semver,pattern={{major}} 44 | type=sha 45 | labels: | 46 | org.opencontainers.image.title=${{ env.IMAGE_NAME }} 47 | org.opencontainers.image.description=image with all required dependences 48 | env: 49 | DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index 50 | - name: Build and push 51 | uses: docker/build-push-action@v5 52 | with: 53 | platforms: linux/amd64,linux/arm64 54 | labels: ${{ steps.meta.outputs.labels }} 55 | push: true 56 | tags: ${{ steps.meta.outputs.tags }} 57 | annotations: ${{ steps.meta.outputs.annotations }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editor tmp files 2 | *~ 3 | *.swp 4 | *.swo 5 | 6 | # Python specific tmp files 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | .env/ 19 | venv/ 20 | .venv/ 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *,cover 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # VSCode 70 | .vscode 71 | 72 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 23.7.0 4 | hooks: 5 | - id: black 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v2.3.0 8 | hooks: 9 | - id: flake8 10 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | Release: Kamcli v2.0.0 (Sep 30, 2019) 2 | 3 | New features: 4 | * support for Python 3.x 5 | * run sql statements from a file 6 | * print command aliases 7 | * cmd uacreg: management of uacreg records 8 | * cmd srv: server info 9 | * cmd rtpengine: management of rtpengine module 10 | * cmd tcp: management of tcp connections 11 | 12 | Release: Kamcli v1.1.0 (Oct 16, 2018) 13 | 14 | For more detailed list of changes, see the git log. 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-bookworm 2 | LABEL org.opencontainers.image.authors="Victor Seva " 3 | 4 | WORKDIR /project 5 | VOLUME /etc/kamcli 6 | 7 | COPY . . 8 | RUN pip3 install . 9 | RUN pip3 install psycopg2 mysqlclient 10 | -------------------------------------------------------------------------------- /docs/Devel.md: -------------------------------------------------------------------------------- 1 | ## KAMCLI Development ## 2 | 3 | Kamailio Command Line Interface Control Tool 4 | 5 | 6 | ### Development Environment ### 7 | 8 | Use 4 spaces for indentation. 9 | 10 | #### [pre-commit](https://pre-commit.com/) #### 11 | 12 | It is highly recommended to set the `pre-commit` git hook to get code identation and 13 | error detections using `black` and `flake8` tools before changes are committed. 14 | 15 | On a Debian/Ubuntu system, do: 16 | 17 | ``` 18 | apt install build-essentials python3-dev python3-virtualenvwrapper 19 | mkvirtualenv kamcli --python=python3 20 | pip install -r requirements/requirements_dev.txt 21 | pre-commit install 22 | ``` 23 | 24 | ### Development Guidelines ### 25 | 26 | #### Used Frameworks #### 27 | 28 | Kamcli is using the following Python frameworks: 29 | 30 | * click - command line interface framework 31 | * http://click.pocoo.org 32 | * SQL Alchemy - connection to database 33 | * http://www.sqlalchemy.org 34 | * pyaml - yaml package used for compact printing of jsonrpc responses 35 | * tabulate - pretty printing of database results 36 | 37 | #### Plugins #### 38 | 39 | Kamcli prototype is: 40 | 41 | ``` 42 | kamcli [params] 43 | ``` 44 | 45 | Each command is implemented as a plugin, its code residing in a single Python 46 | file located in *kamcli/commands/*. The filename is prefixed by **cmd_**, 47 | followed by command name and then the extension **.py**. 48 | 49 | Development of kamcli has its starting point in the *complex* example of Click: 50 | 51 | * https://github.com/mitsuhiko/click/tree/master/examples/complex 52 | 53 | Other examples provided by Click are good source of inspiration: 54 | 55 | * https://github.com/mitsuhiko/click/tree/master/examples 56 | 57 | #### Adding A New Command #### 58 | 59 | In short, the steps for adding a new command (refered also as plugin or module): 60 | 61 | * create a new file file for your new comand in **kamcli/commands/** folder 62 | * name the file **cmd_newcommand.py** 63 | * define **cli(...)** function, which can be a command or group of commands 64 | 65 | Once implemented, the new command should be immediately available as: 66 | 67 | ``` 68 | kamcli newcommand ... 69 | ``` 70 | 71 | The commands **dispatcher** (kamcli/commands/cmd_dispatcher.py) or **address** 72 | (kamcli/commands/cmd_address.py) can be a good reference to look at and reuse 73 | for implementing new commands. 74 | 75 | If the new command is executing MI or JSONRPC commands to kamailio, add the 76 | appropriate mapping inside the **kamcli/iorpc.py** file to the variable 77 | **COMMAND_NAMES**. The recommendation is to use the RPC command as the common 78 | name and then map the MI variant - MI is obsoleted and scheduled to be removed. 79 | -------------------------------------------------------------------------------- /kamcli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamailio/kamcli/8a1b748909793ba239339c8c43b98dbbe0ed4a9f/kamcli/__init__.py -------------------------------------------------------------------------------- /kamcli/cli.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import click 4 | 5 | try: 6 | import ConfigParser as configparser 7 | except ImportError: 8 | import configparser 9 | 10 | kamcli_formats_list = ["raw", "json", "table", "dict", "yaml"] 11 | 12 | COMMAND_ALIASES = { 13 | "acl": "group", 14 | "rpc": "jsonrpc", 15 | "subs": "subscriber", 16 | "sht": "htable", 17 | } 18 | 19 | 20 | def read_global_config(config_paths): 21 | """Get config.""" 22 | parser = configparser.ConfigParser() 23 | if config_paths: 24 | parser.read(config_paths) 25 | else: 26 | parser.read(["kamcli.ini"]) 27 | return parser 28 | 29 | 30 | # try: 31 | # self.optmain.update(parser.items('main')) 32 | # except configparser.NoSectionError: 33 | # pass 34 | 35 | 36 | def parse_user_spec(ctx, ustr): 37 | """Get details of the user from ustr (username, aor or sip uri)""" 38 | udata = {} 39 | if ":" in ustr: 40 | uaor = ustr.split(":")[1] 41 | else: 42 | uaor = ustr 43 | if "@" in uaor: 44 | udata["username"] = uaor.split("@")[0] 45 | udata["domain"] = uaor.split("@")[1] 46 | else: 47 | udata["username"] = uaor.split("@")[0] 48 | try: 49 | udata["domain"] = ctx.gconfig.get("main", "domain") 50 | except configparser.NoOptionError: 51 | ctx.log("Default domain not set in config file") 52 | sys.exit() 53 | if udata["username"] is None: 54 | ctx.log("Failed to get username") 55 | sys.exit() 56 | if udata["domain"] is None: 57 | ctx.log("Failed to get domain") 58 | sys.exit() 59 | udata["username"] = udata["username"].encode("ascii", "ignore").decode() 60 | udata["domain"] = udata["domain"].encode("ascii", "ignore").decode() 61 | return udata 62 | 63 | 64 | CONTEXT_SETTINGS = dict( 65 | auto_envvar_prefix="KAMCLI", help_option_names=["-h", "--help"] 66 | ) 67 | 68 | 69 | class Context(object): 70 | def __init__(self): 71 | self.debug = False 72 | self.wdir = os.getcwd() 73 | self.gconfig_paths = [] 74 | self._gconfig = None 75 | 76 | def log(self, msg, *args): 77 | """Logs a message to stderr.""" 78 | if args: 79 | msg %= args 80 | click.echo("(log): " + msg, file=sys.stderr) 81 | 82 | def vlog(self, msg, *args): 83 | """Logs a message to stderr only if verbose is enabled.""" 84 | if self.debug: 85 | if args: 86 | msg %= args 87 | click.echo("(dbg): " + msg, file=sys.stderr) 88 | 89 | def printf(self, msg, *args): 90 | """Print a formated message to stdout.""" 91 | if args: 92 | msg %= args 93 | click.echo(msg) 94 | 95 | def printnlf(self, msg, *args): 96 | """Print a formated message to stdout without new line.""" 97 | if args: 98 | msg %= args 99 | click.echo(msg, nl=False) 100 | 101 | def read_config(self): 102 | if self._gconfig is None: 103 | self._gconfig = read_global_config(self.gconfig_paths) 104 | if "cmdaliases" in self._gconfig: 105 | COMMAND_ALIASES.update(self._gconfig["cmdaliases"]) 106 | 107 | @property 108 | def gconfig(self): 109 | if self._gconfig is None: 110 | self._gconfig = read_global_config(self.gconfig_paths) 111 | return self._gconfig 112 | 113 | 114 | pass_context = click.make_pass_decorator(Context, ensure=True) 115 | cmd_folder = os.path.abspath( 116 | os.path.join(os.path.dirname(__file__), "commands") 117 | ) 118 | 119 | 120 | class KamCLI(click.MultiCommand): 121 | def list_commands(self, ctx): 122 | rv = [] 123 | for filename in os.listdir(cmd_folder): 124 | if filename.endswith(".py") and filename.startswith("cmd_"): 125 | rv.append(filename[4:-3]) 126 | rv.sort() 127 | return rv 128 | 129 | def get_command(self, ctx, name): 130 | if name in COMMAND_ALIASES: 131 | name = COMMAND_ALIASES[name] 132 | try: 133 | if sys.version_info[0] == 2: 134 | name = name.encode("ascii", "replace") 135 | mod = __import__( 136 | "kamcli.commands.cmd_" + name, None, None, ["cli"] 137 | ) 138 | except ImportError: 139 | return 140 | return mod.cli 141 | 142 | 143 | @click.command( 144 | cls=KamCLI, 145 | context_settings=CONTEXT_SETTINGS, 146 | short_help="Kamailio command line interface control tool", 147 | ) 148 | @click.option( 149 | "-d", "--debug", "debug", is_flag=True, help="Enable debug mode." 150 | ) 151 | @click.option( 152 | "-c", "--config", "config", default=None, help="Configuration file." 153 | ) 154 | @click.option( 155 | "-w", 156 | "--wdir", 157 | "wdir", 158 | type=click.Path(exists=True, file_okay=False, resolve_path=True), 159 | help="Working directory.", 160 | ) 161 | @click.option( 162 | "-n", 163 | "--no-default-configs", 164 | "nodefaultconfigs", 165 | is_flag=True, 166 | help="Skip loading default configuration files.", 167 | ) 168 | @click.option( 169 | "-F", 170 | "--output-format", 171 | "oformat", 172 | type=click.Choice(kamcli_formats_list), 173 | default=None, 174 | help="Format the output (overwriting db/jsonrpc outformat config attributes)", 175 | ) 176 | @click.version_option() 177 | @pass_context 178 | def cli(ctx, debug, config, wdir, nodefaultconfigs, oformat): 179 | """Kamailio command line interface control tool. 180 | 181 | \b 182 | Help per command: kamcli --help 183 | 184 | \b 185 | Default configuration files: 186 | - /etc/kamcli/kamcli.ini 187 | - ./kamcli.ini 188 | - ./kamcli/kamcli.ini 189 | - ~/.kamcli/kamctli.ini 190 | Configs loading order: default configs, then --config option 191 | 192 | \b 193 | License: GPLv2 194 | Copyright: asipto.com 195 | """ 196 | ctx.debug = debug 197 | if wdir is not None: 198 | ctx.wdir = wdir 199 | if not nodefaultconfigs: 200 | if os.path.isfile("./kamcli/kamcli.ini"): 201 | ctx.gconfig_paths.append("./kamcli/kamcli.ini") 202 | if os.path.isfile("./kamcli.ini"): 203 | ctx.gconfig_paths.append("./kamcli.ini") 204 | if os.path.isfile("/etc/kamcli/kamcli.ini"): 205 | ctx.gconfig_paths.append("/etc/kamcli/kamcli.ini") 206 | tpath = os.path.expanduser("~/.kamcli/kamcli.ini") 207 | if os.path.isfile(tpath): 208 | ctx.gconfig_paths.append(tpath) 209 | if config is not None: 210 | ctx.gconfig_paths.append(os.path.expanduser(config)) 211 | ctx.read_config() 212 | if oformat is not None: 213 | ctx.gconfig.set("db", "outformat", oformat) 214 | ctx.gconfig.set("jsonrpc", "outformat", oformat) 215 | -------------------------------------------------------------------------------- /kamcli/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamailio/kamcli/8a1b748909793ba239339c8c43b98dbbe0ed4a9f/kamcli/commands/__init__.py -------------------------------------------------------------------------------- /kamcli/commands/cmd_address.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "address", 10 | help="Manage permissions address records", 11 | short_help="Manage permissions address records", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("add", short_help="Add a new record to address table") 19 | @click.option( 20 | "mask", "--mask", type=int, default=32, help="Mask value (default 32)" 21 | ) 22 | @click.option( 23 | "port", "--port", type=int, default=0, help="Port value (default 0)" 24 | ) 25 | @click.option("tag", "--tag", default="", help='Tag value (default: "")') 26 | @click.argument("group", metavar="", type=int) 27 | @click.argument("address", metavar="
") 28 | @pass_context 29 | def address_add(ctx, mask, port, tag, group, address): 30 | """Add a new record to address db table 31 | 32 | \b 33 | Parameters: 34 | - group id 35 |
- IP address 36 | """ 37 | ctx.vlog("Adding to group id [%d] address [%s]", group, address) 38 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 39 | e.execute( 40 | "insert into address (grp, ip_addr, mask, port, tag) values " 41 | "({0}, {1!r}, {2}, {3}, {4!r})".format( 42 | group, 43 | address.encode("ascii", "ignore").decode(), 44 | mask, 45 | port, 46 | tag.encode("ascii", "ignore").decode(), 47 | ) 48 | ) 49 | 50 | 51 | @cli.command("rm", short_help="Remove a record from address db table") 52 | @click.option("mask", "--mask", type=int, help="Mask value") 53 | @click.option("port", "--port", type=int, help="Port value") 54 | @click.argument("group", metavar="", type=int) 55 | @click.argument("address", metavar="
") 56 | @pass_context 57 | def address_rm(ctx, mask, port, group, address): 58 | """Remove a record from address db table 59 | 60 | \b 61 | Parameters: 62 | - group id 63 |
- IP address 64 | """ 65 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 66 | addr = address.encode("ascii", "ignore").decode() 67 | if not mask: 68 | if not port: 69 | e.execute( 70 | "delete from address where grp={0} and ip_addr={1!r}".format( 71 | group, addr 72 | ) 73 | ) 74 | else: 75 | e.execute( 76 | "delete from address where grp={0} and ip_addr={1!r} " 77 | "and port={2}".format(group, addr, port) 78 | ) 79 | else: 80 | if not port: 81 | e.execute( 82 | "delete from address where grp={0} and " 83 | "ip_addr={1!r} and mask={2}".format(group, addr, mask) 84 | ) 85 | else: 86 | e.execute( 87 | "delete from address where setid={0} and destination={1!r} " 88 | "and mask={2} and port={3}".format(group, addr, mask, port) 89 | ) 90 | 91 | 92 | @cli.command("showdb", short_help="Show address records in database") 93 | @click.option( 94 | "oformat", 95 | "--output-format", 96 | "-F", 97 | type=click.Choice(["raw", "json", "table", "dict"]), 98 | default=None, 99 | help="Format the output", 100 | ) 101 | @click.option( 102 | "ostyle", 103 | "--output-style", 104 | "-S", 105 | default=None, 106 | help="Style of the output (tabulate table format)", 107 | ) 108 | @click.argument("group", nargs=-1, metavar="[]", type=int) 109 | @pass_context 110 | def address_showdb(ctx, oformat, ostyle, group): 111 | """Show details for records in address db table 112 | 113 | \b 114 | Parameters: 115 | - address group 116 | """ 117 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 118 | if not group: 119 | ctx.vlog("Showing all address records") 120 | res = e.execute("select * from address") 121 | else: 122 | ctx.vlog("Showing address records for group") 123 | res = e.execute("select * from address where group={0}".format(group)) 124 | ioutils_dbres_print(ctx, oformat, ostyle, res) 125 | 126 | 127 | @cli.command("list", short_help="Show details for address records in memory") 128 | @click.option( 129 | "mode", 130 | "--mode", 131 | default="all", 132 | help="What to be printed (all, addresses, subnets, domains)", 133 | ) 134 | @click.argument("group", nargs=-1, metavar="[]", type=int) 135 | @pass_context 136 | def address_list(ctx, mode, group): 137 | """Show details for address records in memory 138 | 139 | \b 140 | Parameters: 141 | - address group 142 | """ 143 | if mode == "all": 144 | command_ctl(ctx, "permissions.addressDump", []) 145 | command_ctl(ctx, "permissions.subnetDump", []) 146 | command_ctl(ctx, "permissions.domainDump", []) 147 | elif mode == "addresses": 148 | command_ctl(ctx, "permissions.addressDump", []) 149 | elif mode == "subnets": 150 | command_ctl(ctx, "permissions.subnetDump", []) 151 | elif mode == "domains": 152 | command_ctl(ctx, "permissions.domainDump", []) 153 | else: 154 | command_ctl(ctx, "permissions.addressDump", []) 155 | 156 | 157 | @cli.command( 158 | "reload", short_help="Reload address records from database into memory" 159 | ) 160 | @pass_context 161 | def address_reload(ctx): 162 | """Reload address records from database into memory 163 | """ 164 | command_ctl(ctx, "permissions.addressReload", []) 165 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_aliasdb.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.cli import parse_user_spec 6 | 7 | 8 | @click.group( 9 | "aliasdb", 10 | help="Manage database user aliases", 11 | short_help="Manage database user aliases", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("add", short_help="Add a user-alias pair") 19 | @click.option( 20 | "table", 21 | "--table", 22 | default="dbaliases", 23 | help="Name of database table (default: dbaliases)", 24 | ) 25 | @click.argument("userid", metavar="") 26 | @click.argument("aliasid", metavar="") 27 | @pass_context 28 | def aliasdb_add(ctx, table, userid, aliasid): 29 | """Add a user-alias pair into database 30 | 31 | \b 32 | Parameters: 33 | - username, AoR or SIP URI for subscriber 34 | - username, AoR or SIP URI for alias 35 | """ 36 | udata = parse_user_spec(ctx, userid) 37 | adata = parse_user_spec(ctx, aliasid) 38 | ctx.vlog( 39 | "Adding user [%s@%s] with alias [%s@%s]", 40 | udata["username"], 41 | udata["domain"], 42 | adata["username"], 43 | adata["domain"], 44 | ) 45 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 46 | e.execute( 47 | "insert into {0} (username, domain, alias_username, " 48 | "alias_domain) values ({1!r}, {2!r}, {3!r}, {4!r})".format( 49 | table, 50 | udata["username"], 51 | udata["domain"], 52 | adata["username"], 53 | adata["domain"], 54 | ) 55 | ) 56 | 57 | 58 | @cli.command("rm", short_help="Remove records for a user and/or alias") 59 | @click.option( 60 | "table", 61 | "--table", 62 | default="dbaliases", 63 | help="Name of database table (default: dbaliases)", 64 | ) 65 | @click.option( 66 | "matchalias", 67 | "--match-alias", 68 | is_flag=True, 69 | help="Match userid value as alias (when given one argument)", 70 | ) 71 | @click.argument("userid", metavar="") 72 | @click.argument("aliasid", metavar="", nargs=-1) 73 | @pass_context 74 | def aliasdb_rm(ctx, table, matchalias, userid, aliasid): 75 | """Remove a user from groups (revoke privilege) 76 | 77 | \b 78 | Parameters: 79 | - username, AoR or SIP URI for subscriber 80 | - username, AoR or SIP URI for alias (optional) 81 | """ 82 | udata = parse_user_spec(ctx, userid) 83 | ctx.log( 84 | "Removing alias for record [%s@%s]", udata["username"], udata["domain"] 85 | ) 86 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 87 | if not aliasid: 88 | if matchalias: 89 | e.execute( 90 | "delete from {0} where alias_username={1!r} and " 91 | "alias_domain={2!r}".format( 92 | table, udata["username"], udata["domain"], 93 | ) 94 | ) 95 | else: 96 | e.execute( 97 | "delete from {0} where username={1!r} and domain={2!r}".format( 98 | table, udata["username"], udata["domain"], 99 | ) 100 | ) 101 | else: 102 | for a in aliasid: 103 | adata = parse_user_spec(ctx, a) 104 | e.execute( 105 | "delete from {0} where username={1!r} and domain={2!r} " 106 | "and alias_username={3!r} and alias_domain={4!r}".format( 107 | table, 108 | udata["username"], 109 | udata["domain"], 110 | adata["username"], 111 | adata["domain"], 112 | ) 113 | ) 114 | 115 | 116 | @cli.command("show", short_help="Show user aliases") 117 | @click.option( 118 | "oformat", 119 | "--output-format", 120 | "-F", 121 | type=click.Choice(["raw", "json", "table", "dict"]), 122 | default=None, 123 | help="Format the output", 124 | ) 125 | @click.option( 126 | "ostyle", 127 | "--output-style", 128 | "-S", 129 | default=None, 130 | help="Style of the output (tabulate table format)", 131 | ) 132 | @click.option( 133 | "table", 134 | "--table", 135 | default="dbaliases", 136 | help="Name of database table (default: dbaliases)", 137 | ) 138 | @click.option( 139 | "matchalias", 140 | "--match-alias", 141 | is_flag=True, 142 | help="Match userid value as alias", 143 | ) 144 | @click.argument("userid", nargs=-1, metavar="[]") 145 | @pass_context 146 | def aliasdb_show(ctx, oformat, ostyle, table, matchalias, userid): 147 | """Show details for user aliases 148 | 149 | \b 150 | Parameters: 151 | [] - username, AoR or SIP URI for user or alias 152 | - it can be a list of userids 153 | - if not provided then all aliases are shown 154 | """ 155 | if not userid: 156 | ctx.vlog("Showing all records") 157 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 158 | res = e.execute("select * from {0}".format(table)) 159 | ioutils_dbres_print(ctx, oformat, ostyle, res) 160 | else: 161 | for u in userid: 162 | udata = parse_user_spec(ctx, u) 163 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 164 | 165 | if matchalias: 166 | ctx.vlog( 167 | "Showing records for alias [%s@%s]", 168 | udata["username"], 169 | udata["domain"], 170 | ) 171 | res = e.execute( 172 | "select * from {0} where alias_username={1!r} " 173 | "and alias_domain={2!r}".format( 174 | table, udata["username"], udata["domain"], 175 | ) 176 | ) 177 | else: 178 | ctx.vlog( 179 | "Showing records for user [%s@%s]", 180 | udata["username"], 181 | udata["domain"], 182 | ) 183 | res = e.execute( 184 | "select * from {0} where username={1!r} and " 185 | "domain={2!r}".format( 186 | table, udata["username"], udata["domain"], 187 | ) 188 | ) 189 | ioutils_dbres_print(ctx, oformat, ostyle, res) 190 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_apiban.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | import http.client 5 | import os 6 | import json 7 | import time 8 | import pprint 9 | 10 | 11 | @click.group( 12 | "apiban", help="Manage APIBan records", short_help="Manage APIBan records", 13 | ) 14 | @pass_context 15 | def cli(ctx): 16 | pass 17 | 18 | 19 | def apiban_key(ctx, key): 20 | if key is None: 21 | key = ctx.gconfig.get("apiban", "key", fallback=None) 22 | if key is None: 23 | return os.environ.get("APIBANKEY") 24 | return key 25 | 26 | 27 | def apiban_fetch(ctx, key): 28 | conn = http.client.HTTPSConnection("apiban.org", timeout=4) 29 | idval = "" 30 | allAddresses = [] 31 | while True: 32 | time.sleep(1) 33 | conn.request("GET", "/api/" + key + "/banned" + idval) 34 | r1 = conn.getresponse() 35 | ctx.vlog("response: " + str(r1.status) + " " + r1.reason) 36 | if r1.status == 200: 37 | data1 = r1.read() 38 | jdata = json.loads(data1) 39 | ctx.vlog( 40 | "fetched ipaddress array size: " + str(len(jdata["ipaddress"])) 41 | ) 42 | allAddresses = allAddresses + jdata["ipaddress"] 43 | if jdata["ID"] == "none": 44 | break 45 | else: 46 | idval = "/" + jdata["ID"] 47 | else: 48 | break 49 | return allAddresses 50 | 51 | 52 | @cli.command("show", short_help="Show the addresses returned by apiban.org") 53 | @click.option( 54 | "key", "--key", "-k", default=None, help="The APIBan key", 55 | ) 56 | @pass_context 57 | def apiban_show(ctx, key): 58 | """Show the APIBan addresses 59 | 60 | \b 61 | """ 62 | ctx.vlog("fetching APIBan records") 63 | key = apiban_key(ctx, key) 64 | if key is None or len(key) == 0: 65 | ctx.log( 66 | "no APIBan key - provide it via -k parameter, config file or APIBANKEY environment variable" 67 | ) 68 | return 69 | 70 | allAddresses = apiban_fetch(ctx, key) 71 | ctx.vlog("all ip addresses array size: " + str(len(allAddresses))) 72 | pprint.pprint(allAddresses) 73 | print() 74 | 75 | 76 | @cli.command( 77 | "load", short_help="Load the records from apiban.org to a Kamailio htable" 78 | ) 79 | @click.option( 80 | "key", "--key", "-k", default=None, help="The APIBan key", 81 | ) 82 | @click.option( 83 | "htname", "--htname", "-t", default=None, help="The htable name", 84 | ) 85 | @click.option( 86 | "expire", 87 | "--expire", 88 | "-x", 89 | type=int, 90 | default=0, 91 | help="The expire for htable item", 92 | ) 93 | @pass_context 94 | def apiban_load(ctx, key, htname, expire): 95 | """Load the APIBan addresses to a Kamailio htable 96 | 97 | \b 98 | """ 99 | ctx.vlog("loading APIBan records") 100 | key = apiban_key(ctx, key) 101 | if key is None or len(key) == 0: 102 | ctx.log( 103 | "no APIBan key - provide it via -k parameter, config file or APIBANKEY environment variable" 104 | ) 105 | return 106 | 107 | if htname is None: 108 | htname = ctx.gconfig.get("apiban", "htname", fallback=None) 109 | if htname is None: 110 | htname = "ipban" 111 | allAddresses = apiban_fetch(ctx, key) 112 | ctx.vlog("fetched ip addresses - array size: " + str(len(allAddresses))) 113 | if len(allAddresses) > 0: 114 | for a in allAddresses: 115 | command_ctl(ctx, "htable.seti", [htname, a, 1]) 116 | if expire > 0: 117 | command_ctl(ctx, "htable.setex", [htname, a, 1]) 118 | time.sleep(0.002) 119 | else: 120 | ctx.log("no APIBan records") 121 | 122 | 123 | @cli.command("check", short_help="Check IP address against apiban.org") 124 | @click.option( 125 | "key", "--key", "-k", default=None, help="The APIBan key", 126 | ) 127 | @click.argument("ipaddr", metavar="") 128 | @pass_context 129 | def apiban_check(ctx, key, ipaddr): 130 | """Check IP address against apiban.org 131 | 132 | \b 133 | Parameters: 134 | - IP address 135 | """ 136 | ctx.vlog("cheking address againt apiban.org") 137 | key = apiban_key(ctx, key) 138 | if key is None or len(key) == 0: 139 | ctx.log( 140 | "no APIBan key - provide it via -k parameter, config file or APIBANKEY environment variable" 141 | ) 142 | return 143 | 144 | conn = http.client.HTTPSConnection("apiban.org", timeout=4) 145 | conn.request("GET", "/api/" + key + "/check/" + ipaddr) 146 | r1 = conn.getresponse() 147 | print(r1.status, r1.reason) 148 | data1 = r1.read() 149 | print(data1) 150 | print() 151 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import click 5 | import shutil 6 | from kamcli.cli import pass_context 7 | from kamcli.cli import COMMAND_ALIASES 8 | 9 | 10 | @click.group( 11 | "config", 12 | help="Manage the config file", 13 | short_help="Manage the config file", 14 | ) 15 | @pass_context 16 | def cli(ctx): 17 | pass 18 | 19 | 20 | @cli.command("raw", short_help="Display raw content of configuration file") 21 | @pass_context 22 | def config_raw(ctx): 23 | """Show content of configuration file for kamcli""" 24 | ctx.log("\n---") 25 | ctx.gconfig.write(sys.stdout) 26 | ctx.log("\n---") 27 | 28 | 29 | @cli.command( 30 | "show", short_help="Show expanded content of configuration file sections" 31 | ) 32 | @click.argument("sections", nargs=-1, metavar="") 33 | @pass_context 34 | def config_show(ctx, sections): 35 | """Show expanded content of configuration file section""" 36 | if sections: 37 | ctx.log("\n---") 38 | for s in sections: 39 | ctx.log("[%s]", s) 40 | for k, v in ctx.gconfig.items(s): 41 | ctx.log("%s= %s", k, v) 42 | ctx.log("\n---") 43 | 44 | 45 | @cli.command("paths", short_help="Show the paths of configuration files") 46 | @pass_context 47 | def config_paths(ctx): 48 | """Show the paths of configuration files for kamcli""" 49 | print() 50 | print(ctx.gconfig_paths) 51 | print() 52 | 53 | 54 | @cli.command("cmdaliases", short_help="Show the command aliases") 55 | @pass_context 56 | def config_cmdaliases(ctx): 57 | """Show the command aliases""" 58 | print() 59 | print(json.dumps(COMMAND_ALIASES, indent=4, sort_keys=True)) 60 | print() 61 | 62 | 63 | @cli.command("install", short_help="Install the config file") 64 | @click.option( 65 | "user", "--user", "-u", is_flag=True, help="Install in user home folder", 66 | ) 67 | @pass_context 68 | def config_install(ctx, user): 69 | if os.path.isfile("./kamcli/kamcli.ini"): 70 | if user: 71 | dirName = os.path.expanduser("~/.kamcli") 72 | else: 73 | dirName = "/etc/kamcli" 74 | if not os.path.exists(dirName): 75 | os.mkdir(dirName) 76 | click.echo("directory " + dirName + " created") 77 | else: 78 | click.echo("directory " + dirName + " already exists") 79 | shutil.copyfile("./kamcli/kamcli.ini", dirName + "/kamcli.ini") 80 | click.echo("config file installed to " + dirName + "/kamcli.ini") 81 | else: 82 | click.echo("command must be run in the source code root directory") 83 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_dialog.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "dialog", 10 | help="Manage dialog module (active calls tracking)", 11 | short_help="Manage dialog module", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("showdb", short_help="Show dialog records in database") 19 | @click.option( 20 | "oformat", 21 | "--output-format", 22 | "-F", 23 | type=click.Choice(["raw", "json", "table", "dict"]), 24 | default=None, 25 | help="Format the output", 26 | ) 27 | @click.option( 28 | "ostyle", 29 | "--output-style", 30 | "-S", 31 | default=None, 32 | help="Style of the output (tabulate table format)", 33 | ) 34 | @pass_context 35 | def dialog_showdb(ctx, oformat, ostyle): 36 | """Show details for records in dialog table 37 | 38 | \b 39 | """ 40 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 41 | ctx.vlog("Showing all dialog records") 42 | res = e.execute("select * from dialog") 43 | ioutils_dbres_print(ctx, oformat, ostyle, res) 44 | 45 | 46 | @cli.command("list", short_help="Show details for dialog records in memory") 47 | @pass_context 48 | def dialog_list(ctx): 49 | """Show details for dialog records in memory 50 | 51 | \b 52 | """ 53 | command_ctl(ctx, "dlg.list", []) 54 | 55 | 56 | @cli.command("match", short_help="Show the details for the matching dialogs") 57 | @click.argument("mkey", metavar="") 58 | @click.argument("mop", metavar="") 59 | @click.argument("mval", metavar="") 60 | @pass_context 61 | def dialog_match(ctx, mkey, mop, mval): 62 | """Show the details for the matching dialogs 63 | 64 | \b 65 | Parameters: 66 | - matching key: 67 | ruri - use R-URI; 68 | furi - use From-URI; 69 | turi - use To-URI; 70 | callid - use Call-Id 71 | - matching operator: 72 | eq - string comparison; 73 | re - regular expression; 74 | sw - starts-with 75 | - matching value 76 | """ 77 | command_ctl(ctx, "dlg.list_match", [mkey, mop, mval]) 78 | 79 | 80 | @cli.command( 81 | "terminate", 82 | short_help="Send BYE to the dialog identified by call-id," 83 | " from-tag and to-tag", 84 | ) 85 | @click.argument("callid", metavar="") 86 | @click.argument("fromtag", metavar="") 87 | @click.argument("totag", metavar="") 88 | @pass_context 89 | def dialog_terminate(ctx, callid, fromtag, totag): 90 | """Send BYE to the dialog identified by callid, from-tag and to-tag 91 | 92 | \b 93 | Parameters: 94 | - Call-Id value 95 | - From-Tag value 96 | - To-Tag value 97 | """ 98 | command_ctl(ctx, "dlg.terminate_dlg", [callid, fromtag, totag]) 99 | 100 | 101 | @cli.command("stats_active", short_help="Show statistics for active dialogs") 102 | @pass_context 103 | def dialog_stats_active(ctx): 104 | """Show statistics for active dialogs 105 | 106 | \b 107 | """ 108 | command_ctl(ctx, "dlg.stats_active", []) 109 | 110 | 111 | @cli.command("profile_list", short_help="List the content of a profile") 112 | @click.argument("profile", metavar="") 113 | @pass_context 114 | def dialog_profile_list(ctx, profile): 115 | """List the dialogs groupped in a profile 116 | 117 | \b 118 | Parameters: 119 | - the name of the profile 120 | """ 121 | command_ctl(ctx, "dlg.profile_list", [profile]) 122 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_dialplan.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "dialplan", 10 | help="Manage dialplan module (regexp translations)", 11 | short_help="Manage dialplan module", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("add", short_help="Add a new dialplan rule") 19 | @click.option( 20 | "priority", 21 | "--priority", 22 | type=int, 23 | default=0, 24 | help="Priority value (default: 0)", 25 | ) 26 | @click.option( 27 | "matchop", 28 | "--match-op", 29 | default="equal", 30 | help="Match operator: equal, regexp, fnmatch (default: equal)", 31 | ) 32 | @click.option( 33 | "matchlen", 34 | "--match-len", 35 | type=int, 36 | default=0, 37 | help="Match target lenght (default: 0)", 38 | ) 39 | @click.option("attrs", "--attrs", default="", help='Attributes (default: "")') 40 | @click.argument("dpid", metavar="", type=int) 41 | @click.argument("matchexp", metavar="") 42 | @click.argument("substrepl", nargs=-1, metavar="") 43 | @pass_context 44 | def dialplan_add( 45 | ctx, priority, matchop, matchlen, attrs, dpid, matchexp, substrepl 46 | ): 47 | """Add a new translation rule in dialplan table 48 | 49 | \b 50 | Parameters: 51 | - dialplan id 52 | - match expression 53 | [] - substitution match expression 54 | [] - replacement expression 55 | """ 56 | matchid = 0 57 | if matchop == "regexp": 58 | matchid = 1 59 | elif matchop == "fnmatch": 60 | matchid = 2 61 | substexp = "" 62 | replexp = "" 63 | if len(substrepl) > 0: 64 | substexp = substrepl[0] 65 | if len(substrepl) > 1: 66 | replexp = substrepl[1] 67 | 68 | ctx.vlog( 69 | "Adding to dialplan id [%d] match [%s] expression [%s]", 70 | dpid, 71 | matchop, 72 | matchexp, 73 | ) 74 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 75 | e.execute( 76 | "insert into dialplan (dpid, pr, match_op, match_exp, match_len, " 77 | "subst_exp, repl_exp, attrs) values " 78 | "({0}, {1}, {2}, {3!r}, {4}, {5!r}, {6!r}, {7!r})".format( 79 | dpid, 80 | priority, 81 | matchid, 82 | matchexp.encode("ascii", "ignore").decode(), 83 | matchlen, 84 | substexp.encode("ascii", "ignore").decode(), 85 | replexp.encode("ascii", "ignore").decode(), 86 | attrs.encode("ascii", "ignore").decode(), 87 | ) 88 | ) 89 | 90 | 91 | @cli.command("rm", short_help="Remove records from dialplan") 92 | @click.argument("dpid", metavar="", type=int) 93 | @click.argument("matchexp", nargs=-1, metavar="") 94 | @pass_context 95 | def dialplan_rm(ctx, dpid, matchexp): 96 | """Remove records from dialplan 97 | 98 | \b 99 | Parameters: 100 | - dialplan id 101 | [] - match expression 102 | """ 103 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 104 | if not matchexp: 105 | e.execute("delete from dialplan where dpid={0}".format(dpid)) 106 | else: 107 | for m in matchexp: 108 | e.execute( 109 | "delete from dialplan where " 110 | "dpid={0} and match_exp={1!r}".format( 111 | dpid, matchexp.encode("ascii", "ignore").decode() 112 | ) 113 | ) 114 | 115 | 116 | @cli.command("showdb", short_help="Show dialplan records in database") 117 | @click.option( 118 | "oformat", 119 | "--output-format", 120 | "-F", 121 | type=click.Choice(["raw", "json", "table", "dict"]), 122 | default=None, 123 | help="Format the output", 124 | ) 125 | @click.option( 126 | "ostyle", 127 | "--output-style", 128 | "-S", 129 | default=None, 130 | help="Style of the output (tabulate table format)", 131 | ) 132 | @click.argument("dpid", nargs=-1, metavar="[]", type=int) 133 | @pass_context 134 | def dialplan_showdb(ctx, oformat, ostyle, dpid): 135 | """Show details for records in dialplan 136 | 137 | \b 138 | Parameters: 139 | [] - dialplan id 140 | """ 141 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 142 | if not dpid: 143 | ctx.vlog("Showing all dialplan records") 144 | res = e.execute("select * from dialplan") 145 | ioutils_dbres_print(ctx, oformat, ostyle, res) 146 | else: 147 | for d in dpid: 148 | ctx.vlog("Showing dialplan records for set id: " + d) 149 | res = e.execute("select * from dialplan where dpid={0}".format(d)) 150 | ioutils_dbres_print(ctx, oformat, ostyle, res) 151 | 152 | 153 | @cli.command("list", short_help="Show details for dialplan records in memory") 154 | @click.argument("dpid", metavar="[]", type=int) 155 | @pass_context 156 | def dialplan_list(ctx, dpid): 157 | """Show details for dialplan records in memory 158 | 159 | \b 160 | Parameters: 161 | - dialplan id 162 | """ 163 | command_ctl(ctx, "dialplan.dump", [dpid]) 164 | 165 | 166 | @cli.command( 167 | "reload", short_help="Reload dialplan records from database into memory" 168 | ) 169 | @pass_context 170 | def dialplan_reload(ctx): 171 | """Reload dialplan records from database into memory 172 | """ 173 | command_ctl(ctx, "dialplan.reload", []) 174 | 175 | 176 | @cli.command( 177 | "translate", 178 | short_help="Translate using the rules from dialplan " 179 | "applied to input value", 180 | ) 181 | @click.argument("dpid", metavar="", type=int) 182 | @click.argument("ivalue", metavar="") 183 | @pass_context 184 | def dialplan_translate(ctx, dpid, ivalue): 185 | """Translate using the rules from dialplan applied to input value 186 | 187 | \b 188 | Parameters: 189 | - dialplan id 190 | - input value 191 | """ 192 | command_ctl(ctx, "dialplan.translate", [dpid, ivalue]) 193 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_dispatcher.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "dispatcher", 10 | help="Manage dispatcher module (load balancer)", 11 | short_help="Manage dispatcher module", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("add", short_help="Add a new dispatcher destination") 19 | @click.option("flags", "--flags", type=int, default=0, help="Flags value") 20 | @click.option( 21 | "priority", "--priority", type=int, default=0, help="Priority value" 22 | ) 23 | @click.option("attrs", "--attrs", default="", help='Attributes (default: "")') 24 | @click.option( 25 | "description", "--desc", default="", help='Description (default: "")' 26 | ) 27 | @click.argument("setid", metavar="", type=int) 28 | @click.argument("destination", metavar="") 29 | @pass_context 30 | def dispatcher_add( 31 | ctx, flags, priority, attrs, description, setid, destination 32 | ): 33 | """Add a new destination in a set of dispatcher db table 34 | 35 | \b 36 | Parameters: 37 | - dispatching set id 38 | - SIP URI for destination 39 | """ 40 | ctx.vlog("Adding to setid [%d] destination [%s]", setid, destination) 41 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 42 | e.execute( 43 | "insert into dispatcher " 44 | "(setid, destination, flags, priority, attrs, description) " 45 | "values ({0}, {1!r}, {2}, {3}, {4!r}, {5!r})".format( 46 | setid, 47 | destination.encode("ascii", "ignore").decode(), 48 | flags, 49 | priority, 50 | attrs.encode("ascii", "ignore").decode(), 51 | description.encode("ascii", "ignore").decode(), 52 | ) 53 | ) 54 | 55 | 56 | @cli.command("rm", short_help="Remove a destination from dispatcher table") 57 | @click.argument("setid", metavar="", type=int) 58 | @click.argument("destination", metavar="") 59 | @pass_context 60 | def dispatcher_rm(ctx, setid, destination): 61 | """Remove a destination from db dispatcher table 62 | 63 | \b 64 | Parameters: 65 | - dispatching set id 66 | - SIP URI for destination 67 | """ 68 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 69 | e.execute( 70 | "delete from dispatcher where setid={0} and destination={1!r}".format( 71 | setid, destination.encode("ascii", "ignore").decode() 72 | ) 73 | ) 74 | 75 | 76 | @cli.command("showdb", short_help="Show dispatcher records in database") 77 | @click.option( 78 | "oformat", 79 | "--output-format", 80 | "-F", 81 | type=click.Choice(["raw", "json", "table", "dict"]), 82 | default=None, 83 | help="Format the output", 84 | ) 85 | @click.option( 86 | "ostyle", 87 | "--output-style", 88 | "-S", 89 | default=None, 90 | help="Style of the output (tabulate table format)", 91 | ) 92 | @click.argument("setid", nargs=-1, metavar="[]", type=int) 93 | @pass_context 94 | def dispatcher_showdb(ctx, oformat, ostyle, setid): 95 | """Show details for records in dispatcher table 96 | 97 | \b 98 | Parameters: 99 | [] - dispatching set id 100 | """ 101 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 102 | if not setid: 103 | ctx.vlog("Showing all dispatcher records") 104 | res = e.execute("select * from dispatcher") 105 | ioutils_dbres_print(ctx, oformat, ostyle, res) 106 | else: 107 | for s in setid: 108 | ctx.vlog("Showing dispatcher records for set id") 109 | res = e.execute( 110 | "select * from dispatcher where setid={0}".format(s) 111 | ) 112 | ioutils_dbres_print(ctx, oformat, ostyle, res) 113 | 114 | 115 | @cli.command( 116 | "list", short_help="Show details for dispatcher records in memory" 117 | ) 118 | @pass_context 119 | def dispatcher_list(ctx): 120 | """Show details for dispatcher records in memory 121 | 122 | \b 123 | """ 124 | command_ctl(ctx, "dispatcher.list", []) 125 | 126 | 127 | @cli.command( 128 | "reload", short_help="Reload dispatcher records from database into memory" 129 | ) 130 | @pass_context 131 | def dispatcher_reload(ctx): 132 | """Reload dispatcher records from database into memory 133 | 134 | \b 135 | """ 136 | command_ctl(ctx, "dispatcher.reload", []) 137 | 138 | 139 | @cli.command("memadd", short_help="Add a new dispatcher destination in memory") 140 | @click.option("flags", "--flags", type=int, default=0, help="Flags value") 141 | @click.argument("setid", metavar="", type=int) 142 | @click.argument("destination", metavar="") 143 | @pass_context 144 | def dispatcher_memadd(ctx, flags, setid, destination): 145 | """Add a new destination in a set of dispatcher memory 146 | 147 | \b 148 | Parameters: 149 | - dispatching set id 150 | - SIP URI for destination 151 | """ 152 | ctx.vlog("Adding to setid [%d] destination [%s]", setid, destination) 153 | command_ctl(ctx, "dispatcher.add", [setid, destination, flags]) 154 | 155 | 156 | @cli.command("memrm", short_help="Remove a destination from dispatcher memory") 157 | @click.argument("setid", metavar="", type=int) 158 | @click.argument("destination", metavar="") 159 | @pass_context 160 | def dispatcher_memrm(ctx, setid, destination): 161 | """Remove a destination from dispatcher memory 162 | 163 | \b 164 | Parameters: 165 | - dispatching set id 166 | - SIP URI for destination 167 | """ 168 | command_ctl(ctx, "dispatcher.remove", [setid, destination]) 169 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_dlgs.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.ioutils import ioutils_dbres_print 3 | from kamcli.cli import pass_context 4 | from kamcli.iorpc import command_ctl 5 | 6 | 7 | @click.group( 8 | "dlgs", 9 | help="Manage dlgs module (active calls stats)", 10 | short_help="Manage dlgs module", 11 | ) 12 | @pass_context 13 | def cli(ctx): 14 | pass 15 | 16 | 17 | @cli.command("list", short_help="Show details for dialog records in memory") 18 | @pass_context 19 | def dlgs_list(ctx): 20 | """Show details for dialog records in memory 21 | 22 | \b 23 | """ 24 | command_ctl(ctx, "dlgs.list", []) 25 | 26 | 27 | @cli.command( 28 | "briefing", short_help="Show summary for dialog records in memory" 29 | ) 30 | @pass_context 31 | def dlgs_briefing(ctx): 32 | """Show summary for dialog records in memory 33 | 34 | \b 35 | """ 36 | command_ctl(ctx, "dlgs.briefing", []) 37 | 38 | 39 | @cli.command("get", short_help="Get the details for the first matching dialog") 40 | @click.argument("mkey", metavar="") 41 | @click.argument("mop", metavar="") 42 | @click.argument("mval", metavar="") 43 | @pass_context 44 | def dlgs_get(ctx, mkey, mop, mval): 45 | """Show the details for the first matching dialog 46 | 47 | \b 48 | Parameters: 49 | - matching key: 50 | src - src attribute; 51 | dst - dst attribute; 52 | data - data attribute; 53 | - matching operator: 54 | eq - equal string comparison; 55 | ne - not-equal string comparison; 56 | re - regular expression; 57 | sw - starts-with 58 | fm - fast match 59 | - matching value 60 | """ 61 | command_ctl(ctx, "dlgs.get", [mkey, mop, mval]) 62 | 63 | 64 | @cli.command("getall", short_help="Get the details for all matching dialogs") 65 | @click.argument("mkey", metavar="") 66 | @click.argument("mop", metavar="") 67 | @click.argument("mval", metavar="") 68 | @pass_context 69 | def dlgs_getall(ctx, mkey, mop, mval): 70 | """Show the details for all matching dialogs 71 | 72 | \b 73 | Parameters: 74 | - matching key: 75 | src - src attribute; 76 | dst - dst attribute; 77 | data - data attribute; 78 | - matching operator: 79 | eq - equal string comparison; 80 | ne - not-equal string comparison; 81 | re - regular expression; 82 | sw - starts-with 83 | fm - fast match 84 | - matching value 85 | """ 86 | command_ctl(ctx, "dlgs.getall", [mkey, mop, mval]) 87 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_domain.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "domain", 10 | help="Manage domain module (multi-domain records)", 11 | short_help="Manage domain module", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("add", short_help="Add a new domain") 19 | @click.argument("domain", metavar="") 20 | @pass_context 21 | def domain_add(ctx, domain): 22 | """Add a new domain 23 | 24 | \b 25 | Parameters: 26 | - domain value 27 | """ 28 | ctx.vlog("Adding a new domain [%s]", domain) 29 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 30 | e.execute( 31 | "insert into domain (domain) values ({0!r})".format( 32 | domain.encode("ascii", "ignore").decode() 33 | ) 34 | ) 35 | 36 | 37 | @cli.command("rm", short_help="Remove a record from domain table") 38 | @click.argument("domain", metavar="") 39 | @pass_context 40 | def domain_rm(ctx, domain): 41 | """Remove a a record from db domain table 42 | 43 | \b 44 | Parameters: 45 | - domain value 46 | """ 47 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 48 | e.execute( 49 | "delete from domain where domain={0!r}".format( 50 | domain.encode("ascii", "ignore").decode() 51 | ) 52 | ) 53 | 54 | 55 | ## 56 | # 57 | # 58 | @cli.command("showdb", short_help="Show domain records in database") 59 | @click.option( 60 | "oformat", 61 | "--output-format", 62 | "-F", 63 | type=click.Choice(["raw", "json", "table", "dict"]), 64 | default=None, 65 | help="Format the output", 66 | ) 67 | @click.option( 68 | "ostyle", 69 | "--output-style", 70 | "-S", 71 | default=None, 72 | help="Style of the output (tabulate table format)", 73 | ) 74 | @click.argument("domain", nargs=-1, metavar="[]") 75 | @pass_context 76 | def domain_showdb(ctx, oformat, ostyle, domain): 77 | """Show details for records in domain table 78 | 79 | \b 80 | Parameters: 81 | [] - domain value (optional) 82 | """ 83 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 84 | if not domain: 85 | ctx.vlog("Showing all domain records") 86 | res = e.execute("select * from domain") 87 | ioutils_dbres_print(ctx, oformat, ostyle, res) 88 | else: 89 | for d in domain: 90 | ctx.vlog("Showing a specific domain record") 91 | res = e.execute( 92 | "select * from domain where domain={0!r}".format(d) 93 | ) 94 | ioutils_dbres_print(ctx, oformat, ostyle, res) 95 | 96 | 97 | @cli.command("list", short_help="Show details for domain records in memory") 98 | @pass_context 99 | def domain_list(ctx): 100 | """Show details for domain records in memory 101 | 102 | \b 103 | """ 104 | command_ctl(ctx, "domain.dump", []) 105 | 106 | 107 | @cli.command( 108 | "reload", short_help="Reload domain records from database into memory" 109 | ) 110 | @pass_context 111 | def domain_reload(ctx): 112 | """Reload domain records from database into memory 113 | 114 | \b 115 | """ 116 | command_ctl(ctx, "domain.reload", []) 117 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_group.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.cli import parse_user_spec 6 | 7 | 8 | @click.group( 9 | "group", 10 | help="Manage the ACL of users with group membership", 11 | short_help="Manage group module", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("grant", short_help="Add a user into a group") 19 | @click.argument("userid", metavar="") 20 | @click.argument("groupid", metavar="") 21 | @pass_context 22 | def group_grant(ctx, userid, groupid): 23 | """Add a user into a group (grant privilege) 24 | 25 | \b 26 | Parameters: 27 | - username, AoR or SIP URI for subscriber 28 | - group name 29 | """ 30 | udata = parse_user_spec(ctx, userid) 31 | ctx.vlog( 32 | "Adding user [%s@%s] in group [%s]", 33 | udata["username"], 34 | udata["domain"], 35 | groupid, 36 | ) 37 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 38 | e.execute( 39 | "insert into grp (username, domain, grp) values ({0!r}, {1!r}, {2!r})".format( 40 | udata["username"], udata["domain"], groupid, 41 | ) 42 | ) 43 | 44 | 45 | @cli.command("revoke", short_help="Remove a user from groups") 46 | @click.argument("userid", metavar="") 47 | @click.argument("groupid", metavar="", nargs=-1) 48 | @pass_context 49 | def group_revoke(ctx, userid, groupid): 50 | """Remove a user from groups (revoke privilege) 51 | 52 | \b 53 | Parameters: 54 | - username, AoR or SIP URI for subscriber 55 | - group name 56 | """ 57 | udata = parse_user_spec(ctx, userid) 58 | ctx.log( 59 | "Removing ACL for user [%s@%s]", udata["username"], udata["domain"] 60 | ) 61 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 62 | if not groupid: 63 | e.execute( 64 | "delete from grp where username={0!r} and domain={1!r}".format( 65 | udata["username"], udata["domain"], 66 | ) 67 | ) 68 | else: 69 | e.execute( 70 | "delete from grp where username={0!r} and domain={1!r} and grp={2!r}".format( 71 | udata["username"], udata["domain"], groupid, 72 | ) 73 | ) 74 | 75 | 76 | @cli.command("show", short_help="Show group membership details") 77 | @click.option( 78 | "oformat", 79 | "--output-format", 80 | "-F", 81 | type=click.Choice(["raw", "json", "table", "dict"]), 82 | default=None, 83 | help="Format the output", 84 | ) 85 | @click.option( 86 | "ostyle", 87 | "--output-style", 88 | "-S", 89 | default=None, 90 | help="Style of the output (tabulate table format)", 91 | ) 92 | @click.argument("userid", nargs=-1, metavar="[]") 93 | @pass_context 94 | def group_show(ctx, oformat, ostyle, userid): 95 | """Show details for subscribers 96 | 97 | \b 98 | Parameters: 99 | [] - username, AoR or SIP URI for subscriber 100 | - it can be a list of userids 101 | - if not provided then all subscribers are shown 102 | """ 103 | if not userid: 104 | ctx.vlog("Showing all records") 105 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 106 | res = e.execute("select * from grp") 107 | ioutils_dbres_print(ctx, oformat, ostyle, res) 108 | else: 109 | for u in userid: 110 | udata = parse_user_spec(ctx, u) 111 | ctx.vlog( 112 | "Showing group membership for user [%s@%s]", 113 | udata["username"], 114 | udata["domain"], 115 | ) 116 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 117 | res = e.execute( 118 | "select * from grp where username={0!r} and domain={1!r}".format( 119 | udata["username"], udata["domain"], 120 | ) 121 | ) 122 | ioutils_dbres_print(ctx, oformat, ostyle, res) 123 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_htable.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "htable", 10 | help="Management of htable module", 11 | short_help="Management of htable module", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("list", short_help="List the content of hash table named htname") 19 | @click.argument("htname", metavar="") 20 | @pass_context 21 | def htable_list(ctx, htname): 22 | """List the content of hash table named htname 23 | 24 | \b 25 | Parameters: 26 | - the name of hash table 27 | """ 28 | command_ctl(ctx, "htable.dump", [htname]) 29 | 30 | 31 | @cli.command("get", short_help="Get the value for $sht(htname=>itname)") 32 | @click.argument("htname", metavar="") 33 | @click.argument("itname", nargs=-1, metavar="") 34 | @pass_context 35 | def htable_get(ctx, htname, itname): 36 | """Get the value for $sht(htname=>itname) 37 | 38 | \b 39 | Parameters: 40 | - the name of hash table 41 | - the name of item 42 | """ 43 | if not itname: 44 | command_ctl(ctx, "htable.dump", [htname]) 45 | else: 46 | for itn in itname: 47 | command_ctl(ctx, "htable.get", [htname, itn]) 48 | 49 | 50 | @cli.command("sets", short_help="Set $sht(htname=>itname) to string value") 51 | @click.argument("htname", metavar="") 52 | @click.argument("itname", metavar="") 53 | @click.argument("sval", metavar="") 54 | @pass_context 55 | def htable_sets(ctx, htname, itname, sval): 56 | """Set $sht(htname=>itname) to string value 57 | 58 | \b 59 | Parameters: 60 | - the name of hash table 61 | - the name of item 62 | - the string value 63 | """ 64 | command_ctl(ctx, "htable.sets", [htname, itname, sval]) 65 | 66 | 67 | @cli.command("seti", short_help="Set $sht(htname=>itname) to int value") 68 | @click.argument("htname", metavar="") 69 | @click.argument("itname", metavar="") 70 | @click.argument("ival", metavar="", type=int) 71 | @pass_context 72 | def htable_seti(ctx, htname, itname, ival): 73 | """Set $sht(htname=>itname) to int value 74 | 75 | \b 76 | Parameters: 77 | - the name of hash table 78 | - the name of item 79 | - the int value 80 | """ 81 | command_ctl(ctx, "htable.seti", [htname, itname, ival]) 82 | 83 | 84 | @cli.command("setex", short_help="Set expire for $sht(htname=>itname)") 85 | @click.argument("htname", metavar="") 86 | @click.argument("itname", metavar="") 87 | @click.argument("ival", metavar="", type=int) 88 | @pass_context 89 | def htable_setex(ctx, htname, itname, exval): 90 | """Set expire for $sht(htname=>itname) 91 | 92 | \b 93 | Parameters: 94 | - the name of hash table 95 | - the name of item 96 | - the int value 97 | """ 98 | command_ctl(ctx, "htable.setex", [htname, itname, exval]) 99 | 100 | 101 | @cli.command( 102 | "setxs", short_help="Set $sht(htname=>itname) to string value with expire" 103 | ) 104 | @click.argument("htname", metavar="") 105 | @click.argument("itname", metavar="") 106 | @click.argument("sval", metavar="") 107 | @click.argument("exp", metavar="", type=int) 108 | @pass_context 109 | def htable_setxs(ctx, htname, itname, sval, exp): 110 | """Set $sht(htname=>itname) to string value with expire 111 | 112 | \b 113 | Parameters: 114 | - the name of hash table 115 | - the name of item 116 | - the string value 117 | - the expire interval 118 | """ 119 | command_ctl(ctx, "htable.setxs", [htname, itname, sval, exp]) 120 | 121 | 122 | @cli.command( 123 | "setxi", short_help="Set $sht(htname=>itname) to string value with expire" 124 | ) 125 | @click.argument("htname", metavar="") 126 | @click.argument("itname", metavar="") 127 | @click.argument("ival", metavar="", type=int) 128 | @click.argument("exp", metavar="", type=int) 129 | @pass_context 130 | def htable_setxi(ctx, htname, itname, ival, exp): 131 | """Set $sht(htname=>itname) to string value with expire 132 | 133 | \b 134 | Parameters: 135 | - the name of hash table 136 | - the name of item 137 | - the int value 138 | - the expire interval 139 | """ 140 | command_ctl(ctx, "htable.setxi", [htname, itname, ival, exp]) 141 | 142 | 143 | @cli.command("rm", short_help="Remove the item $sht(htname=>itname)") 144 | @click.option( 145 | "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", 146 | ) 147 | @click.argument("htname", metavar="") 148 | @click.argument("itname", metavar="") 149 | @pass_context 150 | def htable_rm(ctx, yes, htname, itname): 151 | """Remove the item $sht(htname=>itname) 152 | 153 | \b 154 | Parameters: 155 | - the name of hash table 156 | - the name of item 157 | """ 158 | if not yes: 159 | print("Removing item. Are you sure? (y/n):", end=" ") 160 | option = input() 161 | if option != "y": 162 | ctx.vlog("Skip removing item [%s]", itname) 163 | return 164 | command_ctl(ctx, "htable.delete", [htname, itname]) 165 | 166 | 167 | @cli.command( 168 | "flush", short_help="Remove all the content of hash table named htname" 169 | ) 170 | @click.argument("htname", metavar="") 171 | @pass_context 172 | def htable_flush(ctx, htname): 173 | """Remove all the content of hash table named htname 174 | 175 | \b 176 | Parameters: 177 | - the name of hash table 178 | """ 179 | command_ctl(ctx, "htable.flush", [htname]) 180 | 181 | 182 | @cli.command( 183 | "reload", 184 | short_help="Reload the content from database for hash table named htname", 185 | ) 186 | @click.argument("htname", metavar="") 187 | @pass_context 188 | def htable_reload(ctx, htname): 189 | """Reload the content from database for hash table named htname 190 | 191 | \b 192 | Parameters: 193 | - the name of hash table 194 | """ 195 | command_ctl(ctx, "htable.reload", [htname]) 196 | 197 | 198 | @cli.command( 199 | "store", 200 | short_help="Write the content to database for hash table named htname", 201 | ) 202 | @click.argument("htname", metavar="") 203 | @pass_context 204 | def htable_store(ctx, htname): 205 | """Store the content to database for hash table named htname 206 | 207 | \b 208 | Parameters: 209 | - the name of hash table 210 | """ 211 | command_ctl(ctx, "htable.stored", [htname]) 212 | 213 | 214 | @cli.command( 215 | "list-tables", short_help="List the hash tables", 216 | ) 217 | @pass_context 218 | def htable_list_tables(ctx): 219 | """List the hash tables 220 | 221 | \b 222 | """ 223 | command_ctl(ctx, "htable.listTables", []) 224 | 225 | 226 | @cli.command( 227 | "stats", short_help="Print statistics for hash tables", 228 | ) 229 | @pass_context 230 | def htable_stats(ctx): 231 | """Print statistics for hash tables 232 | 233 | \b 234 | """ 235 | command_ctl(ctx, "htable.stats", []) 236 | 237 | 238 | @cli.command("db-add", short_help="Add a new htable record to database") 239 | @click.option( 240 | "dbtname", 241 | "--dbtname", 242 | default="htable", 243 | help='Database table name (default: "htable")', 244 | ) 245 | @click.option( 246 | "colkeyname", 247 | "--colkeyname", 248 | default="key_name", 249 | help='Column name for key name (default: "key_name")', 250 | ) 251 | @click.option( 252 | "colkeyvalue", 253 | "--colkeyvalue", 254 | default="key_value", 255 | help='Column name for value (default: "key_value")', 256 | ) 257 | @click.option( 258 | "colvaltype", 259 | "--colvaltype", 260 | default="value_type", 261 | help='Column name for value type (default: "value_type")', 262 | ) 263 | @click.option( 264 | "valtype", "--valtype", type=int, default=0, help="Value type (default: 0)" 265 | ) 266 | @click.argument("keyname", metavar="") 267 | @click.argument("keyvalue", metavar="") 268 | @pass_context 269 | def htable_dbadd( 270 | ctx, 271 | dbtname, 272 | colkeyname, 273 | colkeyvalue, 274 | colvaltype, 275 | valtype, 276 | keyname, 277 | keyvalue, 278 | ): 279 | """Add a new htable record in database table 280 | 281 | \b 282 | Parameters: 283 | - key name 284 | - associated value for key name 285 | """ 286 | ctx.vlog( 287 | "Adding to htable [%s] record [%s] => [%s]", dbtname, keyname, keyvalue 288 | ) 289 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 290 | dbname = dbtname.encode("ascii", "ignore").decode() 291 | col_kname = colkeyname.encode("ascii", "ignore").decode() 292 | col_kvalue = colkeyvalue.encode("ascii", "ignore").decode() 293 | col_valtype = colvaltype.encode("ascii", "ignore").decode() 294 | kname = keyname.encode("ascii", "ignore").decode() 295 | kvalue = keyvalue.encode("ascii", "ignore").decode() 296 | 297 | if valtype == 0: 298 | e.execute( 299 | "insert into {0} ({1}, {2}) values ({3!r}, {4!r})".format( 300 | dbname, col_kname, col_kvalue, kname, kvalue 301 | ) 302 | ) 303 | else: 304 | e.execute( 305 | "insert into {0} ({1}, {2}, {3}) values ({4!r}, {5}, {6!r})".format( 306 | dbname, 307 | col_kname, 308 | col_valtype, 309 | col_kvalue, 310 | kname, 311 | valtype, 312 | kvalue, 313 | ) 314 | ) 315 | 316 | 317 | @cli.command("db-rm", short_help="Remove a record from htable database") 318 | @click.option( 319 | "dbtname", 320 | "--dbtname", 321 | default="htable", 322 | help='Database table name (default: "htable")', 323 | ) 324 | @click.option( 325 | "colkeyname", 326 | "--colkeyname", 327 | default="key_name", 328 | help='Column name for key name (default: "key_name")', 329 | ) 330 | @click.option( 331 | "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", 332 | ) 333 | @click.argument("keyname", metavar="") 334 | @pass_context 335 | def htable_dbrm(ctx, dbtname, colkeyname, yes, keyname): 336 | """Remove a record from htable database table 337 | 338 | \b 339 | Parameters: 340 | - key name to match the record 341 | """ 342 | if not yes: 343 | print("Removing item. Are you sure? (y/n):", end=" ") 344 | option = input() 345 | if option != "y": 346 | ctx.vlog("Skip removing item [%s]", keyname) 347 | return 348 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 349 | e.execute( 350 | "delete from {0} where {1}={2!r}".format( 351 | dbtname.encode("ascii", "ignore").decode(), 352 | colkeyname.encode("ascii", "ignore").decode(), 353 | keyname.encode("ascii", "ignore").decode(), 354 | ) 355 | ) 356 | 357 | 358 | @cli.command("db-show", short_help="Show htable records in database") 359 | @click.option( 360 | "oformat", 361 | "--output-format", 362 | "-F", 363 | type=click.Choice(["raw", "json", "table", "dict"]), 364 | default=None, 365 | help="Format the output", 366 | ) 367 | @click.option( 368 | "ostyle", 369 | "--output-style", 370 | "-S", 371 | default=None, 372 | help="Style of the output (tabulate table format)", 373 | ) 374 | @click.option( 375 | "dbtname", 376 | "--dbtname", 377 | default="htable", 378 | help='Database table name (default: "htable")', 379 | ) 380 | @click.option( 381 | "colkeyname", 382 | "--colkeyname", 383 | default="key_name", 384 | help='Column name for key name (default: "key_name")', 385 | ) 386 | @click.argument("keyname", nargs=-1, metavar="[]") 387 | @pass_context 388 | def htable_dbshow(ctx, oformat, ostyle, dbtname, colkeyname, keyname): 389 | """Show details for records in htable database table 390 | 391 | \b 392 | Parameters: 393 | - key name to match the record (optional) 394 | """ 395 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 396 | if not keyname: 397 | ctx.vlog("Showing all htable database records") 398 | res = e.execute( 399 | "select * from {0}".format( 400 | dbtname.encode("ascii", "ignore").decode() 401 | ) 402 | ) 403 | ioutils_dbres_print(ctx, oformat, ostyle, res) 404 | else: 405 | ctx.vlog("Showing htable database records for key name") 406 | for k in keyname: 407 | res = e.execute( 408 | "select * from {0} where {1}={2!r}".format( 409 | dbtname.encode("ascii", "ignore").decode(), 410 | colkeyname.encode("ascii", "ignore").decode(), 411 | k.encode("ascii", "ignore").decode(), 412 | ) 413 | ) 414 | ioutils_dbres_print(ctx, oformat, ostyle, res) 415 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_jsonrpc.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl_name 4 | from kamcli.iorpc import command_jsonrpc_fifo 5 | from kamcli.iorpc import command_jsonrpc_socket 6 | 7 | 8 | @click.command("jsonrpc", short_help="Execute JSONRPC commands") 9 | @click.option( 10 | "dryrun", 11 | "--dry-run", 12 | is_flag=True, 13 | help="Do not execute the command, only print it", 14 | ) 15 | @click.option( 16 | "nolog", 17 | "--no-log", 18 | is_flag=True, 19 | help="Do not print the log with executed command", 20 | ) 21 | @click.option( 22 | "storepath", 23 | "--store-path", 24 | "-s", 25 | default="", 26 | help="Path on server where to store the result", 27 | ) 28 | @click.argument("cmd", nargs=1, metavar="[]") 29 | @click.argument("params", nargs=-1, metavar="[]") 30 | @pass_context 31 | def cli(ctx, dryrun, nolog, storepath, cmd, params): 32 | """Execute JSONRPC command 33 | 34 | \b 35 | Command alias: rpc 36 | Parameters: 37 | - - the JSONRPC command 38 | - - parameters for JSONRPC command 39 | - by default the value of a parameter is considered 40 | of type string 41 | - to enforce integer value prefix with 'i:' 42 | (e.g., i:10) 43 | - string values can be also prefixed with 's:' 44 | - if a parameter starts with 's:', prefix it with 's:' 45 | Examples: 46 | - rpc system.listMethods 47 | - rpc core.psx 48 | - rpc stats.get_statistics all 49 | - rpc pv.shvSet counter i:123 50 | """ 51 | if not nolog: 52 | ctx.log("Running JSONRPC command: [%s]", cmd) 53 | if ctx.gconfig.get("jsonrpc", "transport") == "socket": 54 | command_jsonrpc_socket( 55 | ctx, 56 | dryrun, 57 | ctx.gconfig.get("jsonrpc", "srvaddr"), 58 | ctx.gconfig.get("jsonrpc", "rcvaddr"), 59 | ctx.gconfig.get("jsonrpc", "outformat"), 60 | storepath, 61 | command_ctl_name(cmd, "rpc"), 62 | params, 63 | ) 64 | else: 65 | command_jsonrpc_fifo( 66 | ctx, 67 | dryrun, 68 | ctx.gconfig.get("jsonrpc", "path"), 69 | ctx.gconfig.get("jsonrpc", "rplnamebase"), 70 | ctx.gconfig.get("jsonrpc", "outformat"), 71 | storepath, 72 | command_ctl_name(cmd, "rpc"), 73 | params, 74 | ) 75 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_moni.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import click 4 | import json 5 | from kamcli.cli import pass_context 6 | from kamcli.iorpc import command_ctl 7 | 8 | 9 | @click.command("moni", short_help="Monitor relevant statistics") 10 | @click.option( 11 | "norefresh", 12 | "--no-refresh", 13 | is_flag=True, 14 | help="Do not refresh (execute once)", 15 | ) 16 | @pass_context 17 | def cli(ctx, norefresh): 18 | """Monitor relevant statistics on display 19 | 20 | \b 21 | Parameters: 22 | - --no-refresh - execute once 23 | Monitored statistics: 24 | tm, sl and usrloc 25 | """ 26 | 27 | def clear(): 28 | return os.system("tput reset") 29 | 30 | count = 0 31 | slist = [ 32 | "rcv_requests", 33 | "fwd_requests", 34 | "rcv_replies", 35 | "fwd_replies", 36 | "free_size", 37 | "sent_replies", 38 | "tmx:", 39 | "usrloc:", 40 | ] 41 | if norefresh is True: 42 | command_ctl( 43 | ctx, "stats.get_statistics", slist, {"func": cmd_moni_result_print} 44 | ) 45 | else: 46 | while True: 47 | count = count + 1 48 | command_ctl( 49 | ctx, 50 | "stats.get_statistics", 51 | slist, 52 | {"func": cmd_moni_result_print}, 53 | ) 54 | print() 55 | print( 56 | "[cycle #: " 57 | + str(count) 58 | + "; if constant make sure server is running]" 59 | ) 60 | time.sleep(2) 61 | clear() 62 | 63 | 64 | def cmd_moni_result_print(ctx, response, params=None): 65 | ctx.vlog("formatting the response for command ps") 66 | print() 67 | rdata = json.loads(response.decode()) 68 | if "result" in rdata: 69 | rd = rdata["result"] 70 | for a, b in zip(rd[::2], rd[1::2]): 71 | print("{:<40}{:<}".format(a, b)) 72 | else: 73 | print(json.dumps(rdata, indent=4, separators=(",", ": "))) 74 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_mtree.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "mtree", 10 | help="Manage mtree module (memory trees)", 11 | short_help="Manage mtree module", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("db-add", short_help="Add a new mtree record to database") 19 | @click.option( 20 | "tname", 21 | "--tname", 22 | default="", 23 | help='Tree name to be stored in column tname (default: "")', 24 | ) 25 | @click.option( 26 | "coltprefix", 27 | "--coltprefix", 28 | default="tprefix", 29 | help='Column name for prefix (default: "tprefix")', 30 | ) 31 | @click.option( 32 | "coltvalue", 33 | "--coltvalue", 34 | default="tvalue", 35 | help='Column name for value (default: "tvalue")', 36 | ) 37 | @click.argument("dbtname", metavar="") 38 | @click.argument("tprefix", metavar="") 39 | @click.argument("tvalue", metavar="") 40 | @pass_context 41 | def mtree_dbadd(ctx, tname, coltprefix, coltvalue, dbtname, tprefix, tvalue): 42 | """Add a new tree record in database table 43 | 44 | \b 45 | Parameters: 46 | - name of tree database table 47 | - tree prefix 48 | - associated value for prefix 49 | """ 50 | ctx.vlog( 51 | "Adding to tree [%s] record [%s] => [%s]", dbtname, tprefix, tvalue 52 | ) 53 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 54 | dbname = dbtname.encode("ascii", "ignore").decode() 55 | col_pref = coltprefix.encode("ascii", "ignore").decode() 56 | col_val = coltvalue.encode("ascii", "ignore").decode() 57 | prefix = tprefix.encode("ascii", "ignore").decode() 58 | val = tvalue.encode("ascii", "ignore").decode() 59 | if not tname: 60 | e.execute( 61 | "insert into {0} ({1}, {2}) values ({3!r}, {4!r})".format( 62 | dbname, col_pref, col_val, prefix, val 63 | ) 64 | ) 65 | else: 66 | e.execute( 67 | "insert into {0} (tname, {1}, {2}) values " 68 | "({3!r}, {4!r}, {5!r})".format( 69 | dbname, 70 | tname.encode("ascii", "ignore").decode(), 71 | col_pref, 72 | col_val, 73 | prefix, 74 | val, 75 | ) 76 | ) 77 | 78 | 79 | @cli.command("db-rm", short_help="Remove a record from mtree table") 80 | @click.option( 81 | "coltprefix", 82 | "--coltprefix", 83 | default="tprefix", 84 | help='Column name for prefix (default: "tprefix")', 85 | ) 86 | @click.option( 87 | "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", 88 | ) 89 | @click.argument("dbtname", metavar="") 90 | @click.argument("tprefix", metavar="") 91 | @pass_context 92 | def mtree_dbrm(ctx, coltprefix, yes, dbtname, tprefix): 93 | """Remove a record from tree database table 94 | 95 | \b 96 | Parameters: 97 | - name of tree database table 98 | - tree prefix value to match the record 99 | """ 100 | if not yes: 101 | print("Removing prefix. Are you sure? (y/n):", end=" ") 102 | option = input() 103 | if option != "y": 104 | ctx.vlog("Skip removing prefix [%s]", tprefix) 105 | return 106 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 107 | e.execute( 108 | "delete from {0} where {1}={2!r}".format( 109 | dbtname.encode("ascii", "ignore").decode(), 110 | coltprefix.encode("ascii", "ignore").decode(), 111 | tprefix.encode("ascii", "ignore").decode(), 112 | ) 113 | ) 114 | 115 | 116 | @cli.command("db-show", short_help="Show mtree records in database") 117 | @click.option( 118 | "oformat", 119 | "--output-format", 120 | "-F", 121 | type=click.Choice(["raw", "json", "table", "dict"]), 122 | default=None, 123 | help="Format the output", 124 | ) 125 | @click.option( 126 | "ostyle", 127 | "--output-style", 128 | "-S", 129 | default=None, 130 | help="Style of the output (tabulate table format)", 131 | ) 132 | @click.option( 133 | "coltprefix", 134 | "--coltprefix", 135 | default="tprefix", 136 | help='Column name for prefix (default: "tprefix")', 137 | ) 138 | @click.argument("dbtname", metavar="") 139 | @click.argument("tprefix", nargs=-1, metavar="[]") 140 | @pass_context 141 | def mtree_dbshow(ctx, oformat, ostyle, coltprefix, dbtname, tprefix): 142 | """Show details for records in mtree database table 143 | 144 | \b 145 | Parameters: 146 | - name of tree database table 147 | - tree prefix value to match the record 148 | """ 149 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 150 | if not tprefix: 151 | ctx.vlog("Showing all tree database records") 152 | res = e.execute( 153 | "select * from {0}".format( 154 | dbtname.encode("ascii", "ignore").decode() 155 | ) 156 | ) 157 | ioutils_dbres_print(ctx, oformat, ostyle, res) 158 | else: 159 | for tp in tprefix: 160 | ctx.vlog("Showing tree database records for prefix {0}".format(tp)) 161 | res = e.execute( 162 | "select * from {0} where {1}={2!r}".format( 163 | dbtname.encode("ascii", "ignore").decode(), 164 | coltprefix.encode("ascii", "ignore").decode(), 165 | tp.encode("ascii", "ignore").decode(), 166 | ) 167 | ) 168 | ioutils_dbres_print(ctx, oformat, ostyle, res) 169 | 170 | 171 | @cli.command("list", short_help="Show the records in memory tree") 172 | @click.argument("tname", metavar="") 173 | @pass_context 174 | def mtree_show(ctx, tname): 175 | """Show the tree records in memory 176 | 177 | \b 178 | Parameters: 179 | - tree name 180 | """ 181 | command_ctl(ctx, "mtree.list", [tname]) 182 | 183 | 184 | @cli.command( 185 | "reload", short_help="Reload tree records from database into memory" 186 | ) 187 | @click.argument("tname", metavar="") 188 | @pass_context 189 | def mtree_reload(ctx, tname): 190 | """Reload tree records from database into memory 191 | 192 | \b 193 | Parameters: 194 | - tree name 195 | """ 196 | command_ctl(ctx, "mtree.reload", [tname]) 197 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_pike.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | 5 | 6 | @click.group( 7 | "pike", 8 | help="Manage pike module (source IP tracking)", 9 | short_help="Manage pike module", 10 | ) 11 | @pass_context 12 | def cli(ctx): 13 | pass 14 | 15 | 16 | @cli.command("list", short_help="Show the details of tracked IP addresses") 17 | @pass_context 18 | def pike_list(ctx): 19 | """Show details of tracked IP addresses 20 | 21 | \b 22 | """ 23 | command_ctl(ctx, "pike.list", []) 24 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_ping.py: -------------------------------------------------------------------------------- 1 | import click 2 | import json 3 | from kamcli.cli import pass_context 4 | from kamcli.iorpc import command_ctl 5 | 6 | 7 | @click.command("ping", short_help="Send an OPTIONS ping request") 8 | @click.option( 9 | "nowait", "--nowait", "-n", is_flag=True, help="Do wait for response", 10 | ) 11 | @click.option("furi", "--furi", default="", help='From URI (default: "")') 12 | @click.argument("uri", nargs=1, metavar="[]") 13 | @pass_context 14 | def cli(ctx, nowait, furi, uri): 15 | """Send an OPTIONS ping request 16 | 17 | Parameters: 18 | - - the SIP URI of the target 19 | \b 20 | """ 21 | lcmd = "tm.t_uac_wait_block" 22 | if nowait: 23 | lcmd = "tm.t_uac_start" 24 | lfrom = "" 25 | if len(furi) == 0: 26 | ldomain = ctx.gconfig.get("main", "domain", fallback="localhost") 27 | lfrom = "sip:daemon@" + ldomain 28 | else: 29 | lfrom = furi 30 | plist = [ 31 | "OPTIONS", 32 | uri, 33 | ".", 34 | ".", 35 | "From: <" 36 | + lfrom 37 | + ">\r\nTo: <" 38 | + uri 39 | + ">\r\n Contact: <" 40 | + lfrom 41 | + ">\r\n", 42 | ] 43 | command_ctl(ctx, lcmd, plist) 44 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_pipelimit.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "pipelimit", 10 | help="Manage pipelimit module", 11 | short_help="Manage pipelimit module", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("db-add", short_help="Add a new pipelimit record to database") 19 | @click.option( 20 | "dbtname", 21 | "--dbtname", 22 | default="pl_pipes", 23 | help='The name of database table (default: "pl_pipes")', 24 | ) 25 | @click.option( 26 | "alg", 27 | "--alg", 28 | default="taildrop", 29 | help='The name of the algorithm (default: "taildrop")', 30 | ) 31 | @click.argument("pipeid", metavar="") 32 | @click.argument("limit", metavar="", type=click.INT) 33 | @pass_context 34 | def pipelimit_dbadd(ctx, dbtname, alg, pipeid, limit): 35 | """Add a new pipelimit record in database table 36 | 37 | \b 38 | Parameters: 39 | - pipe name id 40 | - pipe limit 41 | """ 42 | ctx.vlog( 43 | "Adding to pipelimit table [%s] record: [%s] [%s] [%s]", 44 | dbtname, 45 | pipeid, 46 | limit, 47 | alg, 48 | ) 49 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 50 | dbtval = dbtname.encode("ascii", "ignore").decode() 51 | pidval = pipeid.encode("ascii", "ignore").decode() 52 | algval = alg.encode("ascii", "ignore").decode() 53 | e.execute( 54 | "insert into {0} (pipeid, algorithm, plimit) values ({1!r}, {2!r}, {3})".format( 55 | dbtval, pidval, algval, limit 56 | ) 57 | ) 58 | 59 | 60 | @cli.command("db-show", short_help="Show pipelimit records in database") 61 | @click.option( 62 | "oformat", 63 | "--output-format", 64 | "-F", 65 | type=click.Choice(["raw", "json", "table", "dict"]), 66 | default=None, 67 | help="Format the output", 68 | ) 69 | @click.option( 70 | "ostyle", 71 | "--output-style", 72 | "-S", 73 | default=None, 74 | help="Style of the output (tabulate table format)", 75 | ) 76 | @click.option( 77 | "dbtname", 78 | "--dbtname", 79 | default="pl_pipes", 80 | help='The name of database table (default: "pl_pipes")', 81 | ) 82 | @click.argument("pipeid", nargs=-1, metavar="[]") 83 | @pass_context 84 | def pipelimit_dbshow(ctx, oformat, ostyle, dbtname, pipeid): 85 | """Show details for records in pipelimit database table 86 | 87 | \b 88 | Parameters: 89 | - pipe name id (optional) 90 | """ 91 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 92 | if not pipeid: 93 | ctx.vlog("Showing all pipelimit database records") 94 | res = e.execute( 95 | "select * from {0}".format( 96 | dbtname.encode("ascii", "ignore").decode() 97 | ) 98 | ) 99 | ioutils_dbres_print(ctx, oformat, ostyle, res) 100 | else: 101 | for p in pipeid: 102 | ctx.vlog( 103 | "Showing pipelimit database records for pipeid {0}".format(p) 104 | ) 105 | res = e.execute( 106 | "select * from {0} where pipeid={1!r}".format( 107 | dbtname.encode("ascii", "ignore").decode(), 108 | p.encode("ascii", "ignore").decode(), 109 | ) 110 | ) 111 | ioutils_dbres_print(ctx, oformat, ostyle, res) 112 | 113 | 114 | @cli.command("db-rm", short_help="Remove a record from pipelimit table") 115 | @click.option( 116 | "dbtname", 117 | "--dbtname", 118 | default="pl_pipes", 119 | help='The name of database table (default: "pl_pipes")', 120 | ) 121 | @click.option( 122 | "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", 123 | ) 124 | @click.argument("pipeid", metavar="") 125 | @pass_context 126 | def pipelimit_dbrm(ctx, dbtname, yes, pipeid): 127 | """Remove a record from pipelimit database table 128 | 129 | \b 130 | Parameters: 131 | - name of pipelimit database table 132 | """ 133 | if not yes: 134 | print("Removing pipe. Are you sure? (y/n):", end=" ") 135 | option = input() 136 | if option != "y": 137 | ctx.vlog("Skip removing pipe [%s]", pipeid) 138 | return 139 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 140 | e.execute( 141 | "delete from {0} where pipeid={1!r}".format( 142 | dbtname.encode("ascii", "ignore").decode(), 143 | pipeid.encode("ascii", "ignore").decode(), 144 | ) 145 | ) 146 | 147 | 148 | @cli.command("list", short_help="List the details of one or all pipes") 149 | @click.argument("pipeid", nargs=-1, metavar="[]") 150 | @pass_context 151 | def pipelimit_list(ctx, pipeid): 152 | """List the details of one or all pipes 153 | 154 | \b 155 | Parameters: 156 | [] - pipe name id 157 | """ 158 | if not pipeid: 159 | command_ctl(ctx, "pl.list", []) 160 | else: 161 | for p in pipeid: 162 | command_ctl(ctx, "pl.list", [p]) 163 | 164 | 165 | @cli.command("stats", short_help="Show pipelimit stats") 166 | @pass_context 167 | def pipelimit_stats(ctx): 168 | """Show pipelimit stats 169 | 170 | \b 171 | """ 172 | command_ctl(ctx, "pl.stats", []) 173 | 174 | 175 | @cli.command("set-pipe", short_help="Set pipe algorithm and limit") 176 | @click.argument("pipeid", metavar="") 177 | @click.argument("alg", metavar="") 178 | @click.argument("limit", metavar="", type=click.INT) 179 | @pass_context 180 | def pipelimit_set_pipe(ctx, pipeid, alg, limit): 181 | """Set pipe algorithm and limit 182 | 183 | \b 184 | Parameters: 185 | - pipe name id 186 | - pipe algorithm 187 | - pipe limit 188 | """ 189 | command_ctl(ctx, "pl.set_pipe", [pipeid, alg, limit]) 190 | 191 | 192 | @cli.command("reset-pipe", short_help="Reset values associated with the pipe") 193 | @click.argument("pipeid", metavar="") 194 | @pass_context 195 | def pipelimit_reset_pipe(ctx, pipeid): 196 | """Reset values associated with the pipe 197 | 198 | \b 199 | Parameters: 200 | - pipe name id 201 | """ 202 | command_ctl(ctx, "pl.reset_pipe", [pipeid]) 203 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_pkg.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | 5 | 6 | @click.group( 7 | "pkg", 8 | help="Private memory (pkg) management", 9 | short_help="Private memory (pkg) management", 10 | ) 11 | @pass_context 12 | def cli(ctx): 13 | pass 14 | 15 | 16 | @cli.command("stats", short_help="Show the stats for pkg memory") 17 | @pass_context 18 | def pkg_stats(ctx): 19 | """Show the stats for pkg memory 20 | 21 | \b 22 | """ 23 | command_ctl(ctx, "pkg.stats", []) 24 | 25 | 26 | @cli.command("info", short_help="Show the info for pkg memory manager") 27 | @pass_context 28 | def pkg_info(ctx): 29 | """Show the info for pkg memory manager 30 | 31 | \b 32 | """ 33 | command_ctl(ctx, "pkg.info", []) 34 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_ps.py: -------------------------------------------------------------------------------- 1 | import click 2 | import json 3 | from kamcli.cli import pass_context 4 | from kamcli.iorpc import command_ctl 5 | 6 | 7 | @click.command("ps", short_help="Print the list of kamailio processes") 8 | @pass_context 9 | def cli(ctx): 10 | """Show details about running kamailio processes 11 | 12 | \b 13 | """ 14 | command_ctl(ctx, "core.psx", [], {"func": cmd_ps_result_print}) 15 | 16 | 17 | # callback to print the result of the rpc command 18 | def cmd_ps_result_print(ctx, response, params=None): 19 | ctx.vlog("formatting the response for command ps") 20 | rdata = json.loads(response.decode()) 21 | if "result" in rdata: 22 | for r in rdata["result"]: 23 | ctx.printf("%4d %5d %s", r["IDX"], r["PID"], r["DSC"]) 24 | else: 25 | print(json.dumps(rdata, indent=4, separators=(",", ": "))) 26 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_pstrap.py: -------------------------------------------------------------------------------- 1 | import click 2 | import os 3 | import subprocess 4 | import json 5 | import datetime 6 | from kamcli.cli import pass_context 7 | from kamcli.iorpc import command_ctl 8 | 9 | 10 | @click.command( 11 | "pstrap", 12 | help="Store runtime details and gdb full backtrace for all Kamailio processes to a file", 13 | short_help="Get runtime details and gdb backtrace with ps", 14 | ) 15 | @click.option( 16 | "all", 17 | "--all", 18 | "-a", 19 | is_flag=True, 20 | help="Print all details in the trap file", 21 | ) 22 | @click.option( 23 | "sysps", 24 | "--sys-ps", 25 | "-s", 26 | is_flag=True, 27 | help="Get the system ps line for each PID", 28 | ) 29 | @pass_context 30 | def cli(ctx, all, sysps): 31 | """Store runtime details and gdb full backtrace for all Kamailio processes to a file 32 | 33 | \b 34 | """ 35 | ofile = ( 36 | "/tmp/gdb_kamailio_" 37 | + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") 38 | + ".txt" 39 | ) 40 | ctx.printf("Trap file: " + ofile) 41 | os.system( 42 | 'echo "--- ps output -----------------------------------------------------" >' 43 | + ofile 44 | ) 45 | 46 | os.system( 47 | "ps auxw | grep kamailio | grep -v grep | grep -v kamcli | sort >>" 48 | + ofile 49 | ) 50 | 51 | if all: 52 | os.system("echo >>" + ofile) 53 | os.system("kamailio -v >>" + ofile) 54 | os.system("echo >>" + ofile) 55 | os.system("kamailio -I >>" + ofile) 56 | 57 | ctx.printf("Trapping Kamailio processes with gdb. It can take a while.") 58 | 59 | child = subprocess.Popen( 60 | ["pgrep", "kamailio"], stdout=subprocess.PIPE, shell=False, text=True 61 | ) 62 | response = child.communicate()[0] 63 | if len(response) > 0: 64 | for pid in response.split(): 65 | ctx.printnlf(".") 66 | os.system("echo >>" + ofile) 67 | os.system( 68 | 'echo "---start ' 69 | + pid 70 | + ' -----------------------------------------------------" >>' 71 | + ofile 72 | ) 73 | if sysps: 74 | os.system( 75 | "ps -o pid,ni,pri,pcpu,stat,pmem,rss,vsz,args -w -p " 76 | + str(pid) 77 | + " >>" 78 | + ofile 79 | ) 80 | os.system( 81 | "gdb kamailio " 82 | + pid 83 | + ' -batch -batch --eval-command="p process_no" --eval-command="p pt[process_no]" --eval-command="bt full" >>' 84 | + ofile 85 | + " 2>&1" 86 | ) 87 | os.system( 88 | 'echo "---end ' 89 | + pid 90 | + ' -------------------------------------------------------" >>' 91 | + ofile 92 | ) 93 | else: 94 | os.system("echo >>" + ofile) 95 | os.system( 96 | 'echo "Unable to get the list with PIDs of running Kamailio processes" >>' 97 | + ofile 98 | ) 99 | 100 | ctx.printf("") 101 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_rpcmethods.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | 5 | 6 | @click.command( 7 | "rpcmethods", short_help="Print the list of available raw RPC methods" 8 | ) 9 | @pass_context 10 | def cli(ctx): 11 | """Print the list of available raw RPC methods 12 | 13 | \b 14 | Show all available methods that can be executed with command 'rpc'. 15 | Examples: 16 | - kamcli rpcmethods 17 | - kamcli rpc 18 | """ 19 | command_ctl(ctx, "system.listMethods", []) 20 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_rtpengine.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "rtpengine", 10 | help="Manage rtpengine module", 11 | short_help="Manage rtpengine module", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("showdb", short_help="Show the rtpengine records in database") 19 | @click.option( 20 | "oformat", 21 | "--output-format", 22 | "-F", 23 | type=click.Choice(["raw", "json", "table", "dict"]), 24 | default=None, 25 | help="Format the output", 26 | ) 27 | @click.option( 28 | "ostyle", 29 | "--output-style", 30 | "-S", 31 | default=None, 32 | help="Style of the output (tabulate table format)", 33 | ) 34 | @pass_context 35 | def rtpengine_showdb(ctx, oformat, ostyle): 36 | """Show the rtpengine records in database table 37 | 38 | \b 39 | Parameters: 40 | none 41 | """ 42 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 43 | ctx.vlog("Showing all rtpengine database records") 44 | res = e.execute("select * from rtpengine") 45 | ioutils_dbres_print(ctx, oformat, ostyle, res) 46 | 47 | 48 | @cli.command("show", short_help="Show the rtpengine records in memory") 49 | @pass_context 50 | def rtpengine_show(ctx): 51 | """Show the rtpengine records in memory 52 | 53 | \b 54 | Parameters: 55 | none 56 | """ 57 | command_ctl(ctx, "rtpengine.show", ["all"]) 58 | 59 | 60 | @cli.command( 61 | "reload", 62 | short_help="Reload the rtpengine records from database into memory", 63 | ) 64 | @pass_context 65 | def rtpengine_reload(ctx): 66 | """Reload the rtpengine records from database into memory 67 | 68 | \b 69 | Parameters: 70 | none 71 | """ 72 | command_ctl(ctx, "rtpengine.reload", []) 73 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_shm.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | 5 | 6 | @click.group( 7 | "shm", 8 | help="Shared memory (shm) management", 9 | short_help="Shared memory (shm) management", 10 | ) 11 | @pass_context 12 | def cli(ctx): 13 | pass 14 | 15 | 16 | @cli.command("stats", short_help="Show the stats for shared (shm) memory") 17 | @pass_context 18 | def shm_stats(ctx): 19 | """Show the stats for shared (shm) memory 20 | 21 | \b 22 | """ 23 | command_ctl(ctx, "shm.stats", []) 24 | 25 | 26 | @cli.command( 27 | "info", short_help="Show the info for shared (shm) memory manager" 28 | ) 29 | @pass_context 30 | def shm_info(ctx): 31 | """Show the info for shared (shm) memory manager 32 | 33 | \b 34 | """ 35 | command_ctl(ctx, "shm.info", []) 36 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_shv.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | 5 | 6 | @click.group( 7 | "shv", 8 | help="Manage $shv(name) variables", 9 | short_help="Manage $shv(name) variables", 10 | ) 11 | @pass_context 12 | def cli(ctx): 13 | pass 14 | 15 | 16 | @cli.command("get", short_help="Get the value for $shv(name)") 17 | @click.argument("name", nargs=-1, metavar="") 18 | @pass_context 19 | def shv_get(ctx, name): 20 | """Get the value for $shv(name) 21 | 22 | \b 23 | Parameters: 24 | - the name of shv variable 25 | """ 26 | if not name: 27 | command_ctl(ctx, "pv.shvGet") 28 | else: 29 | for n in name: 30 | command_ctl(ctx, "pv.shvGet", [n]) 31 | 32 | 33 | @cli.command("sets", short_help="Set $shv(name) to string value") 34 | @click.argument("name", metavar="") 35 | @click.argument("sval", metavar="") 36 | @pass_context 37 | def shv_sets(ctx, name, sval): 38 | """Set $shv(name) to string value 39 | 40 | \b 41 | Parameters: 42 | - the name of shv variable 43 | - the string value 44 | """ 45 | command_ctl(ctx, "pv.shvSet", [name, "str", sval]) 46 | 47 | 48 | @cli.command("seti", short_help="Set $shv(name) to int value") 49 | @click.argument("name", metavar="") 50 | @click.argument("ival", metavar="", type=int) 51 | @pass_context 52 | def srv_seti(ctx, name, ival): 53 | """Set $shv(name) to int value 54 | 55 | \b 56 | Parameters: 57 | - the name of shv variable 58 | - the int value 59 | """ 60 | command_ctl(ctx, "pv.shvSet", [name, "int", ival]) 61 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_sipreq.py: -------------------------------------------------------------------------------- 1 | import click 2 | import json 3 | from kamcli.cli import pass_context 4 | from kamcli.iorpc import command_ctl 5 | 6 | 7 | @click.command("sipreq", short_help="Send a SIP request via RPC command") 8 | @click.option( 9 | "nowait", "--nowait", "-n", is_flag=True, help="Do not wait for response", 10 | ) 11 | @click.option( 12 | "method", 13 | "--method", 14 | "-m", 15 | default="OPTIONS", 16 | help='SIP method (default: "OPTIONS")', 17 | ) 18 | @click.option( 19 | "furi", "--furi", "-f", default="", help='From URI (default: "")' 20 | ) 21 | @click.option( 22 | "curi", "--curi", "-c", default="", help='Contact URI (default: "")' 23 | ) 24 | @click.option( 25 | "duri", "--duri", "-d", default=".", help='Destination URI (default: "")' 26 | ) 27 | @click.option( 28 | "hdrs", "--hdrs", "-a", default="", help='Additional headers (default: "")' 29 | ) 30 | @click.option( 31 | "socket", 32 | "--socket", 33 | "-s", 34 | default=".", 35 | help='Socket for sending (default: "")', 36 | ) 37 | @click.option( 38 | "body", "--body", "-b", default="", help='Destination URI (default: "")' 39 | ) 40 | @click.argument("uri", nargs=1, metavar="[]") 41 | @pass_context 42 | def cli(ctx, nowait, method, furi, curi, duri, hdrs, socket, body, uri): 43 | """Send a SIP request via RPC command 44 | 45 | Parameters: 46 | - - the SIP URI of the target 47 | 48 | Note: additional headers must not include From, To or Contact 49 | headers, these are generated from the other parameters. If From 50 | URI is not provided, then it is generated using local host. The To 51 | URI is set to . If Contact URI is not provided, then it is set 52 | to From URI. If --curi=none, the Contact header is not added. 53 | \b 54 | """ 55 | lcmd = "tm.t_uac_wait_block" 56 | if nowait: 57 | lcmd = "tm.t_uac_start" 58 | lfrom = "" 59 | if len(furi) == 0: 60 | ldomain = ctx.gconfig.get("main", "domain", fallback="localhost") 61 | lfrom = "sip:daemon@" + ldomain 62 | else: 63 | lfrom = furi 64 | lcuri = lfrom 65 | if len(curi) != 0: 66 | if curi == "none": 67 | lcuri = "" 68 | else: 69 | lcuri = curi 70 | lhdrs = "From: <" + lfrom + ">\r\nTo: <" + uri + ">\r\n" 71 | if len(lcuri) != 0: 72 | lhdrs += "Contact: <" + lcuri + ">\r\n" 73 | if len(hdrs) != 0: 74 | lhdrs += hdrs 75 | if lhdrs[-2:] != "\r\n": 76 | lhdrs += "\r\n" 77 | plist = [ 78 | method, 79 | uri, 80 | duri, 81 | socket, 82 | lhdrs, 83 | body, 84 | ] 85 | command_ctl(ctx, lcmd, plist) 86 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_speeddial.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.cli import parse_user_spec 6 | 7 | 8 | @click.group( 9 | "speeddial", 10 | help="Manage speed dial records", 11 | short_help="Manage speed dial records", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("add", short_help="Add a speed dial record") 19 | @click.option( 20 | "table", 21 | "--table", 22 | default="speed_dial", 23 | help="Name of database table (default: speed_dial)", 24 | ) 25 | @click.option( 26 | "fname", 27 | "--fname", 28 | default="", 29 | help='Value for fname column (default: "")', 30 | ) 31 | @click.option( 32 | "lname", 33 | "--lname", 34 | default="", 35 | help='Value for lname column (default: "")', 36 | ) 37 | @click.argument("userid", metavar="") 38 | @click.argument("shortdial", metavar="") 39 | @click.argument("targeturi", metavar="") 40 | @click.argument("desc", metavar="", nargs=-1) 41 | @pass_context 42 | def speeddial_add(ctx, table, fname, lname, userid, shortdial, targeturi, desc): 43 | """Add a speed dial record 44 | 45 | \b 46 | Parameters: 47 | - username, AoR or SIP URI for subscriber 48 | - username, AoR or SIP URI for short dial 49 | - username, AoR or SIP URI for target 50 | [] - description for speed dial record 51 | """ 52 | udata = parse_user_spec(ctx, userid) 53 | sdata = parse_user_spec(ctx, shortdial) 54 | tdata = parse_user_spec(ctx, targeturi) 55 | ctx.vlog( 56 | "Adding for user [%s@%s] short dial [%s@%s] target [sip:%s@%s]", 57 | udata["username"], 58 | udata["domain"], 59 | sdata["username"], 60 | sdata["domain"], 61 | tdata["username"], 62 | tdata["domain"], 63 | ) 64 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 65 | uri = "sip:{}@{}".format(tdata["username"], tdata["domain"]) 66 | if not desc: 67 | e.execute( 68 | "insert into {0} (username, domain, sd_username, " 69 | "sd_domain, new_uri, fname, lname) values ({1!r}, {2!r}, {3!r}, " 70 | "{4!r}, {5!r}, {6!r}, {7!r})".format( 71 | table, 72 | udata["username"], 73 | udata["domain"], 74 | sdata["username"], 75 | sdata["domain"], 76 | uri, 77 | fname, 78 | lname, 79 | ) 80 | ) 81 | else: 82 | e.execute( 83 | "insert into {0} (username, domain, sd_username, " 84 | "sd_domain, new_uri, fname, lname, description) values ({1!r}, {2!r}, {3!r}, " 85 | "{4!r}, {5!r}, {6!r}, {7!r}, {8!r})".format( 86 | table, 87 | udata["username"], 88 | udata["domain"], 89 | sdata["username"], 90 | sdata["domain"], 91 | uri, 92 | fname, 93 | lname, 94 | desc, 95 | ) 96 | ) 97 | 98 | 99 | @cli.command("rm", short_help="Remove speed dial records") 100 | @click.option( 101 | "table", 102 | "--table", 103 | default="speed_dial", 104 | help="Name of database table (default: speed_dial)", 105 | ) 106 | @click.argument("userid", metavar="") 107 | @click.argument("shortdial", metavar="", nargs=-1) 108 | @pass_context 109 | def speeddial_rm(ctx, table, userid, shortdial): 110 | """Remove a user from groups (revoke privilege) 111 | 112 | \b 113 | Parameters: 114 | - username, AoR or SIP URI for subscriber 115 | - username, AoR or SIP URI for short dial 116 | """ 117 | udata = parse_user_spec(ctx, userid) 118 | ctx.log( 119 | "Removing speed dial for record [%s@%s]", 120 | udata["username"], 121 | udata["domain"], 122 | ) 123 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 124 | if not shortdial: 125 | e.execute( 126 | "delete from {0} where username={1!r} and domain={2!r}".format( 127 | table, udata["username"], udata["domain"], 128 | ) 129 | ) 130 | else: 131 | for s in shortdial: 132 | sdata = parse_user_spec(ctx, s) 133 | e.execute( 134 | "delete from {0} where username={1!r} and domain={2!r} " 135 | "and sd_username={3!r} and sd_domain={4!r}".format( 136 | table, 137 | udata["username"], 138 | udata["domain"], 139 | sdata["username"], 140 | sdata["domain"], 141 | ) 142 | ) 143 | 144 | 145 | @cli.command("show", short_help="Show speed dial records") 146 | @click.option( 147 | "oformat", 148 | "--output-format", 149 | "-F", 150 | type=click.Choice(["raw", "json", "table", "dict"]), 151 | default=None, 152 | help="Format the output", 153 | ) 154 | @click.option( 155 | "ostyle", 156 | "--output-style", 157 | "-S", 158 | default=None, 159 | help="Style of the output (tabulate table format)", 160 | ) 161 | @click.option( 162 | "table", 163 | "--table", 164 | default="speed_dial", 165 | help="Name of database table (default: speed_dial)", 166 | ) 167 | @click.argument("userid", metavar="[]") 168 | @click.argument("shortdial", nargs=-1, metavar="[]") 169 | @pass_context 170 | def speeddial_show(ctx, oformat, ostyle, table, userid, shortdial): 171 | """Show details for speed dial records 172 | 173 | \b 174 | Parameters: 175 | - username, AoR or SIP URI for user or alias 176 | [] - username, AoR or SIP URI for short dial (optional) 177 | """ 178 | udata = parse_user_spec(ctx, userid) 179 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 180 | 181 | ctx.vlog( 182 | "Showing speed dial records for user [%s@%s]", 183 | udata["username"], 184 | udata["domain"], 185 | ) 186 | if not shortdial: 187 | res = e.execute( 188 | "select * from {0} where username={1!r} and domain={2!r}".format( 189 | table, udata["username"], udata["domain"], 190 | ) 191 | ) 192 | ioutils_dbres_print(ctx, oformat, ostyle, res) 193 | else: 194 | for s in shortdial: 195 | sdata = parse_user_spec(ctx, s) 196 | res = e.execute( 197 | "select * from {0} where username={1!r} and domain={2!r} " 198 | "and sd_username={3!r} and sd_domain={4!r}".format( 199 | table, 200 | udata["username"], 201 | udata["domain"], 202 | sdata["username"], 203 | sdata["domain"], 204 | ) 205 | ) 206 | ioutils_dbres_print(ctx, oformat, ostyle, res) 207 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_srv.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | import sys 5 | import json 6 | import subprocess 7 | 8 | 9 | @click.group("srv", help="Common server interaction commands") 10 | @pass_context 11 | def cli(ctx): 12 | pass 13 | 14 | 15 | @cli.command("sockets", short_help="Show the list of listen sockets") 16 | @pass_context 17 | def srv_sockets(ctx): 18 | """Show the list of listen sockets 19 | 20 | \b 21 | """ 22 | command_ctl(ctx, "corex.list_sockets") 23 | 24 | 25 | @cli.command("aliases", short_help="Show the list of server domain aliases") 26 | @pass_context 27 | def srv_aliases(ctx): 28 | """Show the list of server domain aliases 29 | 30 | \b 31 | """ 32 | command_ctl(ctx, "corex.list_aliases") 33 | 34 | 35 | @cli.command("rpclist", short_help="Show the list of server rpc commands") 36 | @pass_context 37 | def srv_rpclist(ctx): 38 | """Show the list of server rpc commands 39 | 40 | \b 41 | """ 42 | command_ctl(ctx, "system.listMethods") 43 | 44 | 45 | @cli.command("rpchelp", short_help="Show the help text for rpc command") 46 | @click.argument("command", metavar="") 47 | @pass_context 48 | def srv_rpchelp(ctx, command): 49 | """Show the help text for rpc command 50 | 51 | \b 52 | """ 53 | command_ctl(ctx, "system.methodHelp", [command]) 54 | 55 | 56 | @cli.command("info", short_help="Show server info") 57 | @pass_context 58 | def srv_info(ctx): 59 | """Show server info 60 | 61 | \b 62 | """ 63 | command_ctl(ctx, "core.info") 64 | 65 | 66 | @cli.command("modules", short_help="Show server loaded modules") 67 | @pass_context 68 | def srv_modules(ctx): 69 | """Show server loaded modules 70 | 71 | \b 72 | """ 73 | command_ctl(ctx, "core.modules") 74 | 75 | 76 | @cli.command("version", short_help="Show server version") 77 | @pass_context 78 | def srv_version(ctx): 79 | """Show server version 80 | 81 | \b 82 | """ 83 | command_ctl(ctx, "core.version") 84 | 85 | 86 | @cli.command("ppdefines", short_help="Show pre-processor defines") 87 | @click.option( 88 | "full", "--full", is_flag=True, help="Show full format of the records." 89 | ) 90 | @pass_context 91 | def srv_ppdefines(ctx, full): 92 | """Show pre-processor defines 93 | 94 | \b 95 | """ 96 | if full: 97 | command_ctl(ctx, "core.ppdefines_full") 98 | else: 99 | command_ctl(ctx, "core.ppdefines") 100 | 101 | 102 | @cli.command("shm", short_help="Show shared memory details") 103 | @pass_context 104 | def srv_shm(ctx): 105 | """Show shared memory details 106 | 107 | \b 108 | """ 109 | command_ctl(ctx, "core.shmmem") 110 | 111 | 112 | @cli.command("debug", short_help="Control debug level of the server") 113 | @pass_context 114 | @click.argument("level", metavar="", nargs=-1, type=int) 115 | def srv_debug(ctx, level): 116 | """Control debug level of the server 117 | 118 | \b 119 | Parameters: 120 | - new debug level (optional) 121 | """ 122 | if not level: 123 | command_ctl(ctx, "corex.debug") 124 | else: 125 | command_ctl(ctx, "corex.debug", [level[0]]) 126 | 127 | 128 | @cli.command("runinfo", short_help="Show runtime info") 129 | @pass_context 130 | def srv_runinfo(ctx): 131 | """Show runtime info 132 | 133 | \b 134 | """ 135 | command_ctl(ctx, "core.runinfo") 136 | 137 | 138 | @cli.command("rundetails", short_help="Show runtime details") 139 | @pass_context 140 | def srv_rundetails(ctx): 141 | """Show runtime details 142 | 143 | \b 144 | """ 145 | proc = subprocess.Popen( 146 | sys.argv[0] + " -F json rpc --no-log core.version", 147 | stdout=subprocess.PIPE, 148 | shell=True, 149 | ) 150 | (output, err) = proc.communicate(timeout=10) 151 | proc.wait(timeout=10) 152 | if err is None and len(output) > 32: 153 | jdata = json.loads(output) 154 | click.echo("running: " + jdata["result"]) 155 | 156 | proc = subprocess.Popen( 157 | sys.argv[0] + " -F json rpc --no-log core.uptime", 158 | stdout=subprocess.PIPE, 159 | shell=True, 160 | ) 161 | (output, err) = proc.communicate(timeout=10) 162 | proc.wait(timeout=10) 163 | if err is None and len(output) > 32: 164 | jdata = json.loads(output) 165 | uph = int(jdata["result"]["uptime"] / 3600) 166 | upm = int((jdata["result"]["uptime"] % 3600) / 60) 167 | ups = (jdata["result"]["uptime"] % 3600) % 60 168 | click.echo( 169 | "uptime: " + str(uph) + "h " + str(upm) + "m " + str(ups) + "s" 170 | ) 171 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_stats.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | 5 | 6 | @click.command("stats", short_help="Print internal statistics") 7 | @click.option( 8 | "single", 9 | "--single", 10 | "-s", 11 | is_flag=True, 12 | help="The name belong to one statistic (otherwise the name is " 13 | "for a group)", 14 | ) 15 | @click.option( 16 | "number", 17 | "--number", 18 | "-n", 19 | is_flag=True, 20 | help="The stats values are retrieved in number format", 21 | ) 22 | @click.argument("names", nargs=-1, metavar="[]") 23 | @pass_context 24 | def cli(ctx, single, number, names): 25 | """Print internal statistics 26 | 27 | \b 28 | Parameters: 29 | - [] - name of statistic or statistics group 30 | - if missing, all statistics are printed 31 | - it can be a list of names 32 | """ 33 | rcmd = "stats.fetch" 34 | if number: 35 | rcmd = "stats.fetchn" 36 | if names: 37 | for n in names: 38 | if n.endswith(":"): 39 | # enforce group name by ending with ':' 40 | command_ctl(ctx, rcmd, [n]) 41 | elif n.find(":") > 0: 42 | # get only stat name, when providing 'group:stat' 43 | command_ctl(ctx, rcmd, [n.split(":")[1]]) 44 | elif single: 45 | # single stat name flag 46 | command_ctl(ctx, rcmd, [n]) 47 | else: 48 | # default is group name 49 | command_ctl(ctx, rcmd, [n + ":"]) 50 | else: 51 | # no name, print all 52 | command_ctl(ctx, rcmd, ["all"]) 53 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_subscriber.py: -------------------------------------------------------------------------------- 1 | import click 2 | import hashlib 3 | from sqlalchemy import create_engine 4 | from kamcli.ioutils import ioutils_dbres_print 5 | from kamcli.cli import pass_context 6 | from kamcli.cli import parse_user_spec 7 | 8 | 9 | @click.group( 10 | "subscriber", 11 | help="Manage the subscribers", 12 | short_help="Manage the subscribers", 13 | ) 14 | @pass_context 15 | def cli(ctx): 16 | pass 17 | 18 | 19 | @cli.command("add", short_help="Add a new subscriber") 20 | @click.option( 21 | "dbtname", 22 | "--dbtname", 23 | "-T", 24 | default="subscriber", 25 | help='Database table name (default: "subscriber")', 26 | ) 27 | @click.option( 28 | "pwtext", 29 | "--text-password", 30 | "-t", 31 | type=click.Choice(["yes", "no"]), 32 | default="yes", 33 | help="Store password in clear text (default yes)", 34 | ) 35 | @click.argument("userid", metavar="") 36 | @click.argument("password", metavar="") 37 | @pass_context 38 | def subscriber_add(ctx, dbtname, pwtext, userid, password): 39 | """Add a new subscriber 40 | 41 | \b 42 | Parameters: 43 | - username, AoR or SIP URI for subscriber 44 | - the password 45 | """ 46 | udata = parse_user_spec(ctx, userid) 47 | ctx.vlog( 48 | "Adding subscriber [%s] in domain [%s] with password [%s]", 49 | udata["username"], 50 | udata["domain"], 51 | password, 52 | ) 53 | dig = "{}:{}:{}".format(udata["username"], udata["domain"], password) 54 | ha1 = hashlib.md5(dig.encode()).hexdigest() 55 | dig = "{}@{}:{}:{}".format( 56 | udata["username"], udata["domain"], udata["domain"], password 57 | ) 58 | ha1b = hashlib.md5(dig.encode()).hexdigest() 59 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 60 | if pwtext == "yes": 61 | e.execute( 62 | "insert into {0} (username, domain, password, ha1, ha1b) " 63 | "values ({1!r}, {2!r}, {3!r}, {4!r}, {5!r})".format( 64 | dbtname.encode("ascii", "ignore").decode(), 65 | udata["username"], 66 | udata["domain"], 67 | password.encode("ascii", "ignore").decode(), 68 | ha1, 69 | ha1b, 70 | ) 71 | ) 72 | else: 73 | e.execute( 74 | "insert into {0} (username, domain, ha1, ha1b) values " 75 | "({1!r}, {2!r}, {3!r}, {4!r})".format( 76 | dbtname.encode("ascii", "ignore").decode(), 77 | udata["username"], 78 | udata["domain"], 79 | ha1, 80 | ha1b, 81 | ) 82 | ) 83 | 84 | 85 | @cli.command("rm", short_help="Remove an existing subscriber") 86 | @click.option( 87 | "dbtname", 88 | "--dbtname", 89 | "-T", 90 | default="subscriber", 91 | help='Database table name (default: "subscriber")', 92 | ) 93 | @click.option( 94 | "yes", 95 | "--yes", 96 | "-y", 97 | is_flag=True, 98 | help="Do not ask for confirmation", 99 | ) 100 | @click.argument("userid", metavar="") 101 | @pass_context 102 | def subscriber_rm(ctx, dbtname, yes, userid): 103 | """Remove an existing subscriber 104 | 105 | \b 106 | Parameters: 107 | - username, AoR or SIP URI for subscriber 108 | """ 109 | if not yes: 110 | print("Removing user. Are you sure? (y/n):", end=" ") 111 | option = input() 112 | if option != "y": 113 | ctx.vlog("Skip removing user [%s]", userid) 114 | return 115 | udata = parse_user_spec(ctx, userid) 116 | ctx.log("Removing subscriber [%s@%s]", udata["username"], udata["domain"]) 117 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 118 | e.execute( 119 | "delete from {0} where username={1!r} and domain={2!r}".format( 120 | dbtname.encode("ascii", "ignore").decode(), 121 | udata["username"], 122 | udata["domain"], 123 | ) 124 | ) 125 | 126 | 127 | @cli.command("passwd", short_help="Update the password for a subscriber") 128 | @click.option( 129 | "dbtname", 130 | "--dbtname", 131 | "-T", 132 | default="subscriber", 133 | help='Database table name (default: "subscriber")', 134 | ) 135 | @click.option( 136 | "pwtext", 137 | "--text-password", 138 | "-t", 139 | type=click.Choice(["yes", "no"]), 140 | default="yes", 141 | help="Store password in clear text (default yes)", 142 | ) 143 | @click.argument("userid", metavar="") 144 | @click.argument("password", metavar="") 145 | @pass_context 146 | def subscriber_passwd(ctx, dbtname, pwtext, userid, password): 147 | """Update password for a subscriber 148 | 149 | \b 150 | Parameters: 151 | - username, AoR or SIP URI for subscriber 152 | - the password 153 | """ 154 | udata = parse_user_spec(ctx, userid) 155 | ctx.log( 156 | "Updating subscriber [%s@%s] with password [%s]", 157 | udata["username"], 158 | udata["domain"], 159 | password, 160 | ) 161 | dig = "{}:{}:{}".format(udata["username"], udata["domain"], password) 162 | ha1 = hashlib.md5(dig.encode()).hexdigest() 163 | dig = "{}@{}:{}:{}".format( 164 | udata["username"], udata["domain"], udata["domain"], password 165 | ) 166 | ha1b = hashlib.md5(dig.encode()).hexdigest() 167 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 168 | if pwtext == "yes": 169 | e.execute( 170 | "update {0} set password={1!r}, ha1={2!r}, ha1b={3!r} where " 171 | "username={4!r} and domain={5!r}".format( 172 | dbtname.encode("ascii", "ignore").decode(), 173 | password.encode("ascii", "ignore").decode(), 174 | ha1, 175 | ha1b, 176 | udata["username"], 177 | udata["domain"], 178 | ) 179 | ) 180 | else: 181 | e.execute( 182 | "update {0} set ha1={1!r}, ha1b={2!r} where " 183 | "username={3!r} and domain={4!r}".format( 184 | dbtname, 185 | ha1, 186 | ha1b, 187 | udata["username"], 188 | udata["domain"], 189 | ) 190 | ) 191 | 192 | 193 | @cli.command("show", short_help="Show details for subscribers") 194 | @click.option( 195 | "dbtname", 196 | "--dbtname", 197 | "-T", 198 | default="subscriber", 199 | help='Database table name (default: "subscriber")', 200 | ) 201 | @click.option( 202 | "oformat", 203 | "--output-format", 204 | "-F", 205 | type=click.Choice(["raw", "json", "table", "dict"]), 206 | default=None, 207 | help="Format the output", 208 | ) 209 | @click.option( 210 | "ostyle", 211 | "--output-style", 212 | "-S", 213 | default=None, 214 | help="Style of the output (tabulate table format)", 215 | ) 216 | @click.argument("userid", nargs=-1, metavar="[]") 217 | @pass_context 218 | def subscriber_show(ctx, dbtname, oformat, ostyle, userid): 219 | """Show details for subscribers 220 | 221 | \b 222 | Parameters: 223 | [] - username, AoR or SIP URI for subscriber 224 | - it can be a list of userids 225 | - if not provided then all subscribers are shown 226 | """ 227 | if not userid: 228 | ctx.vlog("Showing all subscribers") 229 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 230 | res = e.execute( 231 | "select * from {0}".format( 232 | dbtname.encode("ascii", "ignore").decode() 233 | ) 234 | ) 235 | ioutils_dbres_print(ctx, oformat, ostyle, res) 236 | else: 237 | for u in userid: 238 | udata = parse_user_spec(ctx, u) 239 | ctx.vlog( 240 | "Showing subscriber [%s@%s]", 241 | udata["username"], 242 | udata["domain"], 243 | ) 244 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 245 | res = e.execute( 246 | "select * from {0} where username={1!r} and domain={2!r}".format( 247 | dbtname.encode("ascii", "ignore").decode(), 248 | udata["username"], 249 | udata["domain"], 250 | ) 251 | ) 252 | ioutils_dbres_print(ctx, oformat, ostyle, res) 253 | 254 | 255 | @cli.command("setattrs", short_help="Set a string attribute for a subscriber") 256 | @click.option( 257 | "dbtname", 258 | "--dbtname", 259 | "-T", 260 | default="subscriber", 261 | help='Database table name (default: "subscriber")', 262 | ) 263 | @click.argument("userid", metavar="") 264 | @click.argument("attr", metavar="") 265 | @click.argument("val", metavar="") 266 | @pass_context 267 | def subscriber_setattrs(ctx, dbtname, userid, attr, val): 268 | """Set a string attribute a subscriber 269 | 270 | \b 271 | Parameters: 272 | - username, AoR or SIP URI for subscriber 273 | - the name of the attribute (column name in 274 | subscriber table) 275 | - the value to be set for the attribute 276 | """ 277 | udata = parse_user_spec(ctx, userid) 278 | ctx.log( 279 | "Updating subscriber [%s@%s] with str attribute [%s]=[%s]", 280 | udata["username"], 281 | udata["domain"], 282 | attr, 283 | val, 284 | ) 285 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 286 | e.execute( 287 | "update {0} set {1}={2!r} where username={3!r} and " 288 | "domain={4!r}".format( 289 | dbtname.encode("ascii", "ignore").decode(), 290 | attr.encode("ascii", "ignore").decode(), 291 | val.encode("ascii", "ignore").decode(), 292 | udata["username"], 293 | udata["domain"], 294 | ) 295 | ) 296 | 297 | 298 | @cli.command( 299 | "setattri", short_help="Set an integer attribute for a subscriber" 300 | ) 301 | @click.option( 302 | "dbtname", 303 | "--dbtname", 304 | "-T", 305 | default="subscriber", 306 | help='Database table name (default: "subscriber")', 307 | ) 308 | @click.argument("userid", metavar="") 309 | @click.argument("attr", metavar="") 310 | @click.argument("val", metavar="") 311 | @pass_context 312 | def subscriber_setattri(ctx, dbtname, userid, attr, val): 313 | """Set an integer attribute a subscriber 314 | 315 | \b 316 | Parameters: 317 | - username, AoR or SIP URI for subscriber 318 | - the name of the attribute (column name in 319 | subscriber table) 320 | - the value to be set for the attribute 321 | """ 322 | udata = parse_user_spec(ctx, userid) 323 | ctx.log( 324 | "Updating subscriber [%s@%s] with int attribute [%s]=[%s]", 325 | udata["username"], 326 | udata["domain"], 327 | attr, 328 | val, 329 | ) 330 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 331 | e.execute( 332 | "update {0} set {1}={2} where username={3!r} and " 333 | "domain={4!r}".format( 334 | dbtname.encode("ascii", "ignore").decode(), 335 | attr.encode("ascii", "ignore").decode(), 336 | val.encode("ascii", "ignore").decode(), 337 | udata["username"], 338 | udata["domain"], 339 | ) 340 | ) 341 | 342 | 343 | @cli.command( 344 | "setattrnull", short_help="Set an attribute to NULL for a subscriber" 345 | ) 346 | @click.option( 347 | "dbtname", 348 | "--dbtname", 349 | "-T", 350 | default="subscriber", 351 | help='Database table name (default: "subscriber")', 352 | ) 353 | @click.argument("userid", metavar="") 354 | @click.argument("attr", metavar="") 355 | @pass_context 356 | def subscriber_setattrnull(ctx, dbtname, userid, attr): 357 | """Set an attribute to NULL for a subscriber 358 | 359 | \b 360 | Parameters: 361 | - username, AoR or SIP URI for subscriber 362 | - the name of the attribute (column name in 363 | subscriber table) 364 | """ 365 | udata = parse_user_spec(ctx, userid) 366 | ctx.log( 367 | "Updating subscriber [%s@%s] with attribute [%s]=NULL", 368 | udata["username"], 369 | udata["domain"], 370 | attr, 371 | ) 372 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 373 | e.execute( 374 | "update {0} set {1}=NULL where username={2!r} and " 375 | "domain={3!r}".format( 376 | dbtname.encode("ascii", "ignore").decode(), 377 | attr.encode("ascii", "ignore").decode(), 378 | udata["username"], 379 | udata["domain"], 380 | ) 381 | ) 382 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_tcp.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | 5 | 6 | @click.group( 7 | "tcp", 8 | help="Manage TCP options and connections", 9 | short_help="Manage TCP options and connections", 10 | ) 11 | @pass_context 12 | def cli(ctx): 13 | pass 14 | 15 | 16 | @cli.command("options", short_help="Show details for TCP options in memory") 17 | @pass_context 18 | def tcp_options(ctx): 19 | """Show details for TCP options in memory 20 | 21 | \b 22 | """ 23 | command_ctl(ctx, "core.tcp_options", []) 24 | 25 | 26 | @cli.command("list", short_help="List current TCP connections") 27 | @pass_context 28 | def tcp_list(ctx): 29 | """List current TCP connections 30 | 31 | \b 32 | """ 33 | command_ctl(ctx, "core.tcp_list", []) 34 | 35 | 36 | @cli.command("info", short_help="Summary of TCP usage") 37 | @pass_context 38 | def tcp_info(ctx): 39 | """Summary of TCP usage 40 | 41 | \b 42 | """ 43 | command_ctl(ctx, "core.tcp_info", []) 44 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_tls.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import click 4 | from sqlalchemy import create_engine 5 | from kamcli.ioutils import ioutils_dbres_print 6 | from kamcli.cli import pass_context 7 | from kamcli.iorpc import command_ctl 8 | 9 | 10 | @click.group("tls", help="Manage tls module") 11 | @pass_context 12 | def cli(ctx): 13 | pass 14 | 15 | 16 | @cli.command("showdb", short_help="Show TLS config records in database") 17 | @click.option( 18 | "oformat", 19 | "--output-format", 20 | "-F", 21 | type=click.Choice(["raw", "json", "table", "dict"]), 22 | default=None, 23 | help="Format the output", 24 | ) 25 | @click.option( 26 | "ostyle", 27 | "--output-style", 28 | "-S", 29 | default=None, 30 | help="Style of the output (tabulate table format)", 31 | ) 32 | @pass_context 33 | def tls_showdb(ctx, oformat, ostyle): 34 | """Show details for records in tlscfg table 35 | 36 | \b 37 | """ 38 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 39 | ctx.vlog("Showing all tlscfg records") 40 | res = e.execute("select * from tlscfg") 41 | ioutils_dbres_print(ctx, oformat, ostyle, res) 42 | 43 | 44 | @cli.command( 45 | "cfgprint", short_help="Print TLS config generated from database records" 46 | ) 47 | @click.option( 48 | "odir", 49 | "--odir", 50 | "-d", 51 | default=None, 52 | help="Output directory path for certificates content", 53 | ) 54 | @click.argument("cfgpath", nargs=-1, metavar="[]", type=click.Path()) 55 | @pass_context 56 | def tls_cfgprint(ctx, odir, cfgpath): 57 | """Print TLS config generated from database records 58 | 59 | \b 60 | [] - config file path (optional) 61 | """ 62 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 63 | ctx.vlog("Generating TLS config from database records") 64 | res = e.execute("select * from tlscfg") 65 | 66 | if cfgpath: 67 | cfgpath = cfgpath[0] 68 | 69 | if not odir: 70 | if cfgpath: 71 | odir = os.path.dirname(str(cfgpath)) 72 | 73 | bstdout = sys.stdout 74 | if cfgpath: 75 | cfgsock = open(str(cfgpath), "w") 76 | sys.stdout = cfgsock 77 | 78 | pcount = 0 79 | for row in res: 80 | if pcount > 0: 81 | print("\n") 82 | 83 | if ( 84 | row["profile_type"] 85 | and row["profile_type"].strip() 86 | and row["profile_name"] 87 | and row["profile_name"].strip() 88 | ): 89 | print( 90 | "[{0:s}:{1:s}]".format( 91 | row["profile_type"], row["profile_name"] 92 | ) 93 | ) 94 | 95 | if row["method"] and row["method"].strip(): 96 | print("method={0:s}".format(row["method"])) 97 | 98 | print("verify_certificate={0:d}".format(row["verify_certificate"])) 99 | print("verify_depth={0:d}".format(row["verify_depth"])) 100 | print( 101 | "require_certificate={0:d}".format(row["require_certificate"]) 102 | ) 103 | 104 | if row["file_type"] == 0: 105 | if row["certificate"] and row["certificate"].strip(): 106 | print("certificate={0:s}".format(row["certificate"])) 107 | 108 | if row["private_key"] and row["private_key"].strip(): 109 | print("private_key={0:s}".format(row["private_key"])) 110 | 111 | if row["ca_list"] and row["ca_list"].strip(): 112 | print("ca_list={0:s}".format(row["ca_list"])) 113 | 114 | if row["crl"] and row["crl"].strip(): 115 | print("crl={0:s}".format(row["crl"])) 116 | else: 117 | if row["certificate"] and row["certificate"].strip(): 118 | fpath = os.path.join( 119 | odir, "certificate_" + str(row["id"]) + ".pem" 120 | ) 121 | fout = open(fpath, "w") 122 | fout.write(row["certificate"]) 123 | fout.close() 124 | print("certificate={0:s}".format(fpath)) 125 | 126 | if row["private_key"] and row["private_key"].strip(): 127 | fpath = os.path.join( 128 | odir, "private_key_" + str(row["id"]) + ".pem" 129 | ) 130 | fout = open(fpath, "w") 131 | fout.write(row["private_key"]) 132 | fout.close() 133 | print("private_key={0:s}".format(fpath)) 134 | 135 | if row["ca_list"] and row["ca_list"].strip(): 136 | fpath = os.path.join( 137 | odir, "ca_list_" + str(row["id"]) + ".pem" 138 | ) 139 | fout = open(fpath, "w") 140 | fout.write(row["ca_list"]) 141 | fout.close() 142 | print("ca_list={0:s}".format(fpath)) 143 | 144 | if row["crl"] and row["crl"].strip(): 145 | fpath = os.path.join( 146 | odir, "crl_" + str(row["id"]) + ".pem" 147 | ) 148 | fout = open(fpath, "w") 149 | fout.write(row["crl"]) 150 | fout.close() 151 | print("crl={0:s}".format(fpath)) 152 | 153 | if row["cipher_list"] and row["cipher_list"].strip(): 154 | print("cipher_list={0:s}".format(row["cipher_list"])) 155 | 156 | if row["server_name"] and row["server_name"].strip(): 157 | print("server_name={0:s}".format(row["server_name"])) 158 | print("server_name_mode={0:d}".format(row["server_name_mode"])) 159 | 160 | if row["server_id"] and row["server_id"].strip(): 161 | print("server_id={0:s}".format(row["server_id"])) 162 | 163 | pcount += 1 164 | 165 | if cfgpath: 166 | sys.stdout = bstdout 167 | cfgsock.close() 168 | print("done") 169 | 170 | 171 | @cli.command("cfgoptions", short_help="Show details for TLS options in memory") 172 | @pass_context 173 | def tls_cfgoptions(ctx): 174 | """Show details for TLS options in memory 175 | 176 | \b 177 | """ 178 | command_ctl(ctx, "tls.options", []) 179 | 180 | 181 | @cli.command("cfgreload", short_help="Reload tls configuration file") 182 | @pass_context 183 | def tls_cfgreload(ctx): 184 | """Reload tls configuration file 185 | 186 | \b 187 | """ 188 | command_ctl(ctx, "tls.reload", []) 189 | 190 | 191 | @cli.command("conlist", short_help="List current tls connections") 192 | @pass_context 193 | def tls_conlist(ctx): 194 | """List current tls connections 195 | 196 | \b 197 | """ 198 | command_ctl(ctx, "tls.list", []) 199 | 200 | 201 | @cli.command("info", short_help="Summary of tls usage") 202 | @pass_context 203 | def tls_info(ctx): 204 | """Summary of tls usage 205 | 206 | \b 207 | """ 208 | command_ctl(ctx, "tls.info", []) 209 | 210 | 211 | @cli.command( 212 | "sqlprint", short_help="Print SQL statement to create the db table" 213 | ) 214 | @pass_context 215 | def tls_sqlprint(ctx): 216 | """Print SQL statement to create the db table 217 | 218 | \b 219 | """ 220 | sqls = """ 221 | CREATE TABLE `tlscfg` ( 222 | `id` INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL, 223 | `profile_type` VARCHAR(64) NOT NULL, 224 | `profile_name` VARCHAR(128) NOT NULL, 225 | `method` VARCHAR(128), 226 | `verify_certificate` INT DEFAULT 0 NOT NULL, 227 | `verify_depth` INT DEFAULT 9 NOT NULL, 228 | `require_certificate` INT DEFAULT 0 NOT NULL, 229 | `cipher_list` VARCHAR(256), 230 | `server_name` VARCHAR(128), 231 | `server_name_mode` INT DEFAULT 0 NOT NULL, 232 | `server_id` VARCHAR(128), 233 | `file_type` INT DEFAULT 0 NOT NULL, 234 | `certificate` TEXT, 235 | `private_key` TEXT, 236 | `ca_list` TEXT, 237 | `crl` TEXT 238 | ); 239 | """ 240 | print(sqls) 241 | 242 | 243 | @cli.command( 244 | "gen-certs", 245 | short_help="Generate self signed certificates in current directory", 246 | ) 247 | @click.option( 248 | "domain", "--domain", "-d", default=None, help="Domain of the certificate", 249 | ) 250 | @click.option( 251 | "expiredays", 252 | "--expire-days", 253 | "-e", 254 | default=365, 255 | help="Validity of the certificate in days", 256 | ) 257 | @pass_context 258 | def tls_gen_certs(ctx, domain, expiredays): 259 | """Generate self signed certificates in current directory 260 | 261 | \b 262 | """ 263 | scmd = "" 264 | if not domain: 265 | scmd = ( 266 | "openssl req -x509 -newkey rsa:4096 -nodes -keyout kamailio-selfsigned.key -out kamailio-selfsigned.pem -days {0}" 267 | ).format(expiredays) 268 | else: 269 | scmd = ( 270 | 'openssl req -x509 -newkey rsa:4096 -nodes -subj "/CN={0}" -keyout kamailio-selfsigned.key -out kamailio-selfsigned.pem -days {1}' 271 | ).format(domain, expiredays) 272 | 273 | os.system(scmd) 274 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_trap.py: -------------------------------------------------------------------------------- 1 | import click 2 | import os 3 | import json 4 | import datetime 5 | from kamcli.cli import pass_context 6 | from kamcli.iorpc import command_ctl 7 | 8 | 9 | @click.command( 10 | "trap", 11 | help="Store runtime details and gdb full backtrace for all Kamailio processes to a file", 12 | short_help="Get runtime details and gdb full backtrace", 13 | ) 14 | @click.option( 15 | "all", 16 | "--all", 17 | "-a", 18 | is_flag=True, 19 | help="Print all details in the trap file", 20 | ) 21 | @click.option( 22 | "norpcps", 23 | "--no-rpc-ps", 24 | "-P", 25 | is_flag=True, 26 | help="Skip rpc command to get the list of processes", 27 | ) 28 | @click.option( 29 | "sysps", 30 | "--sys-ps", 31 | "-s", 32 | is_flag=True, 33 | help="Get the system ps for each PID returned by RPC ps", 34 | ) 35 | @pass_context 36 | def cli(ctx, all, norpcps, sysps): 37 | """Store runtime details and gdb full backtrace for all Kamailio processes to a file 38 | 39 | \b 40 | """ 41 | ofile = ( 42 | "/tmp/gdb_kamailio_" 43 | + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") 44 | + ".txt" 45 | ) 46 | if all: 47 | command_ctl( 48 | ctx, 49 | "core.version", 50 | [], 51 | { 52 | "func": cmd_trap_rpc_print, 53 | "params": {"ofile": ofile, "otitle": "core.version"}, 54 | }, 55 | ) 56 | command_ctl( 57 | ctx, 58 | "core.uptime", 59 | [], 60 | { 61 | "func": cmd_trap_rpc_print, 62 | "params": {"ofile": ofile, "otitle": "core.uptime"}, 63 | }, 64 | ) 65 | if not norpcps: 66 | command_ctl( 67 | ctx, 68 | "core.psx", 69 | [], 70 | { 71 | "func": cmd_trap_print, 72 | "params": {"ofile": ofile, "sysps": sysps}, 73 | }, 74 | ) 75 | 76 | 77 | # callback to write backtraces to file using the result of an rpc command 78 | def cmd_trap_print(ctx, response, params=None): 79 | ofile = None 80 | sysps = False 81 | if params is not None: 82 | if "ofile" in params: 83 | ofile = params["ofile"] 84 | if "sysps" in params: 85 | sysps = params["sysps"] 86 | if ofile is None: 87 | ofile = ( 88 | "/tmp/gdb_kamailio_" 89 | + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") 90 | + ".txt" 91 | ) 92 | ctx.printf("Trap file: " + ofile) 93 | with open(ofile, "a") as outfile: 94 | outfile.write( 95 | "---start core.psx -------------------------------------------------------\n" 96 | ) 97 | outfile.write(response.decode()) 98 | outfile.write( 99 | "\n---end core.psx -------------------------------------------------------\n\n" 100 | ) 101 | rdata = json.loads(response.decode()) 102 | if "result" in rdata: 103 | ctx.printf( 104 | "Trapping " 105 | + str(len(rdata["result"])) 106 | + " Kamailio processes with gdb. It can take a while." 107 | ) 108 | for r in rdata["result"]: 109 | ctx.printnlf(".") 110 | os.system("echo >>" + ofile) 111 | os.system( 112 | 'echo "---start ' 113 | + str(r["PID"]) 114 | + ' -----------------------------------------------------" >>' 115 | + ofile 116 | ) 117 | if sysps: 118 | os.system( 119 | "ps -o pid,ni,pri,pcpu,stat,pmem,rss,vsz,args -w -p " 120 | + str(r["PID"]) 121 | + " >>" 122 | + ofile 123 | ) 124 | os.system( 125 | "gdb kamailio " 126 | + str(r["PID"]) 127 | + ' -batch --eval-command="bt full" >>' 128 | + ofile 129 | + " 2>&1" 130 | ) 131 | os.system( 132 | 'echo "---end ' 133 | + str(r["PID"]) 134 | + ' -------------------------------------------------------" >>' 135 | + ofile 136 | ) 137 | else: 138 | os.system("echo >>" + ofile) 139 | os.system( 140 | 'echo "Unable to get the list with PIDs of running Kamailio processes" >>' 141 | + ofile 142 | ) 143 | ctx.printf("") 144 | 145 | 146 | # callback to print to file the result of an rpc command 147 | def cmd_trap_rpc_print(ctx, response, params=None): 148 | ofile = None 149 | otitle = "SECTION" 150 | if params is not None: 151 | if "ofile" in params: 152 | ofile = params["ofile"] 153 | if "otitle" in params: 154 | otitle = params["otitle"] 155 | olinestart = ( 156 | "---start " 157 | + otitle 158 | + " -------------------------------------------------------\n" 159 | ) 160 | olineend = ( 161 | "\n---end " 162 | + otitle 163 | + " -------------------------------------------------------\n\n" 164 | ) 165 | if ofile is None: 166 | ofile = ( 167 | "/tmp/gdb_kamailio_" 168 | + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") 169 | + ".txt" 170 | ) 171 | with open(ofile, "a") as outfile: 172 | outfile.write(olinestart) 173 | outfile.write(response.decode()) 174 | outfile.write(olineend) 175 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_uacreg.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.cli import pass_context 5 | from kamcli.iorpc import command_ctl 6 | 7 | 8 | @click.group( 9 | "uacreg", 10 | help="Manage uac remote registrations", 11 | short_help="Manage uac registrations", 12 | ) 13 | @pass_context 14 | def cli(ctx): 15 | pass 16 | 17 | 18 | @cli.command("add", short_help="Add a new remote registration account") 19 | @click.option("realm", "--realm", default="", help='Realm (default: "")') 20 | @click.option( 21 | "authha1", "--auth-ha1", is_flag=True, help="Auth password in HA1 format" 22 | ) 23 | @click.option( 24 | "flags", "--flags", type=int, default=0, help="Flags (default: 0)" 25 | ) 26 | @click.option( 27 | "regdelay", 28 | "--reg-delay", 29 | type=int, 30 | default=0, 31 | help="Registration delay (default: 0)", 32 | ) 33 | @click.option( 34 | "socket", "--socket", default="", help='Local socket (default: "")' 35 | ) 36 | @click.argument("l_uuid", metavar="") 37 | @click.argument("l_username", metavar="") 38 | @click.argument("l_domain", metavar="") 39 | @click.argument("r_username", metavar="") 40 | @click.argument("r_domain", metavar="") 41 | @click.argument("auth_username", metavar="") 42 | @click.argument("auth_password", metavar="") 43 | @click.argument("auth_proxy", metavar="") 44 | @click.argument("expires", metavar="", type=int) 45 | @pass_context 46 | def uacreg_add( 47 | ctx, 48 | realm, 49 | authha1, 50 | flags, 51 | regdelay, 52 | socket, 53 | l_uuid, 54 | l_username, 55 | l_domain, 56 | r_username, 57 | r_domain, 58 | auth_username, 59 | auth_password, 60 | auth_proxy, 61 | expires, 62 | ): 63 | """Add a new uac remote registration account 64 | 65 | \b 66 | Parameters: 67 | - local user unique id 68 | - local username 69 | - local domain 70 | - remote username 71 | - remote domain 72 | - auth username 73 | - auth password 74 | - auth proxy (sip address) 75 | - expires interval (int) 76 | """ 77 | ctx.vlog( 78 | "Adding a new uac remote registration account - local uuid: [%s]", 79 | l_uuid, 80 | ) 81 | pwval = "" 82 | ha1val = "" 83 | if authha1: 84 | ha1val = auth_password 85 | else: 86 | pwval = auth_password 87 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 88 | e.execute( 89 | "insert into uacreg (l_uuid, l_username, l_domain, r_username, " 90 | "r_domain, realm, auth_username, auth_password, auth_ha1, auth_proxy, " 91 | "expires, flags, reg_delay, socket) values " 92 | "({0!r}, {1!r}, {2!r}, {3!r}, " 93 | "{4!r}, {5!r}, {6!r}, {7!r}, {8!r}, {9!r}, " 94 | "{10}, {11}, {12}, {13!r})".format( 95 | l_uuid.encode("ascii", "ignore").decode(), 96 | l_username.encode("ascii", "ignore").decode(), 97 | l_domain.encode("ascii", "ignore").decode(), 98 | r_username.encode("ascii", "ignore").decode(), 99 | r_domain.encode("ascii", "ignore").decode(), 100 | realm.encode("ascii", "ignore").decode(), 101 | auth_username.encode("ascii", "ignore").decode(), 102 | pwval.encode("ascii", "ignore").decode(), 103 | ha1val.encode("ascii", "ignore").decode(), 104 | auth_proxy.encode("ascii", "ignore").decode(), 105 | expires, 106 | flags, 107 | regdelay, 108 | socket.encode("ascii", "ignore").decode(), 109 | ) 110 | ) 111 | 112 | 113 | @cli.command( 114 | "passwd", short_help="Set the password for a remote registration account" 115 | ) 116 | @click.option( 117 | "authha1", "--auth-ha1", is_flag=True, help="Auth password in HA1 format" 118 | ) 119 | @click.argument("l_uuid", metavar="") 120 | @click.argument("auth_password", metavar="") 121 | @pass_context 122 | def uacreg_passwd(ctx, realm, authha1, l_uuid, auth_password): 123 | """Set password for a remote registration account 124 | 125 | \b 126 | Parameters: 127 | - local user unique id 128 | - auth password 129 | """ 130 | ctx.vlog( 131 | "Adding a new uac remote registration account - local uuid: [%s]", 132 | l_uuid, 133 | ) 134 | pwval = "" 135 | ha1val = "" 136 | if authha1: 137 | ha1val = auth_password 138 | else: 139 | pwval = auth_password 140 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 141 | e.execute( 142 | "update uacreg set auth_password={0!r}, auth_ha1={1!r} " 143 | "where l_uuid={2!r}".format( 144 | pwval.encode("ascii", "ignore").decode(), 145 | ha1val.encode("ascii", "ignore").decode(), 146 | l_uuid.encode("ascii", "ignore").decode(), 147 | ) 148 | ) 149 | 150 | 151 | @cli.command("showdb", short_help="Show dialplan records in database") 152 | @click.option( 153 | "oformat", 154 | "--output-format", 155 | "-F", 156 | type=click.Choice(["raw", "json", "table", "dict"]), 157 | default=None, 158 | help="Format the output", 159 | ) 160 | @click.option( 161 | "ostyle", 162 | "--output-style", 163 | "-S", 164 | default=None, 165 | help="Style of the output (tabulate table format)", 166 | ) 167 | @click.argument("l_uuid", nargs=-1, metavar="[]") 168 | @pass_context 169 | def uacreg_showdb(ctx, oformat, ostyle, l_uuid): 170 | """Show details for records in uacreg database table 171 | 172 | \b 173 | Parameters: 174 | [] - local user unique id 175 | """ 176 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 177 | if not l_uuid: 178 | ctx.vlog("Showing all uacreg records") 179 | res = e.execute("select * from uacreg") 180 | ioutils_dbres_print(ctx, oformat, ostyle, res) 181 | else: 182 | for record in l_uuid: 183 | ctx.vlog("Showing uacreg records for l_uuid: " + record) 184 | res = e.execute( 185 | "select * from uacreg where l_uuid={0!r}".format(record) 186 | ) 187 | ioutils_dbres_print(ctx, oformat, ostyle, res) 188 | 189 | 190 | @cli.command( 191 | "list", short_help="Show details for remote registration records in memory" 192 | ) 193 | @pass_context 194 | def uacreg_list(ctx): 195 | """Show details for remote registration records in memory 196 | 197 | \b 198 | """ 199 | command_ctl(ctx, "uac.reg_dump", []) 200 | 201 | 202 | @cli.command( 203 | "reload", 204 | short_help="Reload remote registration records from database into memory", 205 | ) 206 | @pass_context 207 | def uacreg_reload(ctx): 208 | """Reload remote registration records from database into memory 209 | """ 210 | command_ctl(ctx, "uac.reg_reload", []) 211 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_ul.py: -------------------------------------------------------------------------------- 1 | import click 2 | from sqlalchemy import create_engine 3 | from kamcli.ioutils import ioutils_dbres_print 4 | from kamcli.ioutils import ioutils_formats_list 5 | from kamcli.cli import pass_context 6 | from kamcli.cli import parse_user_spec 7 | from kamcli.iorpc import command_ctl 8 | 9 | 10 | @click.group( 11 | "ul", 12 | help="Manage user location records", 13 | short_help="Manage user location records", 14 | ) 15 | @pass_context 16 | def cli(ctx): 17 | pass 18 | 19 | 20 | @cli.command("show", short_help="Show details for location records in memory") 21 | @click.option( 22 | "brief", "--brief", is_flag=True, help="Show brief format of the records." 23 | ) 24 | @click.option( 25 | "table", 26 | "--table", 27 | default="location", 28 | help="Name of location table (default: location)", 29 | ) 30 | @click.argument("userid", nargs=-1, metavar="[]") 31 | @pass_context 32 | def ul_show(ctx, brief, table, userid): 33 | """Show details for location records in memory 34 | 35 | \b 36 | Parameters: 37 | [] - username, AoR or SIP URI for subscriber 38 | - it can be a list of userids 39 | - if not provided then all records are shown 40 | """ 41 | if not userid: 42 | ctx.vlog("Showing all records") 43 | if brief: 44 | command_ctl(ctx, "ul.dump", ["brief"]) 45 | else: 46 | command_ctl(ctx, "ul.dump", []) 47 | else: 48 | for u in userid: 49 | udata = parse_user_spec(ctx, u) 50 | ctx.vlog( 51 | "Showing record for [%s@%s]", 52 | udata["username"], 53 | udata["domain"], 54 | ) 55 | aor = udata["username"] + "@" + udata["domain"] 56 | command_ctl(ctx, "ul.lookup", [table, aor]) 57 | 58 | 59 | @cli.command("add", short_help="Add location record") 60 | @click.option( 61 | "table", 62 | "--table", 63 | default="location", 64 | help="Name of location table (default: location)", 65 | ) 66 | @click.option( 67 | "expires", "--expires", type=int, default=0, help="Expires value" 68 | ) 69 | @click.option("qval", "--q", type=float, default=1.0, help="Q value") 70 | @click.option("cpath", "--path", default="", help="Path value") 71 | @click.option("flags", "--flags", type=int, default=0, help="Flags value") 72 | @click.option( 73 | "bflags", "--bflags", type=int, default=0, help="Branch flags value" 74 | ) 75 | @click.option( 76 | "methods", "--methods", type=int, default=4294967295, help="Methods value" 77 | ) 78 | @click.argument("userid", nargs=1, metavar="") 79 | @click.argument("curi", nargs=1, metavar="") 80 | @pass_context 81 | def ul_add( 82 | ctx, table, expires, qval, cpath, flags, bflags, methods, userid, curi 83 | ): 84 | """Add location record 85 | 86 | \b 87 | Parameters: 88 | - username, AoR or SIP URI for subscriber 89 | - contact SIP URI 90 | """ 91 | udata = parse_user_spec(ctx, userid) 92 | ctx.vlog("Adding record for [%s@%s]", udata["username"], udata["domain"]) 93 | aor = udata["username"] + "@" + udata["domain"] 94 | command_ctl( 95 | ctx, 96 | "ul.add", 97 | [table, aor, curi, expires, qval, cpath, flags, bflags, methods], 98 | ) 99 | 100 | 101 | @cli.command("rm", short_help="Delete location records") 102 | @click.option( 103 | "table", 104 | "--table", 105 | default="location", 106 | help="Name of location table (default: location)", 107 | ) 108 | @click.argument("userid", nargs=1, metavar="") 109 | @click.argument("curi", nargs=-1, metavar="[]") 110 | @pass_context 111 | def ul_rm(ctx, table, userid, curi): 112 | """Show details for location records in memory 113 | 114 | \b 115 | Parameters: 116 | - username, AoR or SIP URI for subscriber 117 | [] - contact SIP URI 118 | - optional, it can be a list of URIs 119 | """ 120 | udata = parse_user_spec(ctx, userid) 121 | ctx.vlog("Showing record for [%s@%s]", udata["username"], udata["domain"]) 122 | aor = udata["username"] + "@" + udata["domain"] 123 | if curi: 124 | for c in curi: 125 | command_ctl(ctx, "ul.rm", [table, aor, c]) 126 | else: 127 | command_ctl(ctx, "ul.rm", [table, aor]) 128 | 129 | 130 | @cli.command( 131 | "showdb", short_help="Show details for location records in database" 132 | ) 133 | @click.option( 134 | "oformat", 135 | "--output-format", 136 | "-F", 137 | type=click.Choice(ioutils_formats_list), 138 | default=None, 139 | help="Format the output", 140 | ) 141 | @click.option( 142 | "ostyle", 143 | "--output-style", 144 | "-S", 145 | default=None, 146 | help="Style of the output (tabulate table format)", 147 | ) 148 | @click.argument("userid", nargs=-1, metavar="[]") 149 | @pass_context 150 | def ul_showdb(ctx, oformat, ostyle, userid): 151 | """Show details for location records in database 152 | 153 | \b 154 | Parameters: 155 | [] - username, AoR or SIP URI for subscriber 156 | - it can be a list of userids 157 | - if not provided then all records are shown 158 | """ 159 | if not userid: 160 | ctx.vlog("Showing all records") 161 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 162 | res = e.execute("select * from location") 163 | ioutils_dbres_print(ctx, oformat, ostyle, res) 164 | else: 165 | for u in userid: 166 | udata = parse_user_spec(ctx, u) 167 | ctx.vlog( 168 | "Showing records for [%s@%s]", 169 | udata["username"], 170 | udata["domain"], 171 | ) 172 | e = create_engine(ctx.gconfig.get("db", "rwurl")) 173 | res = e.execute( 174 | "select * from location where username={0!r} and domain={1!r}".format( 175 | udata["username"], udata["domain"], 176 | ) 177 | ) 178 | ioutils_dbres_print(ctx, oformat, ostyle, res) 179 | -------------------------------------------------------------------------------- /kamcli/commands/cmd_uptime.py: -------------------------------------------------------------------------------- 1 | import click 2 | from kamcli.cli import pass_context 3 | from kamcli.iorpc import command_ctl 4 | 5 | 6 | @click.command("uptime", short_help="Print the uptime for kamailio") 7 | @pass_context 8 | def cli(ctx): 9 | """Print the uptime for kamailio 10 | 11 | \b 12 | Show time details since kamailio was started. 13 | """ 14 | command_ctl(ctx, "core.uptime", []) 15 | -------------------------------------------------------------------------------- /kamcli/dbutils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from sqlalchemy import create_engine 3 | from sqlalchemy.sql import text 4 | from sqlalchemy.exc import SQLAlchemyError 5 | 6 | 7 | KDB_IGNORE_MISSING = [ 8 | "userblacklist", 9 | "userblocklist", 10 | ] 11 | 12 | 13 | def dbutils_exec_sqlfile(ctx, sqlengine, fname): 14 | if not os.path.exists(fname): 15 | for i in KDB_IGNORE_MISSING: 16 | if i in fname: 17 | return 18 | ctx.log( 19 | "sql statements file not found [%s]", 20 | fname, 21 | ) 22 | return 23 | sql_file = open(fname, "r") 24 | sql_command = "" 25 | for line in sql_file: 26 | if not line.startswith("--") and line.strip("\n"): 27 | sql_command += line.strip("\n") 28 | if sql_command.endswith(";"): 29 | try: 30 | sqlengine.execute(text(sql_command)) 31 | except SQLAlchemyError: 32 | ctx.log( 33 | "failed to execute sql statement [%s] from file [%s]", 34 | sql_command, 35 | fname, 36 | ) 37 | finally: 38 | sql_command = "" 39 | 40 | 41 | def dbutils_exec_sqltext(ctx, sqlengine, sqltext): 42 | sql_command = "" 43 | for line in sqltext.splitlines(): 44 | tline = line.strip(" \t\r\n") 45 | if len(tline) > 0 and not tline.startswith("--"): 46 | sql_command += " " + tline 47 | if sql_command.endswith(";"): 48 | try: 49 | sqlengine.execute(text(sql_command)) 50 | except SQLAlchemyError: 51 | ctx.log( 52 | "failed to execute sql statements [%s]", 53 | sql_command, 54 | ) 55 | finally: 56 | sql_command = "" 57 | -------------------------------------------------------------------------------- /kamcli/ioutils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | 4 | # import pprint 5 | 6 | ioutils_tabulate_format = True 7 | try: 8 | from tabulate import tabulate 9 | except ImportError: 10 | ioutils_tabulate_format = False 11 | pass # module doesn't exist, deal with it. 12 | 13 | ## 14 | # enable yaml output format if the lib can be loaded 15 | ioutils_yaml_format = True 16 | try: 17 | import yaml 18 | except ImportError: 19 | ioutils_yaml_format = False 20 | pass # yaml module doesn't exist, deal with it. 21 | 22 | 23 | ioutils_formats_list = ["raw", "json", "table", "dict", "yaml"] 24 | 25 | 26 | def ioutils_dbres_print(ctx, oformat, ostyle, res): 27 | """print a database result using different formats and styles""" 28 | if oformat is None: 29 | oformat = ctx.gconfig.get("db", "outformat", fallback=None) 30 | if oformat is None: 31 | if ioutils_tabulate_format is True: 32 | oformat = "table" 33 | else: 34 | oformat = "json" 35 | 36 | if oformat == "table": 37 | if ioutils_tabulate_format is False: 38 | ctx.log("Package tabulate is not installed") 39 | sys.exit() 40 | 41 | if oformat == "yaml": 42 | if ioutils_yaml_format is False: 43 | ctx.log("Package yaml is not installed") 44 | sys.exit() 45 | 46 | if ostyle is None: 47 | ostyle = ctx.gconfig.get("db", "outstyle", fallback="grid") 48 | if ostyle is None: 49 | ostyle = "grid" 50 | 51 | if oformat == "json": 52 | jdata = [] 53 | for row in res: 54 | jdata.append(dict(row)) 55 | print(json.dumps(jdata, indent=4)) 56 | print() 57 | elif oformat == "yaml": 58 | ydata = [] 59 | for row in res: 60 | ydata.append(dict(row)) 61 | print(yaml.dump(ydata, indent=4)) 62 | print() 63 | elif oformat == "dict": 64 | for row in res: 65 | print(dict(row)) 66 | # pprint.pprint(dict(row), indent=4) 67 | print() 68 | elif oformat == "table": 69 | arows = res.fetchall() 70 | dcols = dict((k, k) for k in res.keys()) 71 | drows = [dict(r) for r in arows] 72 | gstring = tabulate(drows, headers=dcols, tablefmt=ostyle) 73 | print(gstring) 74 | else: 75 | allrows = res.fetchall() 76 | print(allrows) 77 | 78 | 79 | def ioutils_dict_print(ctx, oformat, ostyle, res): 80 | """print a dictionary using different formats and styles""" 81 | if oformat is None: 82 | oformat = "json" 83 | 84 | if oformat == "table": 85 | if ioutils_tabulate_format is False: 86 | ctx.log("Package tabulate is not installed") 87 | sys.exit() 88 | 89 | if oformat == "yaml": 90 | if ioutils_yaml_format is False: 91 | ctx.log("Package yaml is not installed") 92 | sys.exit() 93 | 94 | if ostyle is None: 95 | ostyle = "grid" 96 | 97 | if oformat == "json": 98 | jdata = [] 99 | jdata.append(res) 100 | print(json.dumps(jdata, indent=4)) 101 | print() 102 | elif oformat == "yaml": 103 | ydata = [] 104 | ydata.append(res) 105 | print(yaml.dump(ydata, indent=4)) 106 | print() 107 | elif oformat == "dict": 108 | print(res) 109 | # pprint.pprint(dict(row), indent=4) 110 | print() 111 | elif oformat == "table": 112 | gstring = tabulate([res.values()], headers=res.keys(), tablefmt=ostyle) 113 | print(gstring) 114 | else: 115 | print(res) 116 | print() 117 | -------------------------------------------------------------------------------- /kamcli/kamcli.ini: -------------------------------------------------------------------------------- 1 | ### main options 2 | [main] 3 | ; SIP domain to be used when an AoR has no domain 4 | domain=kamailio.org 5 | 6 | 7 | ### subcommand aliases 8 | [cmdaliases] 9 | # alias = subcommand 10 | # - 'kamcli alias ...' becomes equivalent of 'kamcli subcommand ...' 11 | mt = mtree 12 | pl = pipelimit 13 | sd = speeddial 14 | 15 | ### database connectivity - URLs are used for SQL Alchemy 16 | [db] 17 | ; type of database 18 | ; - for MySQL: mysql, 19 | ; - for PostgreSQL: postgresql 20 | ; - for SQLite: sqlite 21 | type=mysql 22 | ; driver to be used fro connecting 23 | ; - for MySQL: mysqldb 24 | ; - for PostgreSQL: psycopg2 25 | ; - for SQLite: pysqlite 26 | driver=mysqldb 27 | ; host of database server 28 | host=localhost 29 | ; port of database server 30 | ; - not enforced - see rwurl, rourl, adminurl 31 | ; - for MySQL: 3306 32 | ; - for PostgreSQL: 5432 33 | dbport=3306 34 | ; kamailio database name for SQL server backends 35 | dbname=kamailio 36 | ; kamailio database path for SQL file backends (e.g., sqlite) 37 | dbpath=/etc/kamailio/kamailio.db 38 | ; read/write user 39 | rwuser=kamailio 40 | ; password for read/write user 41 | rwpassword=kamailiorw 42 | ; read only user 43 | rouser=kamailioro 44 | ; password for read only user 45 | ropassword=kamailioro 46 | ; admin user 47 | adminuser=root 48 | ; password for admin user 49 | adminpassword= 50 | ; database URLs 51 | ; - built using above attributes, don't change unless you know what you do 52 | ; - full format for SQL server backends (mysql, postgres, ...): 53 | ; rwurl=%(type)s+%(driver)s://%(rwuser)s:%(rwpassword)s@%(host)s:%(dbport)s/%(dbname)s 54 | ; rourl=%(type)s+%(driver)s://%(rouser)s:%(ropassword)s@%(host)s:%(dbport)s/%(dbname)s 55 | ; adminurl=%(type)s+%(driver)s://%(adminuser)s:%(adminpassword)s@%(host)s:%(dbport)s 56 | ; - full format for SQL file backends (sqlite, ...): 57 | ; rwurl=%(type)s+%(driver)s:///%(dbpath)s 58 | ; rourl=%(type)s+%(driver)s:///%(dbpath)s 59 | ; adminurl=%(type)s+%(driver)s:///%(dbpath)s 60 | rwurl=%(type)s+%(driver)s://%(rwuser)s:%(rwpassword)s@%(host)s/%(dbname)s 61 | rourl=%(type)s+%(driver)s://%(rouser)s:%(ropassword)s@%(host)s/%(dbname)s 62 | adminurl=%(type)s+%(driver)s://%(adminuser)s:%(adminpassword)s@%(host)s 63 | 64 | ; host from where kamcli is used 65 | accesshost= 66 | 67 | ; path to the folder with SQL scripts for creating database tables 68 | ; - used by `db create` subcommand if not provided via `-s` cli argument 69 | ; - example value for mysql: /usr/local/share/kamailio/mysql 70 | ; - example value for postgresql: /usr/local/share/kamailio/postgres 71 | ; - example value for sqlite: /usr/local/share/kamailio/db_sqlite 72 | scriptsdirectory=/usr/local/share/kamailio/mysql 73 | 74 | ; outformat - the format to print database result 75 | ; - can be: table, json, yaml, dict or raw 76 | outformat=table 77 | 78 | ; outstyle - the style to print database result with tabulate package 79 | ; - default: grid 80 | # outstyle=grid 81 | 82 | 83 | ### control tool settings 84 | [ctl] 85 | ; type - can be: jsonrpc 86 | type=jsonrpc 87 | ; kamgroup - group of the running kamailio server process 88 | kamgroup=kamailio 89 | 90 | 91 | ### jsonrpc settings 92 | [jsonrpc] 93 | ; transport - can be: fifo, socket 94 | transport=socket 95 | 96 | ; path - where kamailio is listening for JSONRPC FIFO commands 97 | path=/var/run/kamailio/kamailio_rpc.fifo 98 | rplnamebase=kamailio_rpc_reply.fifo 99 | rpldir=/tmp 100 | 101 | ; srvaddr - where kamailio is listening for JSONRPC socket commands 102 | ; - it has to be a path to unix socket file, udp:ipaddr:port 103 | ; or tcp:ipaddr:port 104 | srvaddr=/var/run/kamailio/kamailio_rpc.sock 105 | ; srvaddr=udp:127.0.0.1:9062 106 | ; srvaddr=tcp:127.0.0.1:9062 107 | 108 | ; rcvaddr - where kamcli is listening for the JSONRPC responses 109 | ; - it has to be a path to unix socket file or udp:ipaddr:port 110 | ; - pid of kamcli is added at the end to allow multiple use at same time 111 | rcvaddr=/var/run/kamailio/kamailio_rpc_reply.sock 112 | ; rcvaddr=udp:127.0.0.1:9064 113 | 114 | ; outformat - the format to print RPC result 115 | ; - can be: json, yaml or raw 116 | ; - yaml is more compact output 117 | outformat=yaml 118 | 119 | 120 | ### internal cmd shell settings 121 | [shell] 122 | ; do not connect to Kamailio on start up (yes|no) 123 | # noconnect=yes 124 | 125 | ; do not fetch RPC commands on start up for auto-complete (yes|no) 126 | ; - done only if 'noconnect=no' 127 | # norpcautocomplete=yes 128 | 129 | ; do not track history of commands (yes|no) 130 | # nohistory=yes 131 | 132 | ; do not enable syntax higlighting for shell command line (yes|no) 133 | # nosyntax=yes 134 | 135 | ### command re-mapping for cmd shell 136 | # - short name for full command with parameters 137 | [shell.cmdremap] 138 | dv=db show "version" 139 | u=uptime 140 | 141 | 142 | ### apiban settings 143 | [apiban] 144 | ; key - the APIBan key 145 | # key=abcde... 146 | 147 | ; htname - htable name (if not set, defaults to 'ipban') 148 | # htname=ipban 149 | -------------------------------------------------------------------------------- /pkg/deb/bionic/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/bionic/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/bionic/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/bionic/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/bionic/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/bionic/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pkg/deb/bookworm/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/bookworm/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/bookworm/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/bookworm/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/bookworm/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/bookworm/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pkg/deb/bullseye/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/bullseye/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/bullseye/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/bullseye/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/bullseye/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/bullseye/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pkg/deb/buster/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/buster/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/buster/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/buster/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/buster/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/buster/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pkg/deb/debian/backports/bionic: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Target dist: Ubuntu bionic 4 | DIST=bionic 5 | 6 | rm -rf ${DIST} 7 | cp -r debian ${DIST} 8 | 9 | wrap-and-sort -sat -d ${DIST} 10 | 11 | # clean backports scripts 12 | rm -rf ${DIST}/backports 13 | exit 0 14 | -------------------------------------------------------------------------------- /pkg/deb/debian/backports/bookworm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Target dist: Debian bookworm 4 | DIST=bookworm 5 | 6 | rm -rf ${DIST} 7 | cp -r debian ${DIST} 8 | 9 | wrap-and-sort -sat -d ${DIST} 10 | 11 | # clean backports scripts 12 | rm -rf ${DIST}/backports 13 | exit 0 14 | -------------------------------------------------------------------------------- /pkg/deb/debian/backports/bullseye: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Target dist: Debian bullseye 4 | DIST=bullseye 5 | 6 | rm -rf ${DIST} 7 | cp -r debian ${DIST} 8 | 9 | wrap-and-sort -sat -d ${DIST} 10 | 11 | # clean backports scripts 12 | rm -rf ${DIST}/backports 13 | exit 0 14 | -------------------------------------------------------------------------------- /pkg/deb/debian/backports/buster: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Target dist: Debian buster 4 | DIST=buster 5 | 6 | rm -rf ${DIST} 7 | cp -r debian ${DIST} 8 | 9 | wrap-and-sort -sat -d ${DIST} 10 | 11 | # clean backports scripts 12 | rm -rf ${DIST}/backports 13 | exit 0 14 | -------------------------------------------------------------------------------- /pkg/deb/debian/backports/focal: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Target dist: Ubuntu focal 4 | DIST=focal 5 | 6 | rm -rf ${DIST} 7 | cp -r debian ${DIST} 8 | 9 | wrap-and-sort -sat -d ${DIST} 10 | 11 | # clean backports scripts 12 | rm -rf ${DIST}/backports 13 | exit 0 14 | -------------------------------------------------------------------------------- /pkg/deb/debian/backports/jammy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Target dist: Ubuntu jammy 4 | DIST=jammy 5 | 6 | rm -rf ${DIST} 7 | cp -r debian ${DIST} 8 | 9 | wrap-and-sort -sat -d ${DIST} 10 | 11 | # clean backports scripts 12 | rm -rf ${DIST}/backports 13 | exit 0 14 | -------------------------------------------------------------------------------- /pkg/deb/debian/backports/noble: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Target dist: Ubuntu noble 4 | DIST=noble 5 | 6 | rm -rf ${DIST} 7 | cp -r debian ${DIST} 8 | 9 | wrap-and-sort -sat -d ${DIST} 10 | 11 | # clean backports scripts 12 | rm -rf ${DIST}/backports 13 | exit 0 14 | -------------------------------------------------------------------------------- /pkg/deb/debian/backports/stretch: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Target dist: Debian stretch 4 | DIST=stretch 5 | 6 | rm -rf ${DIST} 7 | cp -r debian ${DIST} 8 | 9 | wrap-and-sort -sat -d ${DIST} 10 | 11 | # clean backports scripts 12 | rm -rf ${DIST}/backports 13 | exit 0 14 | -------------------------------------------------------------------------------- /pkg/deb/debian/backports/xenial: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Target dist: Ubuntu xenial 4 | DIST=xenial 5 | 6 | rm -rf ${DIST} 7 | cp -r debian ${DIST} 8 | 9 | echo "pyaml python-yaml" >> ${DIST}/pydist-overrides 10 | 11 | wrap-and-sort -sat -d ${DIST} 12 | 13 | # clean backports scripts 14 | rm -rf ${DIST}/backports 15 | exit 0 16 | -------------------------------------------------------------------------------- /pkg/deb/debian/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/debian/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/debian/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pkg/deb/focal/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/focal/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/focal/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/focal/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/focal/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/focal/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pkg/deb/jammy/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/jammy/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/jammy/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/jammy/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/jammy/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/jammy/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pkg/deb/noble/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/noble/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/noble/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/noble/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/noble/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/noble/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pkg/deb/stretch/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/stretch/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/stretch/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/stretch/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/stretch/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/stretch/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pkg/deb/xenial/changelog: -------------------------------------------------------------------------------- 1 | kamcli (3.0.0~dev0) unstable; urgency=medium 2 | 3 | * devel version 4 | 5 | -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 6 | 7 | kamcli (2.0.0) unstable; urgency=medium 8 | 9 | * Official release 10 | 11 | -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 12 | 13 | kamcli (1.1.0) unstable; urgency=medium 14 | 15 | * Official release 16 | 17 | -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 18 | 19 | kamcli (1.1.0~dev0) unstable; urgency=medium 20 | 21 | * Initial release (Closes: #910283) 22 | 23 | -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 24 | -------------------------------------------------------------------------------- /pkg/deb/xenial/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /pkg/deb/xenial/control: -------------------------------------------------------------------------------- 1 | Source: kamcli 2 | Maintainer: Victor Seva 3 | Section: misc 4 | Priority: optional 5 | X-Python3-Version: >= 3.2 6 | Standards-Version: 4.2.1.1 7 | Build-Depends: 8 | debhelper (>= 9~), 9 | dh-python, 10 | python3-all, 11 | python3-setuptools, 12 | 13 | Package: kamcli 14 | Architecture: all 15 | Depends: 16 | ${misc:Depends}, 17 | ${python3:Depends}, 18 | Description: Kamailio Command Line Interface Control Tool 19 | kamcli is aiming at being a modern and extensible alternative to the shell 20 | script kamctl. 21 | . 22 | It requires that jsonrpcs module of Kamailio is loaded and configured to 23 | listen on an Unix domain socket or FIFO file. The way to interact with 24 | Kamailio has to be set inside kamcli config file (kamcli.ini). 25 | -------------------------------------------------------------------------------- /pkg/deb/xenial/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: kam-cli 3 | Upstream-Contact: Daniel-Constantin Mierla 4 | Source: https://github.com/kamailio/kamcli 5 | 6 | Files: * 7 | Copyright: 2015-2019 Daniel-Constantin Mierla 8 | License: GPL-2 9 | 10 | Files: pkg/deb/* 11 | Copyright: 2018-2019 Victor Seva 12 | License: GPL-2 13 | 14 | License: GPL-2 15 | This program is free software; you can redistribute it and/or modify 16 | it under the terms of the GNU General Public License version 2 as 17 | published by the Free Software Foundation. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this package; if not, write to the Free 27 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, 28 | Boston, MA 02110-1301 USA 29 | X-Comment: On Debian systems, the complete text of the GNU Library General 30 | Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. 31 | -------------------------------------------------------------------------------- /pkg/deb/xenial/kamcli.examples: -------------------------------------------------------------------------------- 1 | kamcli/kamcli.ini 2 | -------------------------------------------------------------------------------- /pkg/deb/xenial/pydist-overrides: -------------------------------------------------------------------------------- 1 | pyaml python-yaml 2 | -------------------------------------------------------------------------------- /pkg/deb/xenial/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | include /usr/share/dpkg/pkg-info.mk 3 | 4 | %: 5 | dh "$@" --with python3 --buildsystem=pybuild 6 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | target-version = ['py37'] 3 | line-length = 79 4 | include = '\.pyi?$' 5 | exclude = ''' 6 | /( 7 | \.git 8 | | \.tox 9 | )/ 10 | ''' 11 | 12 | [tool.tox] 13 | legacy_tox_ini = ''' 14 | [tox] 15 | envlist = style 16 | 17 | [testenv:style] 18 | deps = 19 | pre-commit 20 | commands = pre-commit run --all-files 21 | ''' 22 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | Click~=7.0 2 | prompt-toolkit>=3.0.2 3 | pyaml>=19.4.1 4 | PyYAML>=5.1.2 5 | Pygments>=2.6.0 6 | SQLAlchemy<2.0.0 7 | tabulate>=0.8.5 8 | wheel>=0.35.0 9 | -------------------------------------------------------------------------------- /requirements/requirements.txt: -------------------------------------------------------------------------------- 1 | -r ./base.txt 2 | -------------------------------------------------------------------------------- /requirements/requirements_dev.txt: -------------------------------------------------------------------------------- 1 | -r ./base.txt 2 | 3 | pre-commit 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="kamcli", 5 | version="3.0.0", 6 | packages=["kamcli", "kamcli.commands"], 7 | include_package_data=True, 8 | install_requires=[ 9 | "setuptools", 10 | "click", 11 | "prompt-toolkit", 12 | "pyaml", 13 | "pygments", 14 | "sqlalchemy", 15 | "tabulate", 16 | "wheel", 17 | ], 18 | entry_points=""" 19 | [console_scripts] 20 | kamcli=kamcli.cli:cli 21 | """, 22 | ) 23 | --------------------------------------------------------------------------------