pax_global_header00006660000000000000000000000064151464100140014506gustar00rootroot0000000000000052 comment=206b45bdca5ca750e5f292c3c9addf7749d81db7 kamcli-3.0.0/000077500000000000000000000000001514641001400127465ustar00rootroot00000000000000kamcli-3.0.0/.flake8000066400000000000000000000001751514641001400141240ustar00rootroot00000000000000[flake8] ignore = E203, E231, E266, E501, W503, F403, F401 max-line-length = 79 max-complexity = 18 select = B,C,E,F,W,T4,B9 kamcli-3.0.0/.github/000077500000000000000000000000001514641001400143065ustar00rootroot00000000000000kamcli-3.0.0/.github/dependabot.yml000066400000000000000000000002601514641001400171340ustar00rootroot00000000000000--- version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" commit-message: prefix: "github: [skip ci]" kamcli-3.0.0/.github/workflows/000077500000000000000000000000001514641001400163435ustar00rootroot00000000000000kamcli-3.0.0/.github/workflows/github-packages.yml000066400000000000000000000033111514641001400221220ustar00rootroot00000000000000--- name: github-packages on: push: branches: - master tags: - "v*.*.*" # Allows you to run this workflow manually from the Actions tab workflow_dispatch: env: REGISTRY_NAME: ghcr.io IMAGE_NAME: kamcli REGISTRY_IMAGE: kamailio/kamcli jobs: docker: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY_NAME }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Docker meta id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY_NAME }}/${{ env.REGISTRY_IMAGE }} tags: | type=schedule type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha labels: | org.opencontainers.image.title=${{ env.IMAGE_NAME }} org.opencontainers.image.description=image with all required dependences env: DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index - name: Build and push uses: docker/build-push-action@v5 with: platforms: linux/amd64,linux/arm64 labels: ${{ steps.meta.outputs.labels }} push: true tags: ${{ steps.meta.outputs.tags }} annotations: ${{ steps.meta.outputs.annotations }} kamcli-3.0.0/.gitignore000066400000000000000000000014561514641001400147440ustar00rootroot00000000000000# Editor tmp files *~ *.swp *.swo # Python specific tmp files # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ .env/ venv/ .venv/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # VSCode .vscode kamcli-3.0.0/.pre-commit-config.yaml000066400000000000000000000003021514641001400172220ustar00rootroot00000000000000repos: - repo: https://github.com/psf/black rev: 23.7.0 hooks: - id: black - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 hooks: - id: flake8 kamcli-3.0.0/ChangeLog000066400000000000000000000341101514641001400145170ustar00rootroot00000000000000Release: Kamcli v3.0.0 (Feb 21, 2026) New features: * cmd_acc: added command to reset missed calls table structure * cmd_acc: added command to reset acc table structure * cmd_pstrap: open subprocess output in text mode * cmd_acc: added report for most active source IP addresses * cmd_acc: rename variable used for query field * ioutils: array for table values of ioutils_dict_print() * cmd_acc: new command method-stats * ioutils: helper function to print a dictionary with different formats * cmd_acc: added report top-odst * cmd_acc: added report for top dst * cmd_acc: check if interval is greater than 0 * cmd_acc: added time interval cli option for acc report * cmd_acc: adde command to print acc reports * cmd_acc: added command to list missed calls records * cli.py: replace deprecated SafeConfigParser() with ConfigParser() * iorpc.py: replace deprecated isAlive() with is_alive() * cmd_acc: use short_help for command description * cmd_acc: provide rate_group parameter for rates generate command * cmd_acc: explicit sql transaction begin for cdrs and rates generation * cmd_acc: set default value for table names cli parameters * cmd_db: proper user create for localhost for remote db * cmd_acc: proper name for rates generate function * cmd_acc: use connection to commit call of stored procedures * cmd_acc: explicit commit after store procedure call to go around implicit sqlachemy rollback * cmd_acc: fix use of % inside store procedure create statement * requirements/base: simplify sqlalchemy version condition * requirements: SQLAlchemy version has to be < 2.0.0 * kamcli.ini: sample tcp socket for jsonrpc * iorpc: initial support for tcp transport * cmd_pipelimit: update help message for list command * cmd_pipelimit: proper help message for set pipe command * cmd_pipelimit: command to reset the pipe * cmd_apiban: log message with options to set the key * cmd_pstrap: option to print ps line for each pid * cmd_trap: option to print system ps for each pid from rpc ps result * cmd_trap: renaemd option --no-ps to --no-rpc-ps * cmd_trap: option to skip rpc command to get the list of processes * cmd_jsonrpc: use shorter alias command in help message * cmd_jsonrpc: cli option to set store path for rpc commands * iorpc: option to set store path for udp and fifo rpc commands * cmd_srv: reimplemented cmd runinfo to execute rpc core.runinfo * cmd_srv: renamed runinfo to rundetails * cmd_srv: added runinfo command * cmd_htable: added commands for rpc setxs and setxi * kamcli: aliased acl to command group * cmd_speedial: added cli options to set fname and lname * kamcli.ini: added mt command alias to mtree * cmd_db: format the code with black * cmd_acc: no limit condition for cdrs list * cmd_acc: list prints all acc records if --limit=0 * cmd_acc: formatted code * cmd_acc: added rates-generate command * cmd_acc: added rates-proc-create command * cmd_acc: renamed rating-table-create to rates-table-create * cmd_acc: added rates-rm command * cmd_acc: added rates-add command * cmd_acc: added cdrs-list command * cmd_acc: added cdrs-generate command * cmd_db: added create-table-like command * cmd_db: updates to grant and revoke commands * cmd_shell: display sip server uptime on start * cmd_htable: added command to set expire * kamcli.ini: sample config and docs for shell options * cmd_shell: get nohistory and nosyntax also from config * kamcli.ini: renamed section to shell.cmdremap * cmd_shell: updates to get the entries for shell.cmdremap * kamcli.ini: config section for shell command * cmd_shell: check the options for no connect and no rpc autocomplete in config * cmd_shell: connect on starting to running kamailio and display its version * cmd_shell: auto-complete options for srv rpchelp * cmd_apiban: try also to get apiban.org key from env variable $APIBANKEY * cmd_shell: info message if it cannot fetch the list of rpc commands * cmd_apiban: added option to set expire for loaded item in htable * cmd_shell: check length of output fetching rpc commands * cmd_sell: set timeout for popen() operations to fetch rpc commands * cmd_shell: option to siable rpc command auto complete * cmd_shell: load the list of rpc commands at init for completion * cmd_jsonrpc: option for not printing log message * cmd_apiban: added check subcommand * kamcli.ini: section for apiban command * cmd_apiban: added command for apiban records management * tls: added subcommand to generate self signed certs using openssl * cmd_acc: added list command * cmd_acc: create cdr_id column for acc and cdrs tables * requirements: set requirement to Click~=7.0 * ioutils: convert rows to dict for tabulate output of db result * cmd_subscriber: added cli parameter to specify database table name * cmd_subscriber: use string format to build sql queries * ioutils: add fallback options for getting config outformat and outstyle * cmd_db: list userblocklist module * dbutils: cope with sql files of renamed modules * cmd_srv: added debug subcommand * ioutils: read outstyle db config option * kamcli.ini: config option outstyle for db section * cmd_sipreq: skip adding Contact header if --curi=none * cmd_sipreq: added option to set additional headers * kamcli.ini: added outformat attribute to db section * ioutils: support yaml output for db results * cli: overwrite [jsonrpc/outformat] config value with -F parameter * cmd_sipreq: send SIP request via RPC command * cmd_ping: fix rpc command for nowait option * cmd_ping: --nowait cli option to skip waiting for sip response * cmd_ping: added option to set from uri * kamcli: added ping subcommand * cmd_htable: option to ask confirmation for remove commands * cmd_subscriber: ask confirmation when removing user * cmd_mtree: ask confirmation when removing prefix * cmd_pipelimit: ask confirmation when removing a pipe * cmd_pipelimit: added db-rm command * mtree: db-show with many prefixes * cmd_pipelimit: set type to int for limit argument in set pipe command * cmd_pipelimit: added db add and show commands * kamcli.ini: aliased pl to pipelimit * cmd_pipelimit: added commands for rpc stats and set pipe * cmd_pipelimit: added command for pipelimit management * cmd_db: fix invalid syntax error and some typos * cmd_db: get the scriptsdirectory cfg attribute if not provided as argument * cmd_db: use scripts directory name instead of sql directory * kamcli.ini: scriptsdirectory - new option to set path to db tables create scripts * cmd_db: use psql tool to create database * cmd_db: create subcommand for postgresql, additional options * cmd_db: option to create all tables without interactive confirmation * cmd_db: create functionality for sqlite * cmd_db: option to confirm dropping database * cmd_db: drop for sqlite * cmd_db: no-grant option for create command * cmd_db: postgres support for create-dbonly and drop * cmd_db: use rwurl for version subcommands * cmd_db: show create for postgres and sqlite * cmd_db: slite support for cli subcommands * kamcli.ini: added comments with sqlite options * cmd_db: remove use of CMD_BASE for mysql cli commands * cmd_db: postgresql support for clirun, clishow and clishowg * kamcli: restore default database type to mysql * cmd_db: connect subcommand for postgresql * cmd_db: move CMD_BASE per db dtriver handling * kamcli.ini: showing postgres settings * cmd_shell: create ~/.kamcli folder if not exists and history is enabled * cmd_shm: added subcommand for shm related rpc commands * cmd_pike: removed unused import * cmd_pkg: added subcommand for pkg related rpc commands * Fix small typo on cmd_srv.py comments * README.md: details about the order of how configuration files are loaded * cli: reorder loading paths for config files * cmd_shell: internal command ':c' to print command remapping associations * kamcli.ini: new section [cmd_shell.cmdremap] * cmd_shell: support for commands remapping * cmd_uacreg: added group short help * cmd_tcp: added group short help * cmd_ul: added group short help * cmd_config: print subcommand aliases as an ordered json document * cli: read config at startup and update command aliases * kamcli.ini: section for command aliases * cmd_subscriber: added group short help * cmd_shell: option -S to disable command line syntax highlighting * added pygments to requirements * cmd_shell: bash syntax highlighting for command line using pygments * cmd_shell: added auto suggest from history * cmd_speeddial: added group short help * cmd_shv: added group short help * added -h as option to print help message * cmd_db: added query subcommand * cmd_rtpengine: added group short help * cmd_avp: added db rm and show subcommands * cmd_avp: new command to mange avp records in usr preferences * cmd_trap: reorganize help messages * cmd_pstrap: reorganized help messages * cmd_pike: added group short help * cmd_mtree: added group short help * cmd_jsonrpc: small adjustment to the help text * cmd_htable: added group short help * cmd_group: added group short help * cmd_config: added group short help * cmd_alias: added group short help * cmd_address: added group short help * cmd_domain: added group short help * cmd_dlgs: added group short help * cmd_dialplan: added group short help * cmd_dialog: added group short help * cmd_dispatcher: added group short help * cmd_acc: added group short help * cmd_db: added group short help * cmd_shell: make the prompt bold style * cmd_shell: option to run without saving commands history * cmd_config: option to install config in user directory * added prompt-toolkit to setup requirements * cmd_shell: interactive shell subcommand * cmd_htable: option to set the type for value in db-add * iorpc: allow write to temporary response unixsocket file * cmd_mtree: renamed commands for db operations * cmd_htable: decode database table name value * cmd_htable: added db-show subcommand * cmd_htable: dbtname is now option * cmd_htable: added db-add and db-rm subcommands * iorpc: decode() on read from file * iorpc: encode() on fifo command string * cmd_pike: subcommand to manage the pike module * cmd_dlgs: added briefieng and getall subcommands * cmd_dlgs: update function name to match the command * cmd_dlgs: added dlgs subcommand * cmd_htable: added store subcommand * commands: added pstrap command * cmd_tap: decode() on rpc command response * cmd_trap: option to print more runtime details to output file * cmd_trap: use bytes decode() for string conversion * cmd_trap: convert json reponse to string for writing to file * cmd_trap: print output of core.psx in the trap file * cmd_trap: output file name computed in the main function * cmd_config: command to install config file to /etc/kamcli/kamcli.ini * cmd_htable: command to print htable stats * cmd_htable: command to list the hash tables * ioutils: handle context output format * kamcli: added output format (-F) generic cli option * cmd_htable: added reload subcommand * kamcli: aliased sht to htable command * kamcli: cli - look for kamcli.ini in current directory and ./kamcli/ subfolder * cmd_htable: command to remove an item from hash table * cmd_htable: added flush command to remove all the content from an hash table * cmd_htable: command to manage htable items * cmd_srv: subcommand to manage shared value variables - $shv(name) * cmd_address: fixed mode option name for list command * cmd_stats: option to fetch stats with numeric values * cmd_stats: replaced stats.get_statistics with stats.fetch * iorpc: decode response from the server * iorpc - encode string to be sent on inet sockets * cmd_db: fix version table management * cmd_db: added command to get version number for a table structure * cmd_db: added command to set version number for specific table * cmd_db: added command to revoke access to database * cmd_db: added database drop command * cmd_db: add users in create full database command * cmd_db: use short_help attribute when defining command * cmd_db: added command to create a specific extension group of tables * cmd_db: added commands to create specific group of database tables * cmd_db: added command to create users and grant privileges * kamcli.ini: added db accesshost option * cmd_db: added subcommand to create database only * cmd_db: added subcommand to create database structure * kamcli.ini: add db admin access attributes * cmd_acc: added command to create rating table * cmd_acc: use sql execute to create tables and procedures * cmd_acc: add subcommand to create cdrs stored procedure * cmd_acc: added subcommand to create the cdrs table * cmd_acc: separate commands to update acc or missed_calls tables structures * cmd_acc: command for accounting management * dbutils.py - added file to collect common functions for db operations * cmd_db: run pre-commit hooks for black formatting * cmd_db: added subcommand to update acc and missed calls table structure * cmd_trap: added the gdb trap command * added ctx function to print without new line * cmd_srv: subscommand to print rpc method help * cmd_uacreg: proper function name to match command scope * added srv shm subcommand * requirements strcuture update (#10) * iorpc: remove variables not used * ioutils: remove variable not used * cmd_db: add missing import * cmd_dialplan: fix dialplan_translate cmd * cmd_srv: match function names with commands * cmd_srv: subcommand to print pre-processor defines * cmd_dispatcher: aded subcommands for add/remove in memory records * cmd_dialog: added match subcommand * cmd_dialog: fix terminate function name * add kamgroup config to set group ownership of the file created at path Release: Kamcli v2.0.0 (Sep 30, 2019) New features: * support for Python 3.x * run sql statements from a file * print command aliases * cmd uacreg: management of uacreg records * cmd srv: server info * cmd rtpengine: management of rtpengine module * cmd tcp: management of tcp connections Release: Kamcli v1.1.0 (Oct 16, 2018) For more detailed list of changes, see the git log. kamcli-3.0.0/Dockerfile000066400000000000000000000003311514641001400147350ustar00rootroot00000000000000FROM python:3-bookworm LABEL org.opencontainers.image.authors="Victor Seva " WORKDIR /project VOLUME /etc/kamcli COPY . . RUN pip3 install . RUN pip3 install psycopg2 mysqlclient kamcli-3.0.0/LICENSE000066400000000000000000000431001514641001400137510ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Moe Ghoul, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. kamcli-3.0.0/README.md000066400000000000000000000356241514641001400142370ustar00rootroot00000000000000## KAMCLI ## Kamailio Command Line Interface Control Tool. Kamailio is an open source SIP (RFC3261) server available at: * https://www.kamailio.org **kamcli** is aiming at being a modern and extensible alternative to the shell script **kamctl**. It requires that `jsonrpcs` module of Kamailio is loaded and configured to listen on a Unix domain socket, UDP socket or FIFO file. The way to interact with Kamailio has to be set inside `kamcli` config file (kamcli.ini). **Table Of Content** * [Features](#features) * [Installation](#installation) + [Requirements](#requirements) + [MySQL Backend](#mysql-backend) + [PostgreSQL Backend](#postgresql-backend) + [SQLite Backend](#sqlite-backend) + [Install In Virtual Environment](#install-in-virtual-environment) + [Install On Debian](#install-on-debian) + [Debian And Ubuntu Packages](#debian-and-ubuntu-packages) + [Install On CentOS](#install-on-centos) * [Configuration File](#configuration-file) * [Usage](#usage) * [Interactive Shell Mode](#interactive-shell-mode) * [Examples Of Commands](#examples-of-commands) * [Kamailio Configuration](#kamailio-configuration) * [Database Backend Support](#database-backend-support) * [Development](#development) * [Python2 Support](#python2-support) * [License](#license) ### Features #### The tool provides sub-commands for managing many Kamailio modules and their database records. The can be run in one shot from terminal or via the embedded interactive shell (which can be started with `kamcli shell` - see the corresponding section). The prototype of using **kamcli** is: ``` kamcli [options] command [arguments] ``` New commands for **kamcli** can be implemented as plugins, each command being implemented in a file located in **kamcli/commands/**. Among implemented commands: * **acc** - manage accounting records * **address** - manage permissions address records * **aliasdb** - manage database aliases * **apiban** - interact with apiban.org and push to htable for IP blocking * **avp** - manage avp user preferences * **config** - manage configuration file for kamcli * **db** - manage kamailio database content * **dialog** - manage active calls (dialog) * **dialplan** - manage dialplan records * **dispatcher** - manage load balancer (dispatcher) * **dlgs** - manage dlgs module * **domain** - manage domain records * **group** - manage group membership records (acl) * **htable** - manage htable module * **moni** - continuous refresh of the values for a list of statistics * **mtree** - manage memory trees (mtree) * **pike** - manage pike module * **ping** - send OPTIONS request * **pipelimit** - manage pipelimit module * **pkg** - private memory (pkg) management * **ps** - print the details for kamailio running processes * **pstrap** - store runtime details and gdb backtraces for running processes with ps * **rpc** - interact with kamailio via jsonrpc control commands (alias of jsonrpc) * **rpcmethods** - return the list of available RPC methods (commands) * **rtpengine** - manage RTPEngine records and instances * **shell** - run in interactive shell mode * **shm** - shared memory (shm) management * **shv** - manage $shv(name) variables * **sipreq** - send SIP request via RPC command * **speeddial** - manage speed dial records * **srv** - server management commands (sockets, aliases, ...) * **stats** - get kamailio internal statistics * **subscriber** - manage SIP subscribers * **tcp** - management commands for TCP connections * **tls** - management commands for TLS profiles and connections * **trap** - store runtime details and gdb backtraces for running processes * **uacreg** - manage uac remote registration records * **ul** - manage user location records * **uptime** - print the uptime for kamailio instance Each **kamcli command** can offer many subcommands. The help for each command can be seen with: ``` kamcli command --help ``` The help for each subcommand can be seen with: ``` kamcli command subcommand --help ``` ### Installation #### #### Requirements #### OS Packages (install via apt, yum, ...): * python3 (python version 3.x, recommended at least Python 3.7) * python3-pip * python3-setuptools * python3-dev (optional - needed to install mysqlclient via pip) * python3-venv (optional - needed to install virtual environment) PIP Packages (run inside `kamcli` folder): ``` $ pip3 install -r requirements/requirements.txt ``` #### MySQL Backend #### Extra PIP Packages (install via pip3): * _extra packages requied by kamcli (part of OS or virtual environment)_ * mysqlclient (optional - needed if you want to connect to MySQL database) ``` $ pip3 install mysqlclient ``` Notes: * it may require to install `libmysqlclient-dev` to for getting Python `mysqlclient` extension * `mysql` cli tool is used by some `db` subcommands #### PostgreSQL Backend #### * _extra packages requied by kamcli (part of OS or virtual environment)_ * psycopg2 (optional - needed if you want to connect to PostgreSQL database) ``` $ pip3 install psycopg2 ``` ``` # on some OSes it may require to install `psycopg2-binary` $ pip3 install psycopg2-binary ``` Notes: * it may require to install `libpq-dev` to for getting Python `psycopg2` extension * `psql` cli tool is used by some `db` subcommands #### SQLite Backend #### No extra Python package needs to be installed, it is included in the core language extensions. Notes: * it may require to install `sqlite3` tool if wanted to execute `kamcli db ...` cli specific subcommands. #### Install In Virtual Environment #### It is recommended to install in a virtual environment at least for development. Some useful details about installing Click in virtual environment are available at: * http://click.pocoo.org/4/quickstart/#virtualenv For example, create the virtual environment in the folder venv: ``` $ apt install python3-venv $ mkdir kamclienv $ cd kamclienv $ python3 -m venv venv ``` To activate the virtual environment: ``` $ . venv/bin/activate ``` Clone `kamcli` and install it. The commands can be done inside the virtual environment if activate to be available only there or without virtual environment to be installed in the system. ``` $ git clone https://github.com/kamailio/kamcli.git $ cd kamcli $ pip3 install -r requirements/requirements.txt $ pip3 install mysqlclient $ pip3 install --editable . ``` The above commands install the MySQL driver. The command `pip3 install mysqlclient` can be skipped if no need for database connectivity, or install the PostgreSQL driver, if that type of database is used. The *pip3 install* command installs the dependencies appart of the database connector module needed on top of sqlalchemy, therefore the database driver has to be installed explicitely with `pip3`. To deactivate the virtual environment, run: ``` $ deactivate ``` #### Install On Debian #### Should work also on: Ubuntu or Mint Note: you may have to replace python with python3 and pip with pip3 in package names, installation and execution commands. To get `kamcli` completely installed on Debian with MySQL support, run following commands: ``` apt-get install python3 python3-pip python3-setuptools python3-dev cd /usr/local/src/ git clone https://github.com/kamailio/kamcli.git cd kamcli pip3 install -r requirements/requirements.txt pip3 install mysqlclient pip3 install . ``` To see if all was installed properly, run: ``` kamcli --help ``` **Tip**: install configuration file in the user home directory: ``` cd /usr/local/src/kamcli sudo kamcli config install -u ``` Edit `/root/.kamcli/kamcli.ini` or `/home/$USER/.kamcli/kamcli.ini` and update the config values as needed. #### Debian And Ubuntu Packages #### Packages for various Debian and Ubuntu distributions are made available in the APT repository at: * https://deb.kamailio.org/kamcli-nightly/ They are nightly builds from git development branch. #### Install On CentOS #### Should work also on other RPM-based distros. Note: you may have to replace python with python3 and pip with pip3 in package names, installation and execution commands. To get `kamcli` installed on CentOS with MySQL support, run following commands: ``` yum install python3 python3-devel python3-pip python3-setuptools cd /usr/local/src/ git clone https://github.com/kamailio/kamcli.git cd kamcli pip3 install -r requirements/requirements.txt pip3 install mysqlclient pip3 install . ``` To see if all was installed properly, run: ``` kamcli --help ``` **Tip**: install configuration file in the user home directory: ``` cd /usr/local/src/kamcli sudo kamcli config install -u ``` Edit `/root/.kamcli/kamcli.ini` or `/home/$USER/.kamcli/kamcli.ini` and update the config values as needed. ### Using Docker ### Attatch you config dir to ``/etc/kamcli`` volumen ``` docker run -i -t --rm -v $(realpath ~/.kamcli):/etc/kamcli ghcr.io/kamailio/kamcli:master kamcli ``` ### Configuration File ### Kamcli uses a configuration file with INI format. The name is `kamcli.ini` and it looks for it in: * `./kamcli/kamcli.ini` * `./kamcli.ini` * `/etc/kamcli/kamcli.ini` * `~/.kamcli/kamcli.ini` * the value of `-c` or `--config` command line parameter All the configuration files that are found are loaded in the order listed above. To load only one configuration file, use `-n` parameter together with `-c /path/to/kamcli.ini`. A sample kamailio.ini is available in sources, at `kamcli/kamcli.ini`. The installation process does not automatically deploy the configuration file. To install the global configuration file, run the next command in the source directory: ``` kamcli config install ``` It installs the sample configuration file in `/etc/kamcli/kamcli.ini`. To install user specific configuration file, run the next command in the source directory: ``` kamcli config install -u ``` It installs the sample configuration file in `~/.kamcli/kamcli.ini`. Note: not all configuration file options in `kamcli.ini` are used at this moment, some values are hardcoded, being planned to be replaced with the configuration options. ### Usage ### Read the help messages: ``` $ kamcli --help $ kamcli --help $ kamcli --help ``` ### Interactive Shell Mode ### `kamcli` can offer an internal interactive shell when running: ``` kamcli shell ``` To exit the internal shell type `:q`, or run `:h` to see the internal shell help. The internal shell keeps persistent history of commands (stored in `~/.kamcli/history`) and has auto-completion of `kamcli` options and sub-commands as one starts typing. Using `` pops up the auto-completion suggestions. The cli prompt line has syntax highlighting and keyboard bindings similar to `bash`: * `CTRL + A` - go to start of line * `CTRL + E` - go to end of line * `CTRL + D` - exit the shell session * `CTRL + L` - clear the screen * `CTRL + U` - clear the line before the cursor * `CTRL + K` - clear the line after the cursor * `CTRL + R` - search through previously used commands * `CTRL + W` - delete the word before the cursor Long commands can be remapped to shorter names via configuration file `[cmd_shell.cmdremap]` section, like in the next example: ``` [cmd_shell.cmdremap] dv=db show "version" ``` Executing `dv` inside the interactive shell prints the result of command `db show "version"`. ### Examples Of Commands ### Sample commands to understand quicker the capabilities and how to use it: ``` kamcli -d --help kamcli -d --config=kamcli/kamcli.ini --help kamcli subscriber show kamcli subscriber add test test00 kamcli subscriber show test kamcli subscriber show --help kamcli -d subscriber passwd test01 test10 kamcli -d subscriber add -t no test02 test02 kamcli -d subscriber setattrs test01 rpid +123 kamcli -d subscriber setattrnull test01 rpid kamcli -d jsonrpc --help kamcli -d jsonrpc core.psx kamcli -d jsonrpc system.listMethods kamcli -d jsonrpc stats.get_statistics kamcli -d jsonrpc stats.get_statistics all kamcli -d jsonrpc stats.get_statistics shmem: kamcli -d jsonrpc --dry-run system.listMethods kamcli -d config raw kamcli -d config show main db kamcli -d -no-default-configs config show main db kamcli -d db connect kamcli -d db show -F table version kamcli -d db show -F json subscriber kamcli -d db showcreate version kamcli -d db showcreate -F table version kamcli -d db showcreate -F table -S html version kamcli -d db clirun "describe version" kamcli -d db clishow version kamcli -d db clishowg subscriber kamcli -d ul showdb kamcli -d ul show kamcli -d ul rm test kamcli -d ul add test sip:test@127.0.0.1 kamcli -d stats kamcli -d stats usrloc kamcli -d stats -s registered_users kamcli -d stats usrloc:registered_users ``` ### Kamailio Configuration ### It requires to load the `jsonrpcs` module in `kamalilio.cfg` and enable the FIFO or UnixSocket transports (they should be enabled by default). ``` loadmodule "jsonrpcs.so" # ----- jsonrpcs params ----- # - explicit enable of fifo and unixsocket transports modparam("jsonrpcs", "transport", 6) # - pretty format for output modparam("jsonrpcs", "pretty_format", 1) ``` ### Database Backend Support ### When using `Kamailio` with a database backend and want `kamcli` to manage it, then update configuration file `kamcli.ini` and set the attributes in the section `[db]`. Couple of these attributes (e.g., database name) can be also provided as cli parameters for some `kamcli db ...` sub-commands. Note: of course, it requires to install the Python extension to connect to the type of database to be used, as well as the cli tools for the database type, see the sections related to installation for specific details. One of the most important steps when using `Kamailio` with database backend is the creation of the database and its tables. That can be done with: ``` kamcli db create ``` By default, when applicable, this command creates database access users and grants the appropriate privileges. This behaviour can be controlled via cli parameters. To create the database tables, `kamcli` needs to know where creation scripts are located. For example, when using MySQL for a Kamailio instance installed from sources, the path is: * /usr/local/share/kamailio/mysql This has to be set to `scriptsdirectory` attribute in `[db]` section of `kamcli.ini`, or given via `-s` (or `--scripts-directory`) command line parameter. It can also point to the corresponding folder in the source code tree, for example: ``` kamcli db create -s /usr/local/src/kamailio-dev/utils/kamctl/mysql ``` ### Development ### Notes about setting up development environment and guidelines to add new commands are inside the [docs/Devel.md](docs/Devel.md) file. ### Python2 Support ### The current version of `kamcli` works only with Python3. Python2 has been deprecated and removed from major Linux distributions. To run `kamcli` with Python2.x, use the git branch `v1.2-python2`. The branch will be kept for a while, but likely there will be no new features added to it. ### License ### GPLv2 Copyright: asipto.com kamcli-3.0.0/docs/000077500000000000000000000000001514641001400136765ustar00rootroot00000000000000kamcli-3.0.0/docs/Devel.md000066400000000000000000000045631514641001400152670ustar00rootroot00000000000000## KAMCLI Development ## Kamailio Command Line Interface Control Tool ### Development Environment ### Use 4 spaces for indentation. #### [pre-commit](https://pre-commit.com/) #### It is highly recommended to set the `pre-commit` git hook to get code identation and error detections using `black` and `flake8` tools before changes are committed. On a Debian/Ubuntu system, do: ``` apt install build-essentials python3-dev python3-virtualenvwrapper mkvirtualenv kamcli --python=python3 pip install -r requirements/requirements_dev.txt pre-commit install ``` ### Development Guidelines ### #### Used Frameworks #### Kamcli is using the following Python frameworks: * click - command line interface framework * http://click.pocoo.org * SQL Alchemy - connection to database * http://www.sqlalchemy.org * pyaml - yaml package used for compact printing of jsonrpc responses * tabulate - pretty printing of database results #### Plugins #### Kamcli prototype is: ``` kamcli [params] ``` Each command is implemented as a plugin, its code residing in a single Python file located in *kamcli/commands/*. The filename is prefixed by **cmd_**, followed by command name and then the extension **.py**. Development of kamcli has its starting point in the *complex* example of Click: * https://github.com/mitsuhiko/click/tree/master/examples/complex Other examples provided by Click are good source of inspiration: * https://github.com/mitsuhiko/click/tree/master/examples #### Adding A New Command #### In short, the steps for adding a new command (refered also as plugin or module): * create a new file file for your new comand in **kamcli/commands/** folder * name the file **cmd_newcommand.py** * define **cli(...)** function, which can be a command or group of commands Once implemented, the new command should be immediately available as: ``` kamcli newcommand ... ``` The commands **dispatcher** (kamcli/commands/cmd_dispatcher.py) or **address** (kamcli/commands/cmd_address.py) can be a good reference to look at and reuse for implementing new commands. If the new command is executing MI or JSONRPC commands to kamailio, add the appropriate mapping inside the **kamcli/iorpc.py** file to the variable **COMMAND_NAMES**. The recommendation is to use the RPC command as the common name and then map the MI variant - MI is obsoleted and scheduled to be removed. kamcli-3.0.0/kamcli/000077500000000000000000000000001514641001400142065ustar00rootroot00000000000000kamcli-3.0.0/kamcli/__init__.py000066400000000000000000000000001514641001400163050ustar00rootroot00000000000000kamcli-3.0.0/kamcli/cli.py000066400000000000000000000135071514641001400153350ustar00rootroot00000000000000import os import sys import click try: import ConfigParser as configparser except ImportError: import configparser kamcli_formats_list = ["raw", "json", "table", "dict", "yaml"] COMMAND_ALIASES = { "acl": "group", "rpc": "jsonrpc", "subs": "subscriber", "sht": "htable", } def read_global_config(config_paths): """Get config.""" parser = configparser.ConfigParser() if config_paths: parser.read(config_paths) else: parser.read(["kamcli.ini"]) return parser # try: # self.optmain.update(parser.items('main')) # except configparser.NoSectionError: # pass def parse_user_spec(ctx, ustr): """Get details of the user from ustr (username, aor or sip uri)""" udata = {} if ":" in ustr: uaor = ustr.split(":")[1] else: uaor = ustr if "@" in uaor: udata["username"] = uaor.split("@")[0] udata["domain"] = uaor.split("@")[1] else: udata["username"] = uaor.split("@")[0] try: udata["domain"] = ctx.gconfig.get("main", "domain") except configparser.NoOptionError: ctx.log("Default domain not set in config file") sys.exit() if udata["username"] is None: ctx.log("Failed to get username") sys.exit() if udata["domain"] is None: ctx.log("Failed to get domain") sys.exit() udata["username"] = udata["username"].encode("ascii", "ignore").decode() udata["domain"] = udata["domain"].encode("ascii", "ignore").decode() return udata CONTEXT_SETTINGS = dict( auto_envvar_prefix="KAMCLI", help_option_names=["-h", "--help"] ) class Context(object): def __init__(self): self.debug = False self.wdir = os.getcwd() self.gconfig_paths = [] self._gconfig = None def log(self, msg, *args): """Logs a message to stderr.""" if args: msg %= args click.echo("(log): " + msg, file=sys.stderr) def vlog(self, msg, *args): """Logs a message to stderr only if verbose is enabled.""" if self.debug: if args: msg %= args click.echo("(dbg): " + msg, file=sys.stderr) def printf(self, msg, *args): """Print a formated message to stdout.""" if args: msg %= args click.echo(msg) def printnlf(self, msg, *args): """Print a formated message to stdout without new line.""" if args: msg %= args click.echo(msg, nl=False) def read_config(self): if self._gconfig is None: self._gconfig = read_global_config(self.gconfig_paths) if "cmdaliases" in self._gconfig: COMMAND_ALIASES.update(self._gconfig["cmdaliases"]) @property def gconfig(self): if self._gconfig is None: self._gconfig = read_global_config(self.gconfig_paths) return self._gconfig pass_context = click.make_pass_decorator(Context, ensure=True) cmd_folder = os.path.abspath( os.path.join(os.path.dirname(__file__), "commands") ) class KamCLI(click.MultiCommand): def list_commands(self, ctx): rv = [] for filename in os.listdir(cmd_folder): if filename.endswith(".py") and filename.startswith("cmd_"): rv.append(filename[4:-3]) rv.sort() return rv def get_command(self, ctx, name): if name in COMMAND_ALIASES: name = COMMAND_ALIASES[name] try: if sys.version_info[0] == 2: name = name.encode("ascii", "replace") mod = __import__( "kamcli.commands.cmd_" + name, None, None, ["cli"] ) except ImportError: return return mod.cli @click.command( cls=KamCLI, context_settings=CONTEXT_SETTINGS, short_help="Kamailio command line interface control tool", ) @click.option( "-d", "--debug", "debug", is_flag=True, help="Enable debug mode." ) @click.option( "-c", "--config", "config", default=None, help="Configuration file." ) @click.option( "-w", "--wdir", "wdir", type=click.Path(exists=True, file_okay=False, resolve_path=True), help="Working directory.", ) @click.option( "-n", "--no-default-configs", "nodefaultconfigs", is_flag=True, help="Skip loading default configuration files.", ) @click.option( "-F", "--output-format", "oformat", type=click.Choice(kamcli_formats_list), default=None, help="Format the output (overwriting db/jsonrpc outformat config attributes)", ) @click.version_option() @pass_context def cli(ctx, debug, config, wdir, nodefaultconfigs, oformat): """Kamailio command line interface control tool. \b Help per command: kamcli --help \b Default configuration files: - /etc/kamcli/kamcli.ini - ./kamcli.ini - ./kamcli/kamcli.ini - ~/.kamcli/kamctli.ini Configs loading order: default configs, then --config option \b License: GPLv2 Copyright: asipto.com """ ctx.debug = debug if wdir is not None: ctx.wdir = wdir if not nodefaultconfigs: if os.path.isfile("./kamcli/kamcli.ini"): ctx.gconfig_paths.append("./kamcli/kamcli.ini") if os.path.isfile("./kamcli.ini"): ctx.gconfig_paths.append("./kamcli.ini") if os.path.isfile("/etc/kamcli/kamcli.ini"): ctx.gconfig_paths.append("/etc/kamcli/kamcli.ini") tpath = os.path.expanduser("~/.kamcli/kamcli.ini") if os.path.isfile(tpath): ctx.gconfig_paths.append(tpath) if config is not None: ctx.gconfig_paths.append(os.path.expanduser(config)) ctx.read_config() if oformat is not None: ctx.gconfig.set("db", "outformat", oformat) ctx.gconfig.set("jsonrpc", "outformat", oformat) kamcli-3.0.0/kamcli/commands/000077500000000000000000000000001514641001400160075ustar00rootroot00000000000000kamcli-3.0.0/kamcli/commands/__init__.py000066400000000000000000000000001514641001400201060ustar00rootroot00000000000000kamcli-3.0.0/kamcli/commands/cmd_acc.py000066400000000000000000000516631514641001400177450ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.ioutils import ioutils_dict_print from sqlalchemy.sql import text from sqlalchemy.exc import SQLAlchemyError from kamcli.cli import pass_context from kamcli.dbutils import dbutils_exec_sqltext @click.group( "acc", help="Accounting management", short_help="Accounting management" ) @pass_context def cli(ctx): pass def acc_acc_struct_update_exec(ctx, e): sqltext = """ ALTER TABLE acc ADD COLUMN src_user VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN src_domain VARCHAR(128) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN src_ip VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN dst_ouser VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN dst_user VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN dst_domain VARCHAR(128) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN cdr_id INTEGER NOT NULL DEFAULT 0; """ dbutils_exec_sqltext(ctx, e, sqltext) @cli.command( "acc-struct-update", short_help="Run SQL statements to update acc table structure", ) @pass_context def acc_acc_struct_update(ctx): """Run SQL statements to update acc table structure""" ctx.vlog("Run statements to update acc table structure") e = create_engine(ctx.gconfig.get("db", "rwurl")) acc_acc_struct_update_exec(ctx, e) def acc_acc_struct_reset_exec(ctx, e): sqltext = """ ALTER TABLE acc DROP COLUMN src_user; ALTER TABLE acc DROP COLUMN src_domain; ALTER TABLE acc DROP COLUMN src_ip; ALTER TABLE acc DROP COLUMN dst_ouser; ALTER TABLE acc DROP COLUMN dst_user; ALTER TABLE acc DROP COLUMN dst_domain; ALTER TABLE acc DROP COLUMN cdr_id; """ dbutils_exec_sqltext(ctx, e, sqltext) @cli.command( "acc-struct-reset", short_help="Run SQL statements to reset acc table structure", ) @pass_context def acc_acc_struct_reset(ctx): """Run SQL statements to reset acc table structure""" ctx.vlog("Run statements to reset acc table structure") e = create_engine(ctx.gconfig.get("db", "rwurl")) acc_acc_struct_reset_exec(ctx, e) def acc_mc_struct_update_exec(ctx, e): sqltext = """ ALTER TABLE missed_calls ADD COLUMN src_user VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN src_domain VARCHAR(128) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN src_ip VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN dst_ouser VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN dst_user VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN dst_domain VARCHAR(128) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN cdr_id INTEGER NOT NULL DEFAULT 0; """ dbutils_exec_sqltext(ctx, e, sqltext) @cli.command( "mc-struct-update", short_help="Run SQL statements to update missed_calls table structure", ) @pass_context def acc_mc_struct_update(ctx): """Run SQL statements to update missed_calls table structure""" ctx.vlog("Run statements to update missed_calls table structure") e = create_engine(ctx.gconfig.get("db", "rwurl")) acc_mc_struct_update_exec(ctx, e) def acc_mc_struct_reset_exec(ctx, e): sqltext = """ ALTER TABLE missed_calls DROP COLUMN src_user; ALTER TABLE missed_calls DROP COLUMN src_domain; ALTER TABLE missed_calls DROP COLUMN src_ip; ALTER TABLE missed_calls DROP COLUMN dst_ouser; ALTER TABLE missed_calls DROP COLUMN dst_user; ALTER TABLE missed_calls DROP COLUMN dst_domain; ALTER TABLE missed_calls DROP COLUMN cdr_id; """ dbutils_exec_sqltext(ctx, e, sqltext) @cli.command( "mc-struct-reset", short_help="Run SQL statements to reset missed_calls table structure", ) @pass_context def acc_mc_struct_reset(ctx): """Run SQL statements to reset missed_calls table structure""" ctx.vlog("Run statements to reset missed_calls table structure") e = create_engine(ctx.gconfig.get("db", "rwurl")) acc_mc_struct_reset_exec(ctx, e) @cli.command( "tables-struct-update", short_help="Run SQL statements to update acc and missed_calls tables structures", ) @pass_context def acc_tables_struct_update(ctx): """Run SQL statements to update acc and missed_calls tables structures""" ctx.vlog("Run statements to update acc and missed_calls tables structures") e = create_engine(ctx.gconfig.get("db", "rwurl")) acc_acc_struct_update_exec(ctx, e) acc_mc_struct_update_exec(ctx, e) @cli.command( "cdrs-table-create", short_help="Run SQL statements to create cdrs table structure", ) @pass_context def acc_cdrs_table_create(ctx): """Run SQL statements to create cdrs table structure""" ctx.vlog("Run SQL statements to create cdrs table structure") e = create_engine(ctx.gconfig.get("db", "rwurl")) sqltext = """ CREATE TABLE `cdrs` ( `cdr_id` bigint(20) NOT NULL auto_increment, `src_username` varchar(64) NOT NULL default '', `src_domain` varchar(128) NOT NULL default '', `dst_username` varchar(64) NOT NULL default '', `dst_domain` varchar(128) NOT NULL default '', `dst_ousername` varchar(64) NOT NULL default '', `call_start_time` datetime NOT NULL default '2000-01-01 00:00:00', `duration` int(10) unsigned NOT NULL default '0', `sip_call_id` varchar(128) NOT NULL default '', `sip_from_tag` varchar(128) NOT NULL default '', `sip_to_tag` varchar(128) NOT NULL default '', `src_ip` varchar(64) NOT NULL default '', `cost` integer NOT NULL default '0', `rated` integer NOT NULL default '0', `created` datetime NOT NULL, PRIMARY KEY (`cdr_id`), UNIQUE KEY `uk_cft` (`sip_call_id`,`sip_from_tag`,`sip_to_tag`) ); """ e.execute(sqltext) @cli.command( "cdrs-proc-create", short_help="Run SQL statements to create the stored procedure to generate cdrs", ) @pass_context def acc_cdrs_proc_create(ctx): """Run SQL statements to create the stored procedure to generate cdrs""" ctx.vlog( "Run SQL statements to create the stored procedure to generate cdrs" ) e = create_engine(ctx.gconfig.get("db", "rwurl")) sqltext = """ CREATE PROCEDURE `kamailio_cdrs`() BEGIN DECLARE done INT DEFAULT 0; DECLARE bye_record INT DEFAULT 0; DECLARE v_src_user,v_src_domain,v_dst_user,v_dst_domain,v_dst_ouser,v_callid, v_from_tag,v_to_tag,v_src_ip VARCHAR(64); DECLARE v_inv_time, v_bye_time DATETIME; DECLARE inv_cursor CURSOR FOR SELECT src_user, src_domain, dst_user, dst_domain, dst_ouser, time, callid,from_tag, to_tag, src_ip FROM acc where method='INVITE' and cdr_id='0'; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN inv_cursor; REPEAT FETCH inv_cursor INTO v_src_user, v_src_domain, v_dst_user, v_dst_domain, v_dst_ouser, v_inv_time, v_callid, v_from_tag, v_to_tag, v_src_ip; IF NOT done THEN SET bye_record = 0; SELECT 1, time INTO bye_record, v_bye_time FROM acc WHERE method='BYE' AND callid=v_callid AND ((from_tag=v_from_tag AND to_tag=v_to_tag) OR (from_tag=v_to_tag AND to_tag=v_from_tag)) ORDER BY time ASC LIMIT 1; IF bye_record = 1 THEN INSERT INTO cdrs (src_username,src_domain,dst_username, dst_domain,dst_ousername,call_start_time,duration,sip_call_id, sip_from_tag,sip_to_tag,src_ip,created) VALUES (v_src_user, v_src_domain,v_dst_user,v_dst_domain,v_dst_ouser,v_inv_time, UNIX_TIMESTAMP(v_bye_time)-UNIX_TIMESTAMP(v_inv_time), v_callid,v_from_tag,v_to_tag,v_src_ip,NOW()); UPDATE acc SET cdr_id=last_insert_id() WHERE callid=v_callid AND from_tag=v_from_tag AND to_tag=v_to_tag; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END """ e.execute(sqltext) @cli.command( "rates-table-create", short_help="Run SQL statements to create billing_rates table structure", ) @pass_context def acc_rates_table_create(ctx): """Run SQL statements to create billing_rates table structure""" ctx.vlog("Run SQL statements to create billing_rates table structure") e = create_engine(ctx.gconfig.get("db", "rwurl")) sqltext = """ CREATE TABLE `billing_rates` ( `rate_id` bigint(20) NOT NULL auto_increment, `rate_group` varchar(64) NOT NULL default 'default', `prefix` varchar(64) NOT NULL default '', `rate_unit` integer NOT NULL default '0', `time_unit` integer NOT NULL default '60', PRIMARY KEY (`rate_id`), UNIQUE KEY `uk_rp` (`rate_group`,`prefix`) ); """ e.execute(sqltext) @cli.command( "list", short_help="List accounting records", ) @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "limit", "--limit", "-l", type=int, default=20, help="The limit of listed records (default: 20)", ) @pass_context def acc_list(ctx, oformat, ostyle, limit): """List accounting records \b """ e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog("Showing accounting records") query = "" if limit == 0: query = "select * from acc order by id desc" else: query = "select * from acc order by id desc limit {0}".format(limit) res = e.execute(query) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command( "mc-list", short_help="List missed calls records", ) @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "limit", "--limit", "-l", type=int, default=20, help="The limit of listed records (default: 20)", ) @pass_context def acc_mc_list(ctx, oformat, ostyle, limit): """List missed calls records \b """ e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog("Showing missed calls records") query = "" if limit == 0: query = "select * from missed_calls order by id desc" else: query = "select * from missed_calls order by id desc limit {0}".format( limit ) res = e.execute(query) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command( "cdrs-generate", short_help="Run SQL stored procedure to generate CDRS", ) @pass_context def acc_cdrs_generate(ctx): """Run SQL stored procedure to generate CDRS""" ctx.vlog("Run SQL stored procedure to generate CDRS") e = create_engine(ctx.gconfig.get("db", "rwurl")) with e.connect() as c: t = c.begin() c.execute("call kamailio_cdrs()") t.commit() @cli.command( "cdrs-list", short_help="List call data records", ) @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "limit", "--limit", "-l", type=int, default=20, help="The limit of listed records (default: 20)", ) @pass_context def acc_cdrs_list(ctx, oformat, ostyle, limit): """List call data records \b """ e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog("Showing call data records") query = "" if limit <= 0: query = "select * from cdrs order by cdr_id desc" else: query = "select * from cdrs order by cdr_id desc limit {0}".format( limit ) res = e.execute(query) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("rates-add", short_help="Add a new rating record to database") @click.option( "dbtname", "--dbtname", default="billing_rates", help='The name of the database table (default: "billing_rates")', ) @click.argument("rate_group", metavar="") @click.argument("prefix", metavar="") @click.argument("rate_unit", metavar="") @click.argument("time_unit", metavar="") @pass_context def acc_rates_add(ctx, dbtname, rate_group, prefix, rate_unit, time_unit): """Add a new rating record in database table \b Parameters: - name of rating group - matching prefix - rate unit - time unit """ ctx.vlog( "Adding to db table [%s] record [%s] => [%s]", dbtname, rate_group, prefix, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) v_dbtname = dbtname.encode("ascii", "ignore").decode() v_rate_group = rate_group.encode("ascii", "ignore").decode() v_prefix = prefix.encode("ascii", "ignore").decode() e.execute( "insert into {0} (rate_group, prefix, rate_unit, time_unit) values " "({1!r}, {2!r}, {3}, {4})".format( v_dbtname, v_rate_group, v_prefix, rate_unit, time_unit ) ) @cli.command("rates-rm", short_help="Remove a rating record from database") @click.option( "dbtname", "--dbtname", default="billing_rates", help='The name of the database table (default: "billing_rates")', ) @click.argument("rate_group", metavar="") @click.argument("prefix", metavar="") @pass_context def acc_rates_rm(ctx, dbtname, rate_group, prefix): """Remove a rating record from database \b Parameters: - name of rating group - matching prefix """ ctx.vlog( "Remove from db table [%s] record [%s] => [%s]", dbtname, rate_group, prefix, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) v_dbtname = dbtname.encode("ascii", "ignore").decode() v_rate_group = rate_group.encode("ascii", "ignore").decode() v_prefix = prefix.encode("ascii", "ignore").decode() e.execute( "delete from {0} where rate_group=({1!r} and prefix={2!r}".format( v_dbtname, v_rate_group, v_prefix, ) ) @cli.command( "rates-proc-create", short_help="Run SQL statements to create the stored procedure to rate cdrs", ) @pass_context def acc_rates_proc_create(ctx): """Run SQL statements to create the stored procedure to rate cdrs""" ctx.vlog("Run SQL statements to create the stored procedure to rate cdrs") e = create_engine(ctx.gconfig.get("db", "rwurl")) sqltext = """ CREATE PROCEDURE `kamailio_rating`(`rgroup` varchar(64)) BEGIN DECLARE done, rate_record, vx_cost INT DEFAULT 0; DECLARE v_cdr_id BIGINT DEFAULT 0; DECLARE v_duration, v_rate_unit, v_time_unit INT DEFAULT 0; DECLARE v_dst_username VARCHAR(64); DECLARE cdrs_cursor CURSOR FOR SELECT cdr_id, dst_username, duration FROM cdrs WHERE rated=0; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN cdrs_cursor; REPEAT FETCH cdrs_cursor INTO v_cdr_id, v_dst_username, v_duration; IF NOT done THEN SET rate_record = 0; SELECT 1, rate_unit, time_unit INTO rate_record, v_rate_unit, v_time_unit FROM billing_rates WHERE rate_group=rgroup AND v_dst_username LIKE concat(prefix, '%%') ORDER BY prefix DESC LIMIT 1; IF rate_record = 1 THEN SET vx_cost = v_rate_unit * CEIL(v_duration/v_time_unit); UPDATE cdrs SET rated=1, cost=vx_cost WHERE cdr_id=v_cdr_id; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END """ e.execute(sqltext) @cli.command( "rates-generate", short_help="Run SQL stored procedure to rate the CDRS and generate the costs", ) @click.argument("rate_group", nargs=-1, metavar="[]") @pass_context def acc_rates_generate(ctx, rate_group): """Run SQL stored procedure to rate the CDRS and generate the costs \b Parameters: - name of rating group """ ctx.vlog( "Run SQL stored procedure to rate the CDRS and generate the costs" ) e = create_engine(ctx.gconfig.get("db", "rwurl")) with e.connect() as c: t = c.begin() if not rate_group: c.execute('call kamailio_rating("default")') else: for rg in rate_group: c.execute("call kamailio_rating({0!r})".format(rg)) t.commit() @cli.command( "report", short_help="Show various accounting reports", ) @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "limit", "--limit", "-l", type=int, default=20, help="The limit of listed records (default: 20)", ) @click.option( "interval", "--interval", "-i", type=int, default=24, help="The time interval in hours (default: 24)", ) @click.argument("name", metavar="") @pass_context def acc_report(ctx, oformat, ostyle, limit, interval, name): """Show various accounting reports \b Parameters: - name of the report: - top-src: most active callers - top-dst: most active callees - top-odst: most active original callees - top-srcip: most active source IP addresses """ e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog("Showing accounting report: " + name) qfield = "src_user" if name == "top-dst": qfield = "dst_user" elif name == "top-odst": qfield = "dst_ouser" elif name == "top-srcip": qfield = "src_ip" query = "SELECT `" + qfield + "`, count(*) AS `count` FROM acc" if interval > 0: query = ( query + " WHERE DATE_SUB(NOW(), INTERVAL {0} HOUR) <= time".format( interval ) ) query = query + " GROUP BY `" + qfield + "` ORDER BY count DESC" if limit > 0: query = query + " LIMIT {0}".format(limit) res = e.execute(query) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command( "method-stats", short_help="Show method statistics", ) @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "limit", "--limit", "-l", type=int, default=20, help="The limit of listed records (default: 20)", ) @click.option( "interval", "--interval", "-i", type=int, default=24, help="The time interval in hours (default: 24)", ) @pass_context def acc_method_stats(ctx, oformat, ostyle, limit, interval): """Show method statistics \b """ e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog("Showing method statistics") query = "SELECT method, sip_code, time, UNIX_TIMESTAMP(time) as tstamp FROM acc" if interval > 0: query = ( query + " WHERE DATE_SUB(NOW(), INTERVAL {0} HOUR) <= time".format( interval ) ) if limit > 0: query = query + " LIMIT {0}".format(limit) res = e.execute(query) acc_records = {} acc_records["invite"] = 0 acc_records["bye"] = 0 acc_records["message"] = 0 acc_records["other"] = 0 acc_records["invite200"] = 0 acc_records["invite404"] = 0 acc_records["invite487"] = 0 acc_records["inviteXYZ"] = 0 for row in res: if row["method"] == "INVITE": acc_records["invite"] += 1 if row["sip_code"] == "200": acc_records["invite200"] += 1 elif row["sip_code"] == "404": acc_records["invite404"] += 1 elif row["sip_code"] == "487": acc_records["invite487"] += 1 else: acc_records["inviteXYZ"] += 1 elif row["method"] == "BYE": acc_records["bye"] += 1 elif row["method"] == "MESSAGE": acc_records["message"] += 1 else: acc_records["other"] += 1 ioutils_dict_print(ctx, oformat, ostyle, acc_records) kamcli-3.0.0/kamcli/commands/cmd_address.py000066400000000000000000000115551514641001400206400ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "address", help="Manage permissions address records", short_help="Manage permissions address records", ) @pass_context def cli(ctx): pass @cli.command("add", short_help="Add a new record to address table") @click.option( "mask", "--mask", type=int, default=32, help="Mask value (default 32)" ) @click.option( "port", "--port", type=int, default=0, help="Port value (default 0)" ) @click.option("tag", "--tag", default="", help='Tag value (default: "")') @click.argument("group", metavar="", type=int) @click.argument("address", metavar="
") @pass_context def address_add(ctx, mask, port, tag, group, address): """Add a new record to address db table \b Parameters: - group id
- IP address """ ctx.vlog("Adding to group id [%d] address [%s]", group, address) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "insert into address (grp, ip_addr, mask, port, tag) values " "({0}, {1!r}, {2}, {3}, {4!r})".format( group, address.encode("ascii", "ignore").decode(), mask, port, tag.encode("ascii", "ignore").decode(), ) ) @cli.command("rm", short_help="Remove a record from address db table") @click.option("mask", "--mask", type=int, help="Mask value") @click.option("port", "--port", type=int, help="Port value") @click.argument("group", metavar="", type=int) @click.argument("address", metavar="
") @pass_context def address_rm(ctx, mask, port, group, address): """Remove a record from address db table \b Parameters: - group id
- IP address """ e = create_engine(ctx.gconfig.get("db", "rwurl")) addr = address.encode("ascii", "ignore").decode() if not mask: if not port: e.execute( "delete from address where grp={0} and ip_addr={1!r}".format( group, addr ) ) else: e.execute( "delete from address where grp={0} and ip_addr={1!r} " "and port={2}".format(group, addr, port) ) else: if not port: e.execute( "delete from address where grp={0} and " "ip_addr={1!r} and mask={2}".format(group, addr, mask) ) else: e.execute( "delete from address where setid={0} and destination={1!r} " "and mask={2} and port={3}".format(group, addr, mask, port) ) @cli.command("showdb", short_help="Show address records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("group", nargs=-1, metavar="[]", type=int) @pass_context def address_showdb(ctx, oformat, ostyle, group): """Show details for records in address db table \b Parameters: - address group """ e = create_engine(ctx.gconfig.get("db", "rwurl")) if not group: ctx.vlog("Showing all address records") res = e.execute("select * from address") else: ctx.vlog("Showing address records for group") res = e.execute("select * from address where group={0}".format(group)) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("list", short_help="Show details for address records in memory") @click.option( "mode", "--mode", default="all", help="What to be printed (all, addresses, subnets, domains)", ) @click.argument("group", nargs=-1, metavar="[]", type=int) @pass_context def address_list(ctx, mode, group): """Show details for address records in memory \b Parameters: - address group """ if mode == "all": command_ctl(ctx, "permissions.addressDump", []) command_ctl(ctx, "permissions.subnetDump", []) command_ctl(ctx, "permissions.domainDump", []) elif mode == "addresses": command_ctl(ctx, "permissions.addressDump", []) elif mode == "subnets": command_ctl(ctx, "permissions.subnetDump", []) elif mode == "domains": command_ctl(ctx, "permissions.domainDump", []) else: command_ctl(ctx, "permissions.addressDump", []) @cli.command( "reload", short_help="Reload address records from database into memory" ) @pass_context def address_reload(ctx): """Reload address records from database into memory """ command_ctl(ctx, "permissions.addressReload", []) kamcli-3.0.0/kamcli/commands/cmd_aliasdb.py000066400000000000000000000130321514641001400206020ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.cli import parse_user_spec @click.group( "aliasdb", help="Manage database user aliases", short_help="Manage database user aliases", ) @pass_context def cli(ctx): pass @cli.command("add", short_help="Add a user-alias pair") @click.option( "table", "--table", default="dbaliases", help="Name of database table (default: dbaliases)", ) @click.argument("userid", metavar="") @click.argument("aliasid", metavar="") @pass_context def aliasdb_add(ctx, table, userid, aliasid): """Add a user-alias pair into database \b Parameters: - username, AoR or SIP URI for subscriber - username, AoR or SIP URI for alias """ udata = parse_user_spec(ctx, userid) adata = parse_user_spec(ctx, aliasid) ctx.vlog( "Adding user [%s@%s] with alias [%s@%s]", udata["username"], udata["domain"], adata["username"], adata["domain"], ) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "insert into {0} (username, domain, alias_username, " "alias_domain) values ({1!r}, {2!r}, {3!r}, {4!r})".format( table, udata["username"], udata["domain"], adata["username"], adata["domain"], ) ) @cli.command("rm", short_help="Remove records for a user and/or alias") @click.option( "table", "--table", default="dbaliases", help="Name of database table (default: dbaliases)", ) @click.option( "matchalias", "--match-alias", is_flag=True, help="Match userid value as alias (when given one argument)", ) @click.argument("userid", metavar="") @click.argument("aliasid", metavar="", nargs=-1) @pass_context def aliasdb_rm(ctx, table, matchalias, userid, aliasid): """Remove a user from groups (revoke privilege) \b Parameters: - username, AoR or SIP URI for subscriber - username, AoR or SIP URI for alias (optional) """ udata = parse_user_spec(ctx, userid) ctx.log( "Removing alias for record [%s@%s]", udata["username"], udata["domain"] ) e = create_engine(ctx.gconfig.get("db", "rwurl")) if not aliasid: if matchalias: e.execute( "delete from {0} where alias_username={1!r} and " "alias_domain={2!r}".format( table, udata["username"], udata["domain"], ) ) else: e.execute( "delete from {0} where username={1!r} and domain={2!r}".format( table, udata["username"], udata["domain"], ) ) else: for a in aliasid: adata = parse_user_spec(ctx, a) e.execute( "delete from {0} where username={1!r} and domain={2!r} " "and alias_username={3!r} and alias_domain={4!r}".format( table, udata["username"], udata["domain"], adata["username"], adata["domain"], ) ) @cli.command("show", short_help="Show user aliases") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "table", "--table", default="dbaliases", help="Name of database table (default: dbaliases)", ) @click.option( "matchalias", "--match-alias", is_flag=True, help="Match userid value as alias", ) @click.argument("userid", nargs=-1, metavar="[]") @pass_context def aliasdb_show(ctx, oformat, ostyle, table, matchalias, userid): """Show details for user aliases \b Parameters: [] - username, AoR or SIP URI for user or alias - it can be a list of userids - if not provided then all aliases are shown """ if not userid: ctx.vlog("Showing all records") e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute("select * from {0}".format(table)) ioutils_dbres_print(ctx, oformat, ostyle, res) else: for u in userid: udata = parse_user_spec(ctx, u) e = create_engine(ctx.gconfig.get("db", "rwurl")) if matchalias: ctx.vlog( "Showing records for alias [%s@%s]", udata["username"], udata["domain"], ) res = e.execute( "select * from {0} where alias_username={1!r} " "and alias_domain={2!r}".format( table, udata["username"], udata["domain"], ) ) else: ctx.vlog( "Showing records for user [%s@%s]", udata["username"], udata["domain"], ) res = e.execute( "select * from {0} where username={1!r} and " "domain={2!r}".format( table, udata["username"], udata["domain"], ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) kamcli-3.0.0/kamcli/commands/cmd_apiban.py000066400000000000000000000100151514641001400204330ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl import http.client import os import json import time import pprint @click.group( "apiban", help="Manage APIBan records", short_help="Manage APIBan records", ) @pass_context def cli(ctx): pass def apiban_key(ctx, key): if key is None: key = ctx.gconfig.get("apiban", "key", fallback=None) if key is None: return os.environ.get("APIBANKEY") return key def apiban_fetch(ctx, key): conn = http.client.HTTPSConnection("apiban.org", timeout=4) idval = "" allAddresses = [] while True: time.sleep(1) conn.request("GET", "/api/" + key + "/banned" + idval) r1 = conn.getresponse() ctx.vlog("response: " + str(r1.status) + " " + r1.reason) if r1.status == 200: data1 = r1.read() jdata = json.loads(data1) ctx.vlog( "fetched ipaddress array size: " + str(len(jdata["ipaddress"])) ) allAddresses = allAddresses + jdata["ipaddress"] if jdata["ID"] == "none": break else: idval = "/" + jdata["ID"] else: break return allAddresses @cli.command("show", short_help="Show the addresses returned by apiban.org") @click.option( "key", "--key", "-k", default=None, help="The APIBan key", ) @pass_context def apiban_show(ctx, key): """Show the APIBan addresses \b """ ctx.vlog("fetching APIBan records") key = apiban_key(ctx, key) if key is None or len(key) == 0: ctx.log( "no APIBan key - provide it via -k parameter, config file or APIBANKEY environment variable" ) return allAddresses = apiban_fetch(ctx, key) ctx.vlog("all ip addresses array size: " + str(len(allAddresses))) pprint.pprint(allAddresses) print() @cli.command( "load", short_help="Load the records from apiban.org to a Kamailio htable" ) @click.option( "key", "--key", "-k", default=None, help="The APIBan key", ) @click.option( "htname", "--htname", "-t", default=None, help="The htable name", ) @click.option( "expire", "--expire", "-x", type=int, default=0, help="The expire for htable item", ) @pass_context def apiban_load(ctx, key, htname, expire): """Load the APIBan addresses to a Kamailio htable \b """ ctx.vlog("loading APIBan records") key = apiban_key(ctx, key) if key is None or len(key) == 0: ctx.log( "no APIBan key - provide it via -k parameter, config file or APIBANKEY environment variable" ) return if htname is None: htname = ctx.gconfig.get("apiban", "htname", fallback=None) if htname is None: htname = "ipban" allAddresses = apiban_fetch(ctx, key) ctx.vlog("fetched ip addresses - array size: " + str(len(allAddresses))) if len(allAddresses) > 0: for a in allAddresses: command_ctl(ctx, "htable.seti", [htname, a, 1]) if expire > 0: command_ctl(ctx, "htable.setex", [htname, a, 1]) time.sleep(0.002) else: ctx.log("no APIBan records") @cli.command("check", short_help="Check IP address against apiban.org") @click.option( "key", "--key", "-k", default=None, help="The APIBan key", ) @click.argument("ipaddr", metavar="") @pass_context def apiban_check(ctx, key, ipaddr): """Check IP address against apiban.org \b Parameters: - IP address """ ctx.vlog("cheking address againt apiban.org") key = apiban_key(ctx, key) if key is None or len(key) == 0: ctx.log( "no APIBan key - provide it via -k parameter, config file or APIBANKEY environment variable" ) return conn = http.client.HTTPSConnection("apiban.org", timeout=4) conn.request("GET", "/api/" + key + "/check/" + ipaddr) r1 = conn.getresponse() print(r1.status, r1.reason) data1 = r1.read() print(data1) print() kamcli-3.0.0/kamcli/commands/cmd_avp.py000066400000000000000000000277351514641001400200100ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.cli import parse_user_spec @click.group( "avp", help="Manage AVP user preferences", short_help="Manage AVP user preferences", ) @pass_context def cli(ctx): pass @cli.command("db-add", short_help="Add a new AVP to database") @click.option( "dbtname", "--dbtname", "-T", default="usr_preferences", help='Database table name (default: "usr_preferences")', ) @click.option( "coluuid", "--coluuid", default="uuid", help='Column name for uuid (default: "uuid")', ) @click.option( "colusername", "--colusername", default="username", help='Column name for uuid (default: "username")', ) @click.option( "coldomain", "--coldomain", default="domain", help='Column name for domain (default: "domain")', ) @click.option( "colattribute", "--colattribute", default="attribute", help='Column name for attribute (default: "attribute")', ) @click.option( "coltype", "--coltype", default="type", help='Column name for type (default: "type")', ) @click.option( "colvalue", "--colvalue", default="value", help='Column name for value (default: "value")', ) @click.option( "atype", "--atype", "-t", type=int, default=0, help="Value of the AVP type (default: 0)", ) @click.option( "isuuid", "--is-uuid", "-u", is_flag=True, help="The argument is a , otherwise @", ) @click.option( "setuuid", "--set-uuid", "-U", is_flag=True, help="The is @, but the uuid field is also set to ", ) @click.argument("userid", metavar="") @click.argument("attribute", metavar="") @click.argument("value", metavar="") @pass_context def avp_dbadd( ctx, dbtname, coluuid, colusername, coldomain, colattribute, coltype, colvalue, atype, isuuid, setuuid, userid, attribute, value, ): """Add a new AVP record in database table \b Parameters: - user AVP id (@ or ) - attribute name - associated value for attribute """ ctx.vlog( "Adding to AVP to table [%s] - [%s] [%s] => [%s]", dbtname, userid, attribute, value, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) dbname = dbtname.encode("ascii", "ignore").decode() c_uuid = coluuid.encode("ascii", "ignore").decode() c_username = colusername.encode("ascii", "ignore").decode() c_domain = coldomain.encode("ascii", "ignore").decode() c_attribute = colattribute.encode("ascii", "ignore").decode() c_type = coltype.encode("ascii", "ignore").decode() c_value = colvalue.encode("ascii", "ignore").decode() v_userid = userid.encode("ascii", "ignore").decode() v_attribute = attribute.encode("ascii", "ignore").decode() v_value = value.encode("ascii", "ignore").decode() if isuuid: e.execute( "insert into {0} ({1}, {2}, {3}, {4}) values ({5!r}, {6!r}, {7}, {8!r})".format( dbname, c_uuid, c_attribute, c_type, c_value, v_userid, v_attribute, atype, v_value, ) ) else: udata = parse_user_spec(ctx, userid) if setuuid: e.execute( "insert into {0} ({1}, {2}, {3}, {4}, {5}, {6}) values ({7!r}, {8!r}, {9!r}, {10!r}, {11}, {12!r})".format( dbname, c_uuid, c_username, c_domain, c_attribute, c_type, c_value, udata["username"], udata["username"], udata["domain"], v_attribute, atype, v_value, ) ) else: e.execute( "insert into {0} ({1}, {2}, {3}, {4}, {5}) values ({6!r}, {7!r}, {8!r}, {9}, {10!r})".format( dbname, c_username, c_domain, c_attribute, c_type, c_value, udata["username"], udata["domain"], v_attribute, atype, v_value, ) ) @cli.command("db-rm", short_help="Delete AVPs from database") @click.option( "dbtname", "--dbtname", "-T", default="usr_preferences", help='Database table name (default: "usr_preferences")', ) @click.option( "coluuid", "--coluuid", default="uuid", help='Column name for uuid (default: "uuid")', ) @click.option( "colusername", "--colusername", default="username", help='Column name for uuid (default: "username")', ) @click.option( "coldomain", "--coldomain", default="domain", help='Column name for domain (default: "domain")', ) @click.option( "colattribute", "--colattribute", default="attribute", help='Column name for attribute (default: "attribute")', ) @click.option( "coltype", "--coltype", default="type", help='Column name for type (default: "type")', ) @click.option( "colvalue", "--colvalue", default="value", help='Column name for value (default: "value")', ) @click.option( "atype", "--atype", "-t", type=int, default=-1, help="Value of the AVP type (default: -1 - no match on type)", ) @click.option( "isuuid", "--is-uuid", "-u", is_flag=True, help="The argument is a , otherwise @", ) @click.argument("userid", metavar="") @click.argument("attribute", metavar="") @click.argument("value", metavar="") @pass_context def avp_dbrm( ctx, dbtname, coluuid, colusername, coldomain, colattribute, coltype, colvalue, atype, isuuid, userid, attribute, value, ): """Remove AVP records from database table \b Parameters: - user AVP id (@ or ) - attribute name - associated value for attribute - use '*' to match any value for , or - example - remove all AVPs: kamcli avp db-rm '*' '*' '*' """ ctx.vlog( "Removing AVPs from table [%s] - [%s] [%s] => [%s]", dbtname, userid, attribute, value, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) dbname = dbtname.encode("ascii", "ignore").decode() c_uuid = coluuid.encode("ascii", "ignore").decode() c_username = colusername.encode("ascii", "ignore").decode() c_domain = coldomain.encode("ascii", "ignore").decode() c_attribute = colattribute.encode("ascii", "ignore").decode() c_type = coltype.encode("ascii", "ignore").decode() c_value = colvalue.encode("ascii", "ignore").decode() v_userid = userid.encode("ascii", "ignore").decode() v_attribute = attribute.encode("ascii", "ignore").decode() v_value = value.encode("ascii", "ignore").decode() sqlquery = "DELETE FROM {0}".format(dbname) if atype != -1 or v_userid != "*" or v_userid != "*" or v_userid != "*": sqlquery += " WHERE 1=1" if v_userid != "*": if isuuid: sqlquery += " AND {0}={1!r}".format(c_uuid, v_userid) else: udata = parse_user_spec(ctx, userid) sqlquery += " AND {0}={1!r} AND {2}={3!r}".format( c_username, udata["username"], c_domain, udata["domain"] ) if v_attribute != "*": sqlquery += " AND {0}={1!r}".format(c_attribute, v_attribute) if atype != -1: sqlquery += " AND {0}={1}".format(c_type, atype) if v_value != "*": sqlquery += " AND {0}={1!r}".format(c_value, v_value) e.execute(sqlquery) @cli.command("db-show", short_help="Show AVPs from database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "dbtname", "--dbtname", "-T", default="usr_preferences", help='Database table name (default: "usr_preferences")', ) @click.option( "coluuid", "--coluuid", default="uuid", help='Column name for uuid (default: "uuid")', ) @click.option( "colusername", "--colusername", default="username", help='Column name for uuid (default: "username")', ) @click.option( "coldomain", "--coldomain", default="domain", help='Column name for domain (default: "domain")', ) @click.option( "colattribute", "--colattribute", default="attribute", help='Column name for attribute (default: "attribute")', ) @click.option( "coltype", "--coltype", default="type", help='Column name for type (default: "type")', ) @click.option( "colvalue", "--colvalue", default="value", help='Column name for value (default: "value")', ) @click.option( "atype", "--atype", "-t", type=int, default=-1, help="Value of the AVP type (default: -1 - no match on type)", ) @click.option( "isuuid", "--is-uuid", "-u", is_flag=True, help="The argument is a , otherwise @", ) @click.argument("userid", metavar="") @click.argument("attribute", metavar="") @click.argument("value", metavar="") @pass_context def avp_dbshow( ctx, oformat, ostyle, dbtname, coluuid, colusername, coldomain, colattribute, coltype, colvalue, atype, isuuid, userid, attribute, value, ): """Show AVP records from database table \b Parameters: - user AVP id (@ or ) - attribute name - associated value for attribute - use '*' to match any value for , or - example - remove all AVPs: kamcli avp db-rm '*' '*' '*' """ ctx.vlog( "Show AVPs from table [%s] - [%s] [%s] => [%s]", dbtname, userid, attribute, value, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) dbname = dbtname.encode("ascii", "ignore").decode() c_uuid = coluuid.encode("ascii", "ignore").decode() c_username = colusername.encode("ascii", "ignore").decode() c_domain = coldomain.encode("ascii", "ignore").decode() c_attribute = colattribute.encode("ascii", "ignore").decode() c_type = coltype.encode("ascii", "ignore").decode() c_value = colvalue.encode("ascii", "ignore").decode() v_userid = userid.encode("ascii", "ignore").decode() v_attribute = attribute.encode("ascii", "ignore").decode() v_value = value.encode("ascii", "ignore").decode() sqlquery = "SELECT * FROM {0}".format(dbname) if atype != -1 or v_userid != "*" or v_userid != "*" or v_userid != "*": sqlquery += " WHERE 1=1" if v_userid != "*": if isuuid: sqlquery += " AND {0}={1!r}".format(c_uuid, v_userid) else: udata = parse_user_spec(ctx, userid) sqlquery += " AND {0}={1!r} AND {2}={3!r}".format( c_username, udata["username"], c_domain, udata["domain"] ) if v_attribute != "*": sqlquery += " AND {0}={1!r}".format(c_attribute, v_attribute) if atype != -1: sqlquery += " AND {0}={1}".format(c_type, atype) if v_value != "*": sqlquery += " AND {0}={1!r}".format(c_value, v_value) res = e.execute(sqlquery) ioutils_dbres_print(ctx, oformat, ostyle, res) kamcli-3.0.0/kamcli/commands/cmd_config.py000066400000000000000000000043211514641001400204510ustar00rootroot00000000000000import os import sys import json import click import shutil from kamcli.cli import pass_context from kamcli.cli import COMMAND_ALIASES @click.group( "config", help="Manage the config file", short_help="Manage the config file", ) @pass_context def cli(ctx): pass @cli.command("raw", short_help="Display raw content of configuration file") @pass_context def config_raw(ctx): """Show content of configuration file for kamcli""" ctx.log("\n---") ctx.gconfig.write(sys.stdout) ctx.log("\n---") @cli.command( "show", short_help="Show expanded content of configuration file sections" ) @click.argument("sections", nargs=-1, metavar="") @pass_context def config_show(ctx, sections): """Show expanded content of configuration file section""" if sections: ctx.log("\n---") for s in sections: ctx.log("[%s]", s) for k, v in ctx.gconfig.items(s): ctx.log("%s= %s", k, v) ctx.log("\n---") @cli.command("paths", short_help="Show the paths of configuration files") @pass_context def config_paths(ctx): """Show the paths of configuration files for kamcli""" print() print(ctx.gconfig_paths) print() @cli.command("cmdaliases", short_help="Show the command aliases") @pass_context def config_cmdaliases(ctx): """Show the command aliases""" print() print(json.dumps(COMMAND_ALIASES, indent=4, sort_keys=True)) print() @cli.command("install", short_help="Install the config file") @click.option( "user", "--user", "-u", is_flag=True, help="Install in user home folder", ) @pass_context def config_install(ctx, user): if os.path.isfile("./kamcli/kamcli.ini"): if user: dirName = os.path.expanduser("~/.kamcli") else: dirName = "/etc/kamcli" if not os.path.exists(dirName): os.mkdir(dirName) click.echo("directory " + dirName + " created") else: click.echo("directory " + dirName + " already exists") shutil.copyfile("./kamcli/kamcli.ini", dirName + "/kamcli.ini") click.echo("config file installed to " + dirName + "/kamcli.ini") else: click.echo("command must be run in the source code root directory") kamcli-3.0.0/kamcli/commands/cmd_db.py000066400000000000000000000701251514641001400175760ustar00rootroot00000000000000import os import sys import click from sqlalchemy import create_engine from sqlalchemy.sql import text from sqlalchemy.exc import SQLAlchemyError from kamcli.cli import pass_context from kamcli.ioutils import ioutils_dbres_print from kamcli.ioutils import ioutils_formats_list from kamcli.dbutils import dbutils_exec_sqlfile KDB_GROUP_BASIC = ["standard"] KDB_GROUP_STANDARD = [ "acc", "lcr", "domain", "group", "permissions", "registrar", "usrloc", "msilo", "alias_db", "uri_db", "speeddial", "avpops", "auth_db", "pdt", "dialog", "dispatcher", "dialplan", "topos", ] KDB_GROUP_EXTRA = [ "imc", "cpl", "siptrace", "domainpolicy", "carrierroute", "drouting", "userblacklist", "userblocklist", "htable", "purple", "uac", "pipelimit", "mtree", "sca", "mohqueue", "rtpproxy", "rtpengine", "secfilter", ] KDB_GROUP_PRESENCE = ["presence", "rls"] KDB_GROUP_UID = [ "uid_auth_db", "uid_avp_db", "uid_domain", "uid_gflags", "uid_uri_db", ] @click.group( "db", help="Raw database operations", short_help="Raw database operations" ) @pass_context def cli(ctx): pass @cli.command("query", short_help="Run SQL statement") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("query", metavar="") @pass_context def db_query(ctx, oformat, ostyle, query): e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute(query.encode("ascii", "ignore").decode()) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("connect", short_help="Launch db cli and connect to database") @pass_context def db_connect(ctx): dbtype = ctx.gconfig.get("db", "type") if dbtype.lower() == "mysql": scmd = ("mysql -h {0} -u {1} -p{2} {3}").format( ctx.gconfig.get("db", "host"), ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), ctx.gconfig.get("db", "dbname"), ) elif dbtype == "postgresql": scmd = ('psql "postgresql://{0}:{1}@{2}/{3}"').format( ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), ctx.gconfig.get("db", "host"), ctx.gconfig.get("db", "dbname"), ) elif dbtype == "sqlite": scmd = ("sqlite3 {0} ").format( ctx.gconfig.get("db", "dbpath"), ) else: ctx.log("unsupported database type [%s]", dbtype) sys.exit() os.system(scmd) @cli.command("clirun", short_help="Run SQL statement via cli") @click.argument("query", metavar="") @pass_context def db_clirun(ctx, query): dbtype = ctx.gconfig.get("db", "type") if dbtype == "mysql": scmd = ('mysql -h {0} -u {1} -p{2} -e "{3} ;" {4}').format( ctx.gconfig.get("db", "host"), ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), query, ctx.gconfig.get("db", "dbname"), ) elif dbtype == "postgresql": scmd = ('psql "postgresql://{0}:{1}@{2}/{3}" -c "{4} ;"').format( ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), ctx.gconfig.get("db", "host"), ctx.gconfig.get("db", "dbname"), query, ) elif dbtype == "sqlite": scmd = ('sqlite3 {0} "{1} "').format( ctx.gconfig.get("db", "dbpath"), query, ) else: ctx.log("unsupported database type [%s]", dbtype) sys.exit() os.system(scmd) @cli.command("clishow", short_help="Show content of table via cli") @click.argument("table", metavar="") @pass_context def db_clishow(ctx, table): dbtype = ctx.gconfig.get("db", "type") if dbtype == "mysql": scmd = ( 'mysql -h {0} -u {1} -p{2} -e "select * from {3} ;" {4}' ).format( ctx.gconfig.get("db", "host"), ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), table, ctx.gconfig.get("db", "dbname"), ) elif dbtype == "postgresql": scmd = ( 'psql "postgresql://{0}:{1}@{2}/{3}" -c "select * from {4} ;"' ).format( ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), ctx.gconfig.get("db", "host"), ctx.gconfig.get("db", "dbname"), table, ) elif dbtype == "sqlite": scmd = ('sqlite3 {0} "select * from {1} "').format( ctx.gconfig.get("db", "dbpath"), table, ) else: ctx.log("unsupported database type [%s]", dbtype) sys.exit() os.system(scmd) @cli.command("clishowg", short_help="Show content of table via cli") @click.argument("table", metavar="
") @pass_context def db_clishowg(ctx, table): dbtype = ctx.gconfig.get("db", "type") if dbtype == "mysql": scmd = ( r'mysql -h {0} -u {1} -p{2} -e "select * from {3} \\G" {4}' ).format( ctx.gconfig.get("db", "host"), ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), table, ctx.gconfig.get("db", "dbname"), ) elif dbtype == "postgresql": scmd = ( 'psql "postgresql://{0}:{1}@{2}/{3}" -c "\\x" -c "select * from {4} ;" -c "\\x"' ).format( ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), ctx.gconfig.get("db", "host"), ctx.gconfig.get("db", "dbname"), table, ) elif dbtype == "sqlite": scmd = ('sqlite3 -line {0} "select * from {1} "').format( ctx.gconfig.get("db", "dbpath"), table, ) else: ctx.log("unsupported database type [%s]", dbtype) sys.exit() os.system(scmd) @cli.command("show", short_help="Show content of a table") @click.option( "oformat", "--output-format", "-F", type=click.Choice(ioutils_formats_list), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("table", metavar="
") @pass_context def db_show(ctx, oformat, ostyle, table): ctx.vlog("Content of database table [%s]", table) e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute("select * from {0}".format(table)) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command( "showcreate", short_help="Show create statement of of a database table" ) @click.option( "oformat", "--output-format", "-F", type=click.Choice(ioutils_formats_list), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("table", metavar="
") @pass_context def db_showcreate(ctx, oformat, ostyle, table): ctx.vlog("Show create of database table [%s]", table) dbtype = ctx.gconfig.get("db", "type") if dbtype == "mysql": e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute("show create table {0}".format(table)) ioutils_dbres_print(ctx, oformat, ostyle, res) elif dbtype == "postgresql": scmd = ('psql "postgresql://{0}:{1}@{2}/{3}" -c "\\d {4} "').format( ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), ctx.gconfig.get("db", "host"), ctx.gconfig.get("db", "dbname"), table, ) os.system(scmd) elif dbtype == "sqlite": scmd = ('sqlite3 {0} ".schema {1} "').format( ctx.gconfig.get("db", "dbpath"), table, ) os.system(scmd) else: ctx.log("unsupported database type [%s]", dbtype) @cli.command("runfile", short_help="Run SQL statements in a file") @click.argument("fname", metavar="") @pass_context def db_runfile(ctx, fname): """Run SQL statements in a file \b Parameters: - name to the file with the SQL statements """ ctx.vlog("Run statements in the file [%s]", fname) e = create_engine(ctx.gconfig.get("db", "rwurl")) dbutils_exec_sqlfile(ctx, e, fname) def db_create_mysql_host_users( ctx, e, nousers, nogrants, dbname, dbhost, dbrwuser, dbrwpassword, dbrouser, dbropassword, ): if not nousers: e.execute( "CREATE USER {0!r}@{1!r} IDENTIFIED BY {2!r}".format( dbrwuser, dbhost, dbrwpassword ) ) if not nogrants: e.execute( "GRANT ALL PRIVILEGES ON {0}.* TO {1!r}@{2!r}".format( dbname, dbrwuser, dbhost ) ) if not nousers: e.execute( "CREATE USER {0!r}@{1!r} IDENTIFIED BY {2!r}".format( dbrouser, dbhost, dbropassword ) ) if not nogrants: e.execute( "GRANT SELECT ON {0}.* TO {1!r}@{2!r}".format( dbname, dbrouser, dbhost ) ) def db_create_mysql_users(ctx, e, dbname, nousers, nogrants): dbhost = ctx.gconfig.get("db", "host") dbrwuser = ctx.gconfig.get("db", "rwuser") dbrwpassword = ctx.gconfig.get("db", "rwpassword") dbrouser = ctx.gconfig.get("db", "rouser") dbropassword = ctx.gconfig.get("db", "ropassword") dbaccesshost = ctx.gconfig.get("db", "accesshost") db_create_mysql_host_users( ctx, e, nousers, nogrants, dbname, dbhost, dbrwuser, dbrwpassword, dbrouser, dbropassword, ) if dbhost != "localhost": db_create_mysql_host_users( ctx, e, nousers, nogrants, dbname, "localhost", dbrwuser, dbrwpassword, dbrouser, dbropassword, ) if len(dbaccesshost) > 0: db_create_mysql_host_users( ctx, e, nousers, nogrants, dbname, dbaccesshost, dbrwuser, dbrwpassword, dbrouser, dbropassword, ) def db_create_sql_group(ctx, e, dirpath, dbgroup): for t in dbgroup: fname = dirpath + "/" + t + "-create.sql" dbutils_exec_sqlfile(ctx, e, fname) def db_create_sql_table_groups(ctx, e, ldirectory, alltables): db_create_sql_group(ctx, e, ldirectory, KDB_GROUP_BASIC) db_create_sql_group(ctx, e, ldirectory, KDB_GROUP_STANDARD) option = "y" if not alltables: print("Do you want to create extra tables? (y/n):", end=" ") option = input() if option == "y": db_create_sql_group(ctx, e, ldirectory, KDB_GROUP_EXTRA) if not alltables: print("Do you want to create presence tables? (y/n):", end=" ") option = input() if option == "y": db_create_sql_group(ctx, e, ldirectory, KDB_GROUP_PRESENCE) if not alltables: print("Do you want to create uid tables? (y/n):", end=" ") option = input() if option == "y": db_create_sql_group(ctx, e, ldirectory, KDB_GROUP_UID) def db_create_mysql(ctx, ldbname, ldirectory, nousers, nogrants, alltables): e = create_engine(ctx.gconfig.get("db", "adminurl")) e.execute("create database {0}".format(ldbname)) db_create_mysql_users(ctx, e, ldbname, nousers, nogrants) e.execute("use {0}".format(ldbname)) db_create_sql_table_groups(ctx, e, ldirectory, alltables) def db_create_postgresql( ctx, ldbname, ldirectory, nousers, nogrants, nofunctions, alltables ): scmd = ( 'psql "postgresql://{0}:{1}@{2}" -c "create database {3} "' ).format( ctx.gconfig.get("db", "adminuser"), ctx.gconfig.get("db", "adminpassword"), ctx.gconfig.get("db", "host"), ldbname, ) os.system(scmd) e = create_engine(ctx.gconfig.get("db", "adminurl")) if not nogrants: e.execute( "CREATE USER {0} WITH PASSWORD '{1}';".format( ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), ) ) e.execute( "GRANT CONNECT ON DATABASE {0} TO {1};".format( ldbname, ctx.gconfig.get("db", "rwuser"), ) ) if ctx.gconfig.get("db", "rwuser") != ctx.gconfig.get("db", "rouser"): e.execute( "CREATE USER {0} WITH PASSWORD '{1}';".format( ctx.gconfig.get("db", "rouser"), ctx.gconfig.get("db", "ropassword"), ) ) e.execute( "GRANT CONNECT ON DATABASE {0} TO {1};".format( ldbname, ctx.gconfig.get("db", "rouser"), ) ) e.dispose() e = create_engine( "{0}+{1}://{2}:{3}@{4}/{5}".format( ctx.gconfig.get("db", "type"), ctx.gconfig.get("db", "driver"), ctx.gconfig.get("db", "rwuser"), ctx.gconfig.get("db", "rwpassword"), ctx.gconfig.get("db", "host"), ldbname, ) ) if not nofunctions: e.execute( "CREATE FUNCTION concat(text, text) RETURNS text AS 'SELECT $1 || $2;' LANGUAGE 'sql';" ) e.execute( "CREATE FUNCTION rand() RETURNS double precision AS 'SELECT random();' LANGUAGE 'sql';" ) db_create_sql_table_groups(ctx, e, ldirectory, alltables) e.dispose() e = create_engine(ctx.gconfig.get("db", "adminurl")) if not nogrants: e.execute( "GRANT ALL PRIVILEGES ON DATABASE {0} TO {1};".format( ldbname, ctx.gconfig.get("db", "rwuser"), ) ) if ctx.gconfig.get("db", "rwuser") != ctx.gconfig.get("db", "rouser"): e.execute( "GRANT SELECT ON DATABASE {0} TO {1};".format( ldbname, ctx.gconfig.get("db", "rouser"), ) ) def db_create_sqlite(ctx, ldbname, ldirectory, alltables): e = create_engine( "{0}+{1}:///{2}".format( ctx.gconfig.get("db", "type"), ctx.gconfig.get("db", "driver"), ldbname, ) ) db_create_sql_table_groups(ctx, e, ldirectory, alltables) @cli.command("create", short_help="Create database structure") @click.option( "dbname", "--dbname", "-d", default="", help="Database name or path to the folder for database", ) @click.option( "scriptsdirectory", "--scripts-directory", "-s", default="", help="Path to the directory with db schema files", ) @click.option( "nousers", "--no-users", "-U", is_flag=True, help="Do not create users", ) @click.option( "nogrants", "--no-grants", "-G", is_flag=True, help="Do not grant privileges", ) @click.option( "nofunctions", "--no-functions", "-F", is_flag=True, help="Do not create additional SQL functions", ) @click.option( "alltables", "--all-tables", "-a", is_flag=True, help="Create all tables without asking for confirmation", ) @pass_context def db_create( ctx, dbname, scriptsdirectory, nousers, nogrants, nofunctions, alltables ): """Create database structure \b """ dbtype = ctx.gconfig.get("db", "type") if dbtype == "sqlite": ldbname = ctx.gconfig.get("db", "dbpath") else: ldbname = ctx.gconfig.get("db", "dbname") if len(dbname) > 0: ldbname = dbname ldirectory = ctx.gconfig.get("db", "scriptsdirectory") if len(scriptsdirectory) > 0: ldirectory = scriptsdirectory ctx.vlog("Creating database [%s] structure", ldbname) if dbtype == "mysql": db_create_mysql(ctx, ldbname, ldirectory, nousers, nogrants, alltables) return elif dbtype == "postgresql": db_create_postgresql( ctx, ldbname, ldirectory, nousers, nogrants, nofunctions, alltables ) return elif dbtype == "sqlite": db_create_sqlite(ctx, ldbname, ldirectory, alltables) return else: ctx.vlog("Database type [%s] not supported yet", dbtype) return @cli.command("create-dbonly", short_help="Create database only") @click.option( "dbname", "--dbname", "-d", default="", help="Database name or path to the folder for database", ) @pass_context def db_create_dbonly(ctx, dbname): """Create database only \b """ ctx.vlog("Creating only database [%s]", dbname) dbtype = ctx.gconfig.get("db", "type") if dbtype == "sqlite": ldbname = ctx.gconfig.get("db", "dbpath") else: ldbname = ctx.gconfig.get("db", "dbname") if len(dbname) > 0: ldbname = dbname if dbtype == "mysql": e = create_engine(ctx.gconfig.get("db", "adminurl")) e.execute("create database {0}".format(ldbname)) elif dbtype == "postgresql": scmd = ( 'psql "postgresql://{0}:{1}@{2}" -c "create database {3} "' ).format( ctx.gconfig.get("db", "adminuser"), ctx.gconfig.get("db", "adminpassword"), ctx.gconfig.get("db", "host"), ldbname, ) os.system(scmd) elif dbtype == "sqlite": ctx.vlog("Database file for type [%s] is created on first use", dbtype) else: ctx.vlog("Database type [%s] not supported yet", dbtype) return @cli.command("drop", short_help="Drop database") @click.option( "dbname", "--dbname", "-d", default="", help="Database name or path to the database", ) @click.option( "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", ) @pass_context def db_drop(ctx, dbname, yes): """Drop database \b """ dbtype = ctx.gconfig.get("db", "type") if dbtype == "sqlite": ldbname = ctx.gconfig.get("db", "dbpath") else: ldbname = ctx.gconfig.get("db", "dbname") if len(dbname) > 0: ldbname = dbname if not yes: print("Dropping database. Are you sure? (y/n):", end=" ") option = input() if option != "y": ctx.vlog("Skip dropping database [%s]", ldbname) return ctx.vlog("Dropping database [%s]", ldbname) if dbtype == "mysql": e = create_engine(ctx.gconfig.get("db", "adminurl")) e.execute("drop database {0}".format(ldbname)) elif dbtype == "postgresql": scmd = ( 'psql "postgresql://{0}:{1}@{2}" -c "drop database {3} "' ).format( ctx.gconfig.get("db", "adminuser"), ctx.gconfig.get("db", "adminpassword"), ctx.gconfig.get("db", "host"), ldbname, ) os.system(scmd) elif dbtype == "sqlite": if not os.path.isfile(ldbname): ctx.vlog("Database file [%s] does not exist", ldbname) else: os.remove(ldbname) return else: ctx.vlog("Database type [%s] not supported yet", dbtype) return def db_create_tables_list(ctx, directory, group): dbtype = ctx.gconfig.get("db", "type") if dbtype != "mysql": ctx.vlog("Database type [%s] not supported yet", dbtype) return ldirectory = "" if len(directory) > 0: ldirectory = directory e = create_engine(ctx.gconfig.get("db", "rwurl")) db_create_sql_group(ctx, e, ldirectory, group) @cli.command("create-tables-basic", short_help="Create basic database tables") @click.option( "scriptsdirectory", "--scripts-directory", "-s", default="", help="Path to the directory with db schema files", ) @pass_context def db_create_tables_basic(ctx, scriptsdirectory): """Create basic database tables \b """ ldirectory = ctx.gconfig.get("db", "scriptsdirectory") if len(scriptsdirectory) > 0: ldirectory = scriptsdirectory db_create_tables_list(ctx, ldirectory, KDB_GROUP_BASIC) @cli.command( "create-tables-standard", short_help="Create standard database tables" ) @click.option( "scriptsdirectory", "--scripts-directory", "-s", default="", help="Path to the directory with db schema files", ) @pass_context def db_create_tables_standard(ctx, scriptsdirectory): """Create standard database tables \b """ ldirectory = ctx.gconfig.get("db", "scriptsdirectory") if len(scriptsdirectory) > 0: ldirectory = scriptsdirectory db_create_tables_list(ctx, ldirectory, KDB_GROUP_STANDARD) @cli.command("create-tables-extra", short_help="Create extra database tables") @click.option( "scriptsdirectory", "--scripts-directory", "-s", default="", help="Path to the directory with db schema files", ) @pass_context def db_create_tables_extra(ctx, scriptsdirectory): """Create extra database tables \b """ ldirectory = ctx.gconfig.get("db", "scriptsdirectory") if len(scriptsdirectory) > 0: ldirectory = scriptsdirectory db_create_tables_list(ctx, ldirectory, KDB_GROUP_EXTRA) @cli.command( "create-tables-presence", short_help="Create presence database tables" ) @click.option( "scriptsdirectory", "--scripts-directory", "-s", default="", help="Path to the directory with db schema files", ) @pass_context def db_create_tables_presence(ctx, scriptsdirectory): """Create presence database tables \b """ ldirectory = ctx.gconfig.get("db", "scriptsdirectory") if len(scriptsdirectory) > 0: ldirectory = scriptsdirectory db_create_tables_list(ctx, ldirectory, KDB_GROUP_PRESENCE) @cli.command("create-tables-uid", short_help="Create uid database tables") @click.option( "scriptsdirectory", "--scripts-directory", "-s", default="", help="Path to the directory with db schema files", ) @pass_context def db_create_tables_uid(ctx, scriptsdirectory): """Create uid database tables \b """ ldirectory = ctx.gconfig.get("db", "scriptsdirectory") if len(scriptsdirectory) > 0: ldirectory = scriptsdirectory db_create_tables_list(ctx, ldirectory, KDB_GROUP_UID) @cli.command( "create-tables-group", short_help="Create the group of database tables for a specific extension", ) @click.option( "scriptsdirectory", "--scripts-directory", "-s", default="", help="Path to the directory with db schema files", ) @click.argument("gname", metavar="") @pass_context def db_create_tables_group(ctx, scriptsdirectory, gname): """Create the group of database tables for a specific extension \b Parameters: - the name of the group of tables """ ldirectory = ctx.gconfig.get("db", "scriptsdirectory") if len(scriptsdirectory) > 0: ldirectory = scriptsdirectory e = create_engine(ctx.gconfig.get("db", "rwurl")) fpath = ldirectory + "/" + gname + "-create.sql" dbutils_exec_sqlfile(ctx, e, fpath) @cli.command( "create-table-like", short_help="Create a new table like another one" ) @click.argument("newname", metavar="") @click.argument("oldname", metavar="") @pass_context def db_create_table_like(ctx, newname, oldname): e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute("CREATE TABLE {0} LIKE {1}".format(newname, oldname)) @cli.command("grant", short_help="Create db access users and grant privileges") @click.option( "dbname", "--dbname", "-d", default="", help="Database name", ) @pass_context def db_grant(ctx, dbname): """Create db access users and grant privileges \b """ dbtype = ctx.gconfig.get("db", "type") if dbtype != "mysql": ctx.vlog("Database type [%s] not supported yet", dbtype) return ldbname = ctx.gconfig.get("db", "dbname") if len(dbname) > 0: ldbname = dbname ctx.vlog("Creating only database [%s]", ldbname) e = create_engine(ctx.gconfig.get("db", "adminurl")) db_create_mysql_users(ctx, e, ldbname, False, False) def db_revoke_host_users(ctx, e, dbname, dbhost, dbrwuser, dbrouser): e.execute( "REVOKE ALL PRIVILEGES ON {0}.* FROM {1!r}@{2!r}".format( dbname, dbrwuser, dbhost ) ) e.execute("DROP USER {0!r}@{1!r}".format(dbrwuser, dbhost)) e.execute( "REVOKE SELECT ON {0}.* FROM {1!r}@{2!r}".format( dbname, dbrouser, dbhost ) ) e.execute("DROP USER {0!r}@{1!r}".format(dbrouser, dbhost)) def db_revoke_users(ctx, e, dbname): dbhost = ctx.gconfig.get("db", "host") dbrwuser = ctx.gconfig.get("db", "rwuser") dbrouser = ctx.gconfig.get("db", "rouser") dbaccesshost = ctx.gconfig.get("db", "accesshost") db_revoke_host_users(ctx, e, dbname, dbhost, dbrwuser, dbrouser) if dbhost != "localhost": db_revoke_host_users( ctx, e, dbname, "localhost", dbrwuser, dbrouser, ) if len(dbaccesshost) > 0: db_revoke_host_users( ctx, e, dbname, dbaccesshost, dbrwuser, dbrouser, ) @cli.command("revoke", short_help="Revoke db access privileges") @click.option( "dbname", "--dbname", "-d", default="", help="Database name", ) @pass_context def db_revoke(ctx, dbname): """Revoke db access privileges \b """ dbtype = ctx.gconfig.get("db", "type") if dbtype != "mysql": ctx.vlog("Database type [%s] not supported yet", dbtype) return ldbname = ctx.gconfig.get("db", "dbname") if len(dbname) > 0: ldbname = dbname ctx.vlog("Revoke access to database [%s]", ldbname) e = create_engine(ctx.gconfig.get("db", "adminurl")) db_revoke_users(ctx, e, ldbname) @cli.command( "version-set", short_help="Set the version number for a table structure" ) @click.option( "vertable", "--version-table", default="version", help="Name of the table with version records", ) @click.argument("table", metavar="
") @click.argument("version", metavar="", type=int) @pass_context def db_version_set(ctx, vertable, table, version): """Set the version number for a table structure \b Parameters:
- Name of the table to set the version for - Version number """ e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "delete from {0} where table_name={1!r}".format( vertable.encode("ascii", "ignore").decode(), table.encode("ascii", "ignore").decode(), ) ) e.execute( "insert into {0} (table_name, table_version) values ({1!r}, {2})".format( vertable.encode("ascii", "ignore").decode(), table.encode("ascii", "ignore").decode(), version, ) ) @cli.command( "version-get", short_help="Get the version number for a table structure" ) @click.option( "vertable", "--version-table", default="version", help="Name of the table with version records", ) @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("table", metavar="
") @pass_context def db_version_get(ctx, vertable, oformat, ostyle, table): """Get the version number for a table structure \b Parameters:
- Name of the table to get the version for """ e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute( "select * from {0} where table_name={1!r}".format( vertable.encode("ascii", "ignore").decode(), table.encode("ascii", "ignore").decode(), ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) kamcli-3.0.0/kamcli/commands/cmd_dialog.py000066400000000000000000000060721514641001400204500ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "dialog", help="Manage dialog module (active calls tracking)", short_help="Manage dialog module", ) @pass_context def cli(ctx): pass @cli.command("showdb", short_help="Show dialog records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @pass_context def dialog_showdb(ctx, oformat, ostyle): """Show details for records in dialog table \b """ e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog("Showing all dialog records") res = e.execute("select * from dialog") ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("list", short_help="Show details for dialog records in memory") @pass_context def dialog_list(ctx): """Show details for dialog records in memory \b """ command_ctl(ctx, "dlg.list", []) @cli.command("match", short_help="Show the details for the matching dialogs") @click.argument("mkey", metavar="") @click.argument("mop", metavar="") @click.argument("mval", metavar="") @pass_context def dialog_match(ctx, mkey, mop, mval): """Show the details for the matching dialogs \b Parameters: - matching key: ruri - use R-URI; furi - use From-URI; turi - use To-URI; callid - use Call-Id - matching operator: eq - string comparison; re - regular expression; sw - starts-with - matching value """ command_ctl(ctx, "dlg.list_match", [mkey, mop, mval]) @cli.command( "terminate", short_help="Send BYE to the dialog identified by call-id," " from-tag and to-tag", ) @click.argument("callid", metavar="") @click.argument("fromtag", metavar="") @click.argument("totag", metavar="") @pass_context def dialog_terminate(ctx, callid, fromtag, totag): """Send BYE to the dialog identified by callid, from-tag and to-tag \b Parameters: - Call-Id value - From-Tag value - To-Tag value """ command_ctl(ctx, "dlg.terminate_dlg", [callid, fromtag, totag]) @cli.command("stats_active", short_help="Show statistics for active dialogs") @pass_context def dialog_stats_active(ctx): """Show statistics for active dialogs \b """ command_ctl(ctx, "dlg.stats_active", []) @cli.command("profile_list", short_help="List the content of a profile") @click.argument("profile", metavar="") @pass_context def dialog_profile_list(ctx, profile): """List the dialogs groupped in a profile \b Parameters: - the name of the profile """ command_ctl(ctx, "dlg.profile_list", [profile]) kamcli-3.0.0/kamcli/commands/cmd_dialplan.py000066400000000000000000000122561514641001400207760ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "dialplan", help="Manage dialplan module (regexp translations)", short_help="Manage dialplan module", ) @pass_context def cli(ctx): pass @cli.command("add", short_help="Add a new dialplan rule") @click.option( "priority", "--priority", type=int, default=0, help="Priority value (default: 0)", ) @click.option( "matchop", "--match-op", default="equal", help="Match operator: equal, regexp, fnmatch (default: equal)", ) @click.option( "matchlen", "--match-len", type=int, default=0, help="Match target lenght (default: 0)", ) @click.option("attrs", "--attrs", default="", help='Attributes (default: "")') @click.argument("dpid", metavar="", type=int) @click.argument("matchexp", metavar="") @click.argument("substrepl", nargs=-1, metavar="") @pass_context def dialplan_add( ctx, priority, matchop, matchlen, attrs, dpid, matchexp, substrepl ): """Add a new translation rule in dialplan table \b Parameters: - dialplan id - match expression [] - substitution match expression [] - replacement expression """ matchid = 0 if matchop == "regexp": matchid = 1 elif matchop == "fnmatch": matchid = 2 substexp = "" replexp = "" if len(substrepl) > 0: substexp = substrepl[0] if len(substrepl) > 1: replexp = substrepl[1] ctx.vlog( "Adding to dialplan id [%d] match [%s] expression [%s]", dpid, matchop, matchexp, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "insert into dialplan (dpid, pr, match_op, match_exp, match_len, " "subst_exp, repl_exp, attrs) values " "({0}, {1}, {2}, {3!r}, {4}, {5!r}, {6!r}, {7!r})".format( dpid, priority, matchid, matchexp.encode("ascii", "ignore").decode(), matchlen, substexp.encode("ascii", "ignore").decode(), replexp.encode("ascii", "ignore").decode(), attrs.encode("ascii", "ignore").decode(), ) ) @cli.command("rm", short_help="Remove records from dialplan") @click.argument("dpid", metavar="", type=int) @click.argument("matchexp", nargs=-1, metavar="") @pass_context def dialplan_rm(ctx, dpid, matchexp): """Remove records from dialplan \b Parameters: - dialplan id [] - match expression """ e = create_engine(ctx.gconfig.get("db", "rwurl")) if not matchexp: e.execute("delete from dialplan where dpid={0}".format(dpid)) else: for m in matchexp: e.execute( "delete from dialplan where " "dpid={0} and match_exp={1!r}".format( dpid, matchexp.encode("ascii", "ignore").decode() ) ) @cli.command("showdb", short_help="Show dialplan records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("dpid", nargs=-1, metavar="[]", type=int) @pass_context def dialplan_showdb(ctx, oformat, ostyle, dpid): """Show details for records in dialplan \b Parameters: [] - dialplan id """ e = create_engine(ctx.gconfig.get("db", "rwurl")) if not dpid: ctx.vlog("Showing all dialplan records") res = e.execute("select * from dialplan") ioutils_dbres_print(ctx, oformat, ostyle, res) else: for d in dpid: ctx.vlog("Showing dialplan records for set id: " + d) res = e.execute("select * from dialplan where dpid={0}".format(d)) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("list", short_help="Show details for dialplan records in memory") @click.argument("dpid", metavar="[]", type=int) @pass_context def dialplan_list(ctx, dpid): """Show details for dialplan records in memory \b Parameters: - dialplan id """ command_ctl(ctx, "dialplan.dump", [dpid]) @cli.command( "reload", short_help="Reload dialplan records from database into memory" ) @pass_context def dialplan_reload(ctx): """Reload dialplan records from database into memory """ command_ctl(ctx, "dialplan.reload", []) @cli.command( "translate", short_help="Translate using the rules from dialplan " "applied to input value", ) @click.argument("dpid", metavar="", type=int) @click.argument("ivalue", metavar="") @pass_context def dialplan_translate(ctx, dpid, ivalue): """Translate using the rules from dialplan applied to input value \b Parameters: - dialplan id - input value """ command_ctl(ctx, "dialplan.translate", [dpid, ivalue]) kamcli-3.0.0/kamcli/commands/cmd_dispatcher.py000066400000000000000000000117701514641001400213400ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "dispatcher", help="Manage dispatcher module (load balancer)", short_help="Manage dispatcher module", ) @pass_context def cli(ctx): pass @cli.command("add", short_help="Add a new dispatcher destination") @click.option("flags", "--flags", type=int, default=0, help="Flags value") @click.option( "priority", "--priority", type=int, default=0, help="Priority value" ) @click.option("attrs", "--attrs", default="", help='Attributes (default: "")') @click.option( "description", "--desc", default="", help='Description (default: "")' ) @click.argument("setid", metavar="", type=int) @click.argument("destination", metavar="") @pass_context def dispatcher_add( ctx, flags, priority, attrs, description, setid, destination ): """Add a new destination in a set of dispatcher db table \b Parameters: - dispatching set id - SIP URI for destination """ ctx.vlog("Adding to setid [%d] destination [%s]", setid, destination) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "insert into dispatcher " "(setid, destination, flags, priority, attrs, description) " "values ({0}, {1!r}, {2}, {3}, {4!r}, {5!r})".format( setid, destination.encode("ascii", "ignore").decode(), flags, priority, attrs.encode("ascii", "ignore").decode(), description.encode("ascii", "ignore").decode(), ) ) @cli.command("rm", short_help="Remove a destination from dispatcher table") @click.argument("setid", metavar="", type=int) @click.argument("destination", metavar="") @pass_context def dispatcher_rm(ctx, setid, destination): """Remove a destination from db dispatcher table \b Parameters: - dispatching set id - SIP URI for destination """ e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "delete from dispatcher where setid={0} and destination={1!r}".format( setid, destination.encode("ascii", "ignore").decode() ) ) @cli.command("showdb", short_help="Show dispatcher records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("setid", nargs=-1, metavar="[]", type=int) @pass_context def dispatcher_showdb(ctx, oformat, ostyle, setid): """Show details for records in dispatcher table \b Parameters: [] - dispatching set id """ e = create_engine(ctx.gconfig.get("db", "rwurl")) if not setid: ctx.vlog("Showing all dispatcher records") res = e.execute("select * from dispatcher") ioutils_dbres_print(ctx, oformat, ostyle, res) else: for s in setid: ctx.vlog("Showing dispatcher records for set id") res = e.execute( "select * from dispatcher where setid={0}".format(s) ) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command( "list", short_help="Show details for dispatcher records in memory" ) @pass_context def dispatcher_list(ctx): """Show details for dispatcher records in memory \b """ command_ctl(ctx, "dispatcher.list", []) @cli.command( "reload", short_help="Reload dispatcher records from database into memory" ) @pass_context def dispatcher_reload(ctx): """Reload dispatcher records from database into memory \b """ command_ctl(ctx, "dispatcher.reload", []) @cli.command("memadd", short_help="Add a new dispatcher destination in memory") @click.option("flags", "--flags", type=int, default=0, help="Flags value") @click.argument("setid", metavar="", type=int) @click.argument("destination", metavar="") @pass_context def dispatcher_memadd(ctx, flags, setid, destination): """Add a new destination in a set of dispatcher memory \b Parameters: - dispatching set id - SIP URI for destination """ ctx.vlog("Adding to setid [%d] destination [%s]", setid, destination) command_ctl(ctx, "dispatcher.add", [setid, destination, flags]) @cli.command("memrm", short_help="Remove a destination from dispatcher memory") @click.argument("setid", metavar="", type=int) @click.argument("destination", metavar="") @pass_context def dispatcher_memrm(ctx, setid, destination): """Remove a destination from dispatcher memory \b Parameters: - dispatching set id - SIP URI for destination """ command_ctl(ctx, "dispatcher.remove", [setid, destination]) kamcli-3.0.0/kamcli/commands/cmd_dlgs.py000066400000000000000000000043221514641001400201360ustar00rootroot00000000000000import click from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "dlgs", help="Manage dlgs module (active calls stats)", short_help="Manage dlgs module", ) @pass_context def cli(ctx): pass @cli.command("list", short_help="Show details for dialog records in memory") @pass_context def dlgs_list(ctx): """Show details for dialog records in memory \b """ command_ctl(ctx, "dlgs.list", []) @cli.command( "briefing", short_help="Show summary for dialog records in memory" ) @pass_context def dlgs_briefing(ctx): """Show summary for dialog records in memory \b """ command_ctl(ctx, "dlgs.briefing", []) @cli.command("get", short_help="Get the details for the first matching dialog") @click.argument("mkey", metavar="") @click.argument("mop", metavar="") @click.argument("mval", metavar="") @pass_context def dlgs_get(ctx, mkey, mop, mval): """Show the details for the first matching dialog \b Parameters: - matching key: src - src attribute; dst - dst attribute; data - data attribute; - matching operator: eq - equal string comparison; ne - not-equal string comparison; re - regular expression; sw - starts-with fm - fast match - matching value """ command_ctl(ctx, "dlgs.get", [mkey, mop, mval]) @cli.command("getall", short_help="Get the details for all matching dialogs") @click.argument("mkey", metavar="") @click.argument("mop", metavar="") @click.argument("mval", metavar="") @pass_context def dlgs_getall(ctx, mkey, mop, mval): """Show the details for all matching dialogs \b Parameters: - matching key: src - src attribute; dst - dst attribute; data - data attribute; - matching operator: eq - equal string comparison; ne - not-equal string comparison; re - regular expression; sw - starts-with fm - fast match - matching value """ command_ctl(ctx, "dlgs.getall", [mkey, mop, mval]) kamcli-3.0.0/kamcli/commands/cmd_domain.py000066400000000000000000000054471514641001400204650ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "domain", help="Manage domain module (multi-domain records)", short_help="Manage domain module", ) @pass_context def cli(ctx): pass @cli.command("add", short_help="Add a new domain") @click.argument("domain", metavar="") @pass_context def domain_add(ctx, domain): """Add a new domain \b Parameters: - domain value """ ctx.vlog("Adding a new domain [%s]", domain) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "insert into domain (domain) values ({0!r})".format( domain.encode("ascii", "ignore").decode() ) ) @cli.command("rm", short_help="Remove a record from domain table") @click.argument("domain", metavar="") @pass_context def domain_rm(ctx, domain): """Remove a a record from db domain table \b Parameters: - domain value """ e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "delete from domain where domain={0!r}".format( domain.encode("ascii", "ignore").decode() ) ) ## # # @cli.command("showdb", short_help="Show domain records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("domain", nargs=-1, metavar="[]") @pass_context def domain_showdb(ctx, oformat, ostyle, domain): """Show details for records in domain table \b Parameters: [] - domain value (optional) """ e = create_engine(ctx.gconfig.get("db", "rwurl")) if not domain: ctx.vlog("Showing all domain records") res = e.execute("select * from domain") ioutils_dbres_print(ctx, oformat, ostyle, res) else: for d in domain: ctx.vlog("Showing a specific domain record") res = e.execute( "select * from domain where domain={0!r}".format(d) ) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("list", short_help="Show details for domain records in memory") @pass_context def domain_list(ctx): """Show details for domain records in memory \b """ command_ctl(ctx, "domain.dump", []) @cli.command( "reload", short_help="Reload domain records from database into memory" ) @pass_context def domain_reload(ctx): """Reload domain records from database into memory \b """ command_ctl(ctx, "domain.reload", []) kamcli-3.0.0/kamcli/commands/cmd_group.py000066400000000000000000000070451514641001400203460ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.cli import parse_user_spec @click.group( "group", help="Manage the ACL of users with group membership", short_help="Manage group module", ) @pass_context def cli(ctx): pass @cli.command("grant", short_help="Add a user into a group") @click.argument("userid", metavar="") @click.argument("groupid", metavar="") @pass_context def group_grant(ctx, userid, groupid): """Add a user into a group (grant privilege) \b Parameters: - username, AoR or SIP URI for subscriber - group name """ udata = parse_user_spec(ctx, userid) ctx.vlog( "Adding user [%s@%s] in group [%s]", udata["username"], udata["domain"], groupid, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "insert into grp (username, domain, grp) values ({0!r}, {1!r}, {2!r})".format( udata["username"], udata["domain"], groupid, ) ) @cli.command("revoke", short_help="Remove a user from groups") @click.argument("userid", metavar="") @click.argument("groupid", metavar="", nargs=-1) @pass_context def group_revoke(ctx, userid, groupid): """Remove a user from groups (revoke privilege) \b Parameters: - username, AoR or SIP URI for subscriber - group name """ udata = parse_user_spec(ctx, userid) ctx.log( "Removing ACL for user [%s@%s]", udata["username"], udata["domain"] ) e = create_engine(ctx.gconfig.get("db", "rwurl")) if not groupid: e.execute( "delete from grp where username={0!r} and domain={1!r}".format( udata["username"], udata["domain"], ) ) else: e.execute( "delete from grp where username={0!r} and domain={1!r} and grp={2!r}".format( udata["username"], udata["domain"], groupid, ) ) @cli.command("show", short_help="Show group membership details") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("userid", nargs=-1, metavar="[]") @pass_context def group_show(ctx, oformat, ostyle, userid): """Show details for subscribers \b Parameters: [] - username, AoR or SIP URI for subscriber - it can be a list of userids - if not provided then all subscribers are shown """ if not userid: ctx.vlog("Showing all records") e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute("select * from grp") ioutils_dbres_print(ctx, oformat, ostyle, res) else: for u in userid: udata = parse_user_spec(ctx, u) ctx.vlog( "Showing group membership for user [%s@%s]", udata["username"], udata["domain"], ) e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute( "select * from grp where username={0!r} and domain={1!r}".format( udata["username"], udata["domain"], ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) kamcli-3.0.0/kamcli/commands/cmd_htable.py000066400000000000000000000262731514641001400204550ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "htable", help="Management of htable module", short_help="Management of htable module", ) @pass_context def cli(ctx): pass @cli.command("list", short_help="List the content of hash table named htname") @click.argument("htname", metavar="") @pass_context def htable_list(ctx, htname): """List the content of hash table named htname \b Parameters: - the name of hash table """ command_ctl(ctx, "htable.dump", [htname]) @cli.command("get", short_help="Get the value for $sht(htname=>itname)") @click.argument("htname", metavar="") @click.argument("itname", nargs=-1, metavar="") @pass_context def htable_get(ctx, htname, itname): """Get the value for $sht(htname=>itname) \b Parameters: - the name of hash table - the name of item """ if not itname: command_ctl(ctx, "htable.dump", [htname]) else: for itn in itname: command_ctl(ctx, "htable.get", [htname, itn]) @cli.command("sets", short_help="Set $sht(htname=>itname) to string value") @click.argument("htname", metavar="") @click.argument("itname", metavar="") @click.argument("sval", metavar="") @pass_context def htable_sets(ctx, htname, itname, sval): """Set $sht(htname=>itname) to string value \b Parameters: - the name of hash table - the name of item - the string value """ command_ctl(ctx, "htable.sets", [htname, itname, sval]) @cli.command("seti", short_help="Set $sht(htname=>itname) to int value") @click.argument("htname", metavar="") @click.argument("itname", metavar="") @click.argument("ival", metavar="", type=int) @pass_context def htable_seti(ctx, htname, itname, ival): """Set $sht(htname=>itname) to int value \b Parameters: - the name of hash table - the name of item - the int value """ command_ctl(ctx, "htable.seti", [htname, itname, ival]) @cli.command("setex", short_help="Set expire for $sht(htname=>itname)") @click.argument("htname", metavar="") @click.argument("itname", metavar="") @click.argument("ival", metavar="", type=int) @pass_context def htable_setex(ctx, htname, itname, exval): """Set expire for $sht(htname=>itname) \b Parameters: - the name of hash table - the name of item - the int value """ command_ctl(ctx, "htable.setex", [htname, itname, exval]) @cli.command( "setxs", short_help="Set $sht(htname=>itname) to string value with expire" ) @click.argument("htname", metavar="") @click.argument("itname", metavar="") @click.argument("sval", metavar="") @click.argument("exp", metavar="", type=int) @pass_context def htable_setxs(ctx, htname, itname, sval, exp): """Set $sht(htname=>itname) to string value with expire \b Parameters: - the name of hash table - the name of item - the string value - the expire interval """ command_ctl(ctx, "htable.setxs", [htname, itname, sval, exp]) @cli.command( "setxi", short_help="Set $sht(htname=>itname) to string value with expire" ) @click.argument("htname", metavar="") @click.argument("itname", metavar="") @click.argument("ival", metavar="", type=int) @click.argument("exp", metavar="", type=int) @pass_context def htable_setxi(ctx, htname, itname, ival, exp): """Set $sht(htname=>itname) to string value with expire \b Parameters: - the name of hash table - the name of item - the int value - the expire interval """ command_ctl(ctx, "htable.setxi", [htname, itname, ival, exp]) @cli.command("rm", short_help="Remove the item $sht(htname=>itname)") @click.option( "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", ) @click.argument("htname", metavar="") @click.argument("itname", metavar="") @pass_context def htable_rm(ctx, yes, htname, itname): """Remove the item $sht(htname=>itname) \b Parameters: - the name of hash table - the name of item """ if not yes: print("Removing item. Are you sure? (y/n):", end=" ") option = input() if option != "y": ctx.vlog("Skip removing item [%s]", itname) return command_ctl(ctx, "htable.delete", [htname, itname]) @cli.command( "flush", short_help="Remove all the content of hash table named htname" ) @click.argument("htname", metavar="") @pass_context def htable_flush(ctx, htname): """Remove all the content of hash table named htname \b Parameters: - the name of hash table """ command_ctl(ctx, "htable.flush", [htname]) @cli.command( "reload", short_help="Reload the content from database for hash table named htname", ) @click.argument("htname", metavar="") @pass_context def htable_reload(ctx, htname): """Reload the content from database for hash table named htname \b Parameters: - the name of hash table """ command_ctl(ctx, "htable.reload", [htname]) @cli.command( "store", short_help="Write the content to database for hash table named htname", ) @click.argument("htname", metavar="") @pass_context def htable_store(ctx, htname): """Store the content to database for hash table named htname \b Parameters: - the name of hash table """ command_ctl(ctx, "htable.stored", [htname]) @cli.command( "list-tables", short_help="List the hash tables", ) @pass_context def htable_list_tables(ctx): """List the hash tables \b """ command_ctl(ctx, "htable.listTables", []) @cli.command( "stats", short_help="Print statistics for hash tables", ) @pass_context def htable_stats(ctx): """Print statistics for hash tables \b """ command_ctl(ctx, "htable.stats", []) @cli.command("db-add", short_help="Add a new htable record to database") @click.option( "dbtname", "--dbtname", default="htable", help='Database table name (default: "htable")', ) @click.option( "colkeyname", "--colkeyname", default="key_name", help='Column name for key name (default: "key_name")', ) @click.option( "colkeyvalue", "--colkeyvalue", default="key_value", help='Column name for value (default: "key_value")', ) @click.option( "colvaltype", "--colvaltype", default="value_type", help='Column name for value type (default: "value_type")', ) @click.option( "valtype", "--valtype", type=int, default=0, help="Value type (default: 0)" ) @click.argument("keyname", metavar="") @click.argument("keyvalue", metavar="") @pass_context def htable_dbadd( ctx, dbtname, colkeyname, colkeyvalue, colvaltype, valtype, keyname, keyvalue, ): """Add a new htable record in database table \b Parameters: - key name - associated value for key name """ ctx.vlog( "Adding to htable [%s] record [%s] => [%s]", dbtname, keyname, keyvalue ) e = create_engine(ctx.gconfig.get("db", "rwurl")) dbname = dbtname.encode("ascii", "ignore").decode() col_kname = colkeyname.encode("ascii", "ignore").decode() col_kvalue = colkeyvalue.encode("ascii", "ignore").decode() col_valtype = colvaltype.encode("ascii", "ignore").decode() kname = keyname.encode("ascii", "ignore").decode() kvalue = keyvalue.encode("ascii", "ignore").decode() if valtype == 0: e.execute( "insert into {0} ({1}, {2}) values ({3!r}, {4!r})".format( dbname, col_kname, col_kvalue, kname, kvalue ) ) else: e.execute( "insert into {0} ({1}, {2}, {3}) values ({4!r}, {5}, {6!r})".format( dbname, col_kname, col_valtype, col_kvalue, kname, valtype, kvalue, ) ) @cli.command("db-rm", short_help="Remove a record from htable database") @click.option( "dbtname", "--dbtname", default="htable", help='Database table name (default: "htable")', ) @click.option( "colkeyname", "--colkeyname", default="key_name", help='Column name for key name (default: "key_name")', ) @click.option( "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", ) @click.argument("keyname", metavar="") @pass_context def htable_dbrm(ctx, dbtname, colkeyname, yes, keyname): """Remove a record from htable database table \b Parameters: - key name to match the record """ if not yes: print("Removing item. Are you sure? (y/n):", end=" ") option = input() if option != "y": ctx.vlog("Skip removing item [%s]", keyname) return e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "delete from {0} where {1}={2!r}".format( dbtname.encode("ascii", "ignore").decode(), colkeyname.encode("ascii", "ignore").decode(), keyname.encode("ascii", "ignore").decode(), ) ) @cli.command("db-show", short_help="Show htable records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "dbtname", "--dbtname", default="htable", help='Database table name (default: "htable")', ) @click.option( "colkeyname", "--colkeyname", default="key_name", help='Column name for key name (default: "key_name")', ) @click.argument("keyname", nargs=-1, metavar="[]") @pass_context def htable_dbshow(ctx, oformat, ostyle, dbtname, colkeyname, keyname): """Show details for records in htable database table \b Parameters: - key name to match the record (optional) """ e = create_engine(ctx.gconfig.get("db", "rwurl")) if not keyname: ctx.vlog("Showing all htable database records") res = e.execute( "select * from {0}".format( dbtname.encode("ascii", "ignore").decode() ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) else: ctx.vlog("Showing htable database records for key name") for k in keyname: res = e.execute( "select * from {0} where {1}={2!r}".format( dbtname.encode("ascii", "ignore").decode(), colkeyname.encode("ascii", "ignore").decode(), k.encode("ascii", "ignore").decode(), ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) kamcli-3.0.0/kamcli/commands/cmd_jsonrpc.py000066400000000000000000000044251514641001400206670ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl_name from kamcli.iorpc import command_jsonrpc_fifo from kamcli.iorpc import command_jsonrpc_socket @click.command("jsonrpc", short_help="Execute JSONRPC commands") @click.option( "dryrun", "--dry-run", is_flag=True, help="Do not execute the command, only print it", ) @click.option( "nolog", "--no-log", is_flag=True, help="Do not print the log with executed command", ) @click.option( "storepath", "--store-path", "-s", default="", help="Path on server where to store the result", ) @click.argument("cmd", nargs=1, metavar="[]") @click.argument("params", nargs=-1, metavar="[]") @pass_context def cli(ctx, dryrun, nolog, storepath, cmd, params): """Execute JSONRPC command \b Command alias: rpc Parameters: - - the JSONRPC command - - parameters for JSONRPC command - by default the value of a parameter is considered of type string - to enforce integer value prefix with 'i:' (e.g., i:10) - string values can be also prefixed with 's:' - if a parameter starts with 's:', prefix it with 's:' Examples: - rpc system.listMethods - rpc core.psx - rpc stats.get_statistics all - rpc pv.shvSet counter i:123 """ if not nolog: ctx.log("Running JSONRPC command: [%s]", cmd) if ctx.gconfig.get("jsonrpc", "transport") == "socket": command_jsonrpc_socket( ctx, dryrun, ctx.gconfig.get("jsonrpc", "srvaddr"), ctx.gconfig.get("jsonrpc", "rcvaddr"), ctx.gconfig.get("jsonrpc", "outformat"), storepath, command_ctl_name(cmd, "rpc"), params, ) else: command_jsonrpc_fifo( ctx, dryrun, ctx.gconfig.get("jsonrpc", "path"), ctx.gconfig.get("jsonrpc", "rplnamebase"), ctx.gconfig.get("jsonrpc", "outformat"), storepath, command_ctl_name(cmd, "rpc"), params, ) kamcli-3.0.0/kamcli/commands/cmd_moni.py000066400000000000000000000033261514641001400201520ustar00rootroot00000000000000import os import time import click import json from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.command("moni", short_help="Monitor relevant statistics") @click.option( "norefresh", "--no-refresh", is_flag=True, help="Do not refresh (execute once)", ) @pass_context def cli(ctx, norefresh): """Monitor relevant statistics on display \b Parameters: - --no-refresh - execute once Monitored statistics: tm, sl and usrloc """ def clear(): return os.system("tput reset") count = 0 slist = [ "rcv_requests", "fwd_requests", "rcv_replies", "fwd_replies", "free_size", "sent_replies", "tmx:", "usrloc:", ] if norefresh is True: command_ctl( ctx, "stats.get_statistics", slist, {"func": cmd_moni_result_print} ) else: while True: count = count + 1 command_ctl( ctx, "stats.get_statistics", slist, {"func": cmd_moni_result_print}, ) print() print( "[cycle #: " + str(count) + "; if constant make sure server is running]" ) time.sleep(2) clear() def cmd_moni_result_print(ctx, response, params=None): ctx.vlog("formatting the response for command ps") print() rdata = json.loads(response.decode()) if "result" in rdata: rd = rdata["result"] for a, b in zip(rd[::2], rd[1::2]): print("{:<40}{:<}".format(a, b)) else: print(json.dumps(rdata, indent=4, separators=(",", ": "))) kamcli-3.0.0/kamcli/commands/cmd_mtree.py000066400000000000000000000131001514641001400203130ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "mtree", help="Manage mtree module (memory trees)", short_help="Manage mtree module", ) @pass_context def cli(ctx): pass @cli.command("db-add", short_help="Add a new mtree record to database") @click.option( "tname", "--tname", default="", help='Tree name to be stored in column tname (default: "")', ) @click.option( "coltprefix", "--coltprefix", default="tprefix", help='Column name for prefix (default: "tprefix")', ) @click.option( "coltvalue", "--coltvalue", default="tvalue", help='Column name for value (default: "tvalue")', ) @click.argument("dbtname", metavar="") @click.argument("tprefix", metavar="") @click.argument("tvalue", metavar="") @pass_context def mtree_dbadd(ctx, tname, coltprefix, coltvalue, dbtname, tprefix, tvalue): """Add a new tree record in database table \b Parameters: - name of tree database table - tree prefix - associated value for prefix """ ctx.vlog( "Adding to tree [%s] record [%s] => [%s]", dbtname, tprefix, tvalue ) e = create_engine(ctx.gconfig.get("db", "rwurl")) dbname = dbtname.encode("ascii", "ignore").decode() col_pref = coltprefix.encode("ascii", "ignore").decode() col_val = coltvalue.encode("ascii", "ignore").decode() prefix = tprefix.encode("ascii", "ignore").decode() val = tvalue.encode("ascii", "ignore").decode() if not tname: e.execute( "insert into {0} ({1}, {2}) values ({3!r}, {4!r})".format( dbname, col_pref, col_val, prefix, val ) ) else: e.execute( "insert into {0} (tname, {1}, {2}) values " "({3!r}, {4!r}, {5!r})".format( dbname, tname.encode("ascii", "ignore").decode(), col_pref, col_val, prefix, val, ) ) @cli.command("db-rm", short_help="Remove a record from mtree table") @click.option( "coltprefix", "--coltprefix", default="tprefix", help='Column name for prefix (default: "tprefix")', ) @click.option( "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", ) @click.argument("dbtname", metavar="") @click.argument("tprefix", metavar="") @pass_context def mtree_dbrm(ctx, coltprefix, yes, dbtname, tprefix): """Remove a record from tree database table \b Parameters: - name of tree database table - tree prefix value to match the record """ if not yes: print("Removing prefix. Are you sure? (y/n):", end=" ") option = input() if option != "y": ctx.vlog("Skip removing prefix [%s]", tprefix) return e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "delete from {0} where {1}={2!r}".format( dbtname.encode("ascii", "ignore").decode(), coltprefix.encode("ascii", "ignore").decode(), tprefix.encode("ascii", "ignore").decode(), ) ) @cli.command("db-show", short_help="Show mtree records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "coltprefix", "--coltprefix", default="tprefix", help='Column name for prefix (default: "tprefix")', ) @click.argument("dbtname", metavar="") @click.argument("tprefix", nargs=-1, metavar="[]") @pass_context def mtree_dbshow(ctx, oformat, ostyle, coltprefix, dbtname, tprefix): """Show details for records in mtree database table \b Parameters: - name of tree database table - tree prefix value to match the record """ e = create_engine(ctx.gconfig.get("db", "rwurl")) if not tprefix: ctx.vlog("Showing all tree database records") res = e.execute( "select * from {0}".format( dbtname.encode("ascii", "ignore").decode() ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) else: for tp in tprefix: ctx.vlog("Showing tree database records for prefix {0}".format(tp)) res = e.execute( "select * from {0} where {1}={2!r}".format( dbtname.encode("ascii", "ignore").decode(), coltprefix.encode("ascii", "ignore").decode(), tp.encode("ascii", "ignore").decode(), ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("list", short_help="Show the records in memory tree") @click.argument("tname", metavar="") @pass_context def mtree_show(ctx, tname): """Show the tree records in memory \b Parameters: - tree name """ command_ctl(ctx, "mtree.list", [tname]) @cli.command( "reload", short_help="Reload tree records from database into memory" ) @click.argument("tname", metavar="") @pass_context def mtree_reload(ctx, tname): """Reload tree records from database into memory \b Parameters: - tree name """ command_ctl(ctx, "mtree.reload", [tname]) kamcli-3.0.0/kamcli/commands/cmd_pike.py000066400000000000000000000007041514641001400201350ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "pike", help="Manage pike module (source IP tracking)", short_help="Manage pike module", ) @pass_context def cli(ctx): pass @cli.command("list", short_help="Show the details of tracked IP addresses") @pass_context def pike_list(ctx): """Show details of tracked IP addresses \b """ command_ctl(ctx, "pike.list", []) kamcli-3.0.0/kamcli/commands/cmd_ping.py000066400000000000000000000020451514641001400201420ustar00rootroot00000000000000import click import json from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.command("ping", short_help="Send an OPTIONS ping request") @click.option( "nowait", "--nowait", "-n", is_flag=True, help="Do wait for response", ) @click.option("furi", "--furi", default="", help='From URI (default: "")') @click.argument("uri", nargs=1, metavar="[]") @pass_context def cli(ctx, nowait, furi, uri): """Send an OPTIONS ping request Parameters: - - the SIP URI of the target \b """ lcmd = "tm.t_uac_wait_block" if nowait: lcmd = "tm.t_uac_start" lfrom = "" if len(furi) == 0: ldomain = ctx.gconfig.get("main", "domain", fallback="localhost") lfrom = "sip:daemon@" + ldomain else: lfrom = furi plist = [ "OPTIONS", uri, ".", ".", "From: <" + lfrom + ">\r\nTo: <" + uri + ">\r\n Contact: <" + lfrom + ">\r\n", ] command_ctl(ctx, lcmd, plist) kamcli-3.0.0/kamcli/commands/cmd_pipelimit.py000066400000000000000000000126151514641001400212050ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "pipelimit", help="Manage pipelimit module", short_help="Manage pipelimit module", ) @pass_context def cli(ctx): pass @cli.command("db-add", short_help="Add a new pipelimit record to database") @click.option( "dbtname", "--dbtname", default="pl_pipes", help='The name of database table (default: "pl_pipes")', ) @click.option( "alg", "--alg", default="taildrop", help='The name of the algorithm (default: "taildrop")', ) @click.argument("pipeid", metavar="") @click.argument("limit", metavar="", type=click.INT) @pass_context def pipelimit_dbadd(ctx, dbtname, alg, pipeid, limit): """Add a new pipelimit record in database table \b Parameters: - pipe name id - pipe limit """ ctx.vlog( "Adding to pipelimit table [%s] record: [%s] [%s] [%s]", dbtname, pipeid, limit, alg, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) dbtval = dbtname.encode("ascii", "ignore").decode() pidval = pipeid.encode("ascii", "ignore").decode() algval = alg.encode("ascii", "ignore").decode() e.execute( "insert into {0} (pipeid, algorithm, plimit) values ({1!r}, {2!r}, {3})".format( dbtval, pidval, algval, limit ) ) @cli.command("db-show", short_help="Show pipelimit records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "dbtname", "--dbtname", default="pl_pipes", help='The name of database table (default: "pl_pipes")', ) @click.argument("pipeid", nargs=-1, metavar="[]") @pass_context def pipelimit_dbshow(ctx, oformat, ostyle, dbtname, pipeid): """Show details for records in pipelimit database table \b Parameters: - pipe name id (optional) """ e = create_engine(ctx.gconfig.get("db", "rwurl")) if not pipeid: ctx.vlog("Showing all pipelimit database records") res = e.execute( "select * from {0}".format( dbtname.encode("ascii", "ignore").decode() ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) else: for p in pipeid: ctx.vlog( "Showing pipelimit database records for pipeid {0}".format(p) ) res = e.execute( "select * from {0} where pipeid={1!r}".format( dbtname.encode("ascii", "ignore").decode(), p.encode("ascii", "ignore").decode(), ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("db-rm", short_help="Remove a record from pipelimit table") @click.option( "dbtname", "--dbtname", default="pl_pipes", help='The name of database table (default: "pl_pipes")', ) @click.option( "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", ) @click.argument("pipeid", metavar="") @pass_context def pipelimit_dbrm(ctx, dbtname, yes, pipeid): """Remove a record from pipelimit database table \b Parameters: - name of pipelimit database table """ if not yes: print("Removing pipe. Are you sure? (y/n):", end=" ") option = input() if option != "y": ctx.vlog("Skip removing pipe [%s]", pipeid) return e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "delete from {0} where pipeid={1!r}".format( dbtname.encode("ascii", "ignore").decode(), pipeid.encode("ascii", "ignore").decode(), ) ) @cli.command("list", short_help="List the details of one or all pipes") @click.argument("pipeid", nargs=-1, metavar="[]") @pass_context def pipelimit_list(ctx, pipeid): """List the details of one or all pipes \b Parameters: [] - pipe name id """ if not pipeid: command_ctl(ctx, "pl.list", []) else: for p in pipeid: command_ctl(ctx, "pl.list", [p]) @cli.command("stats", short_help="Show pipelimit stats") @pass_context def pipelimit_stats(ctx): """Show pipelimit stats \b """ command_ctl(ctx, "pl.stats", []) @cli.command("set-pipe", short_help="Set pipe algorithm and limit") @click.argument("pipeid", metavar="") @click.argument("alg", metavar="") @click.argument("limit", metavar="", type=click.INT) @pass_context def pipelimit_set_pipe(ctx, pipeid, alg, limit): """Set pipe algorithm and limit \b Parameters: - pipe name id - pipe algorithm - pipe limit """ command_ctl(ctx, "pl.set_pipe", [pipeid, alg, limit]) @cli.command("reset-pipe", short_help="Reset values associated with the pipe") @click.argument("pipeid", metavar="") @pass_context def pipelimit_reset_pipe(ctx, pipeid): """Reset values associated with the pipe \b Parameters: - pipe name id """ command_ctl(ctx, "pl.reset_pipe", [pipeid]) kamcli-3.0.0/kamcli/commands/cmd_pkg.py000066400000000000000000000012031514641001400177610ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "pkg", help="Private memory (pkg) management", short_help="Private memory (pkg) management", ) @pass_context def cli(ctx): pass @cli.command("stats", short_help="Show the stats for pkg memory") @pass_context def pkg_stats(ctx): """Show the stats for pkg memory \b """ command_ctl(ctx, "pkg.stats", []) @cli.command("info", short_help="Show the info for pkg memory manager") @pass_context def pkg_info(ctx): """Show the info for pkg memory manager \b """ command_ctl(ctx, "pkg.info", []) kamcli-3.0.0/kamcli/commands/cmd_ps.py000066400000000000000000000013471514641001400176330ustar00rootroot00000000000000import click import json from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.command("ps", short_help="Print the list of kamailio processes") @pass_context def cli(ctx): """Show details about running kamailio processes \b """ command_ctl(ctx, "core.psx", [], {"func": cmd_ps_result_print}) # callback to print the result of the rpc command def cmd_ps_result_print(ctx, response, params=None): ctx.vlog("formatting the response for command ps") rdata = json.loads(response.decode()) if "result" in rdata: for r in rdata["result"]: ctx.printf("%4d %5d %s", r["IDX"], r["PID"], r["DSC"]) else: print(json.dumps(rdata, indent=4, separators=(",", ": "))) kamcli-3.0.0/kamcli/commands/cmd_pstrap.py000066400000000000000000000052761514641001400205270ustar00rootroot00000000000000import click import os import subprocess import json import datetime from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.command( "pstrap", help="Store runtime details and gdb full backtrace for all Kamailio processes to a file", short_help="Get runtime details and gdb backtrace with ps", ) @click.option( "all", "--all", "-a", is_flag=True, help="Print all details in the trap file", ) @click.option( "sysps", "--sys-ps", "-s", is_flag=True, help="Get the system ps line for each PID", ) @pass_context def cli(ctx, all, sysps): """Store runtime details and gdb full backtrace for all Kamailio processes to a file \b """ ofile = ( "/tmp/gdb_kamailio_" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".txt" ) ctx.printf("Trap file: " + ofile) os.system( 'echo "--- ps output -----------------------------------------------------" >' + ofile ) os.system( "ps auxw | grep kamailio | grep -v grep | grep -v kamcli | sort >>" + ofile ) if all: os.system("echo >>" + ofile) os.system("kamailio -v >>" + ofile) os.system("echo >>" + ofile) os.system("kamailio -I >>" + ofile) ctx.printf("Trapping Kamailio processes with gdb. It can take a while.") child = subprocess.Popen( ["pgrep", "kamailio"], stdout=subprocess.PIPE, shell=False, text=True ) response = child.communicate()[0] if len(response) > 0: for pid in response.split(): ctx.printnlf(".") os.system("echo >>" + ofile) os.system( 'echo "---start ' + pid + ' -----------------------------------------------------" >>' + ofile ) if sysps: os.system( "ps -o pid,ni,pri,pcpu,stat,pmem,rss,vsz,args -w -p " + str(pid) + " >>" + ofile ) os.system( "gdb kamailio " + pid + ' -batch -batch --eval-command="p process_no" --eval-command="p pt[process_no]" --eval-command="bt full" >>' + ofile + " 2>&1" ) os.system( 'echo "---end ' + pid + ' -------------------------------------------------------" >>' + ofile ) else: os.system("echo >>" + ofile) os.system( 'echo "Unable to get the list with PIDs of running Kamailio processes" >>' + ofile ) ctx.printf("") kamcli-3.0.0/kamcli/commands/cmd_rpcmethods.py000066400000000000000000000007341514641001400213600ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.command( "rpcmethods", short_help="Print the list of available raw RPC methods" ) @pass_context def cli(ctx): """Print the list of available raw RPC methods \b Show all available methods that can be executed with command 'rpc'. Examples: - kamcli rpcmethods - kamcli rpc """ command_ctl(ctx, "system.listMethods", []) kamcli-3.0.0/kamcli/commands/cmd_rtpengine.py000066400000000000000000000031171514641001400212010ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "rtpengine", help="Manage rtpengine module", short_help="Manage rtpengine module", ) @pass_context def cli(ctx): pass @cli.command("showdb", short_help="Show the rtpengine records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @pass_context def rtpengine_showdb(ctx, oformat, ostyle): """Show the rtpengine records in database table \b Parameters: none """ e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog("Showing all rtpengine database records") res = e.execute("select * from rtpengine") ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("show", short_help="Show the rtpengine records in memory") @pass_context def rtpengine_show(ctx): """Show the rtpengine records in memory \b Parameters: none """ command_ctl(ctx, "rtpengine.show", ["all"]) @cli.command( "reload", short_help="Reload the rtpengine records from database into memory", ) @pass_context def rtpengine_reload(ctx): """Reload the rtpengine records from database into memory \b Parameters: none """ command_ctl(ctx, "rtpengine.reload", []) kamcli-3.0.0/kamcli/commands/cmd_shell.py000066400000000000000000000361101514641001400203140ustar00rootroot00000000000000## # Adapted from: https://github.com/click-contrib/click-repl # # Copyright (c) 2014-2015 Markus Unterwaditzer & contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in # the Software without restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies # of the Software, and to permit persons to whom the Software is furnished to do # so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # kamcli specific code - Copyright (c) 2020 Daniel-Constantin Mierla # import click import click._bashcomplete import click.parser from kamcli.cli import pass_context from kamcli.iorpc import command_ctl from collections import defaultdict from prompt_toolkit.completion import Completer, Completion from prompt_toolkit.history import InMemoryHistory from prompt_toolkit.shortcuts import prompt from prompt_toolkit.history import FileHistory from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.styles import Style from prompt_toolkit.lexers import PygmentsLexer from pygments.lexers.shell import BashLexer import os import shlex import sys import json import subprocess SHELL_COMMAND_REMAP = {} _ksr_rpc_commands = [] class InternalCommandException(Exception): pass class ExitReplException(InternalCommandException): pass # Handle click.exceptions.Exit introduced in Click 7.0 try: from click.exceptions import Exit as ClickExit except ImportError: class ClickExit(RuntimeError): pass # Dictionary with internal commands of the interactive shell _internal_commands = dict() def _register_internal_command(names, target, description=None): if not hasattr(target, "__call__"): raise ValueError("Internal command must be a callable") if isinstance(names, str): names = [names] elif not isinstance(names, (list, tuple)): raise ValueError('"names" must be a string or a list / tuple') for name in names: _internal_commands[name] = (target, description) def _get_registered_target(name, default=None): target_info = _internal_commands.get(name) if target_info: return target_info[0] return default def _exit_internal(): raise ExitReplException() def _help_internal(): formatter = click.HelpFormatter() formatter.write_heading("Interactive Shell Help") formatter.indent() with formatter.section("Kamcli Commands"): formatter.write_text( "- type the command followed by options and parameters" ) formatter.write_text("- example: db show version") formatter.write_text( "- - completion window with options and subcommands" ) formatter.write_text("- ? - kamcli help") with formatter.section("External Shell Commands"): formatter.write_text('- prefix external commands with "!"') formatter.write_text("- example: !ls -la") with formatter.section("Internal Shell Commands"): formatter.write_text('- prefix internal commands with ":" or "/"') formatter.write_text("- example: :exit") formatter.write_text("- example: /exit") formatter.write_text("- available internal commands:") for mnemonic, target_info in _internal_commands.items(): formatter.write_dl( [(" * " + mnemonic + " -", target_info[1])], col_spacing=2 ) return formatter.getvalue() def _help_cmdmap_internal(): formatter = click.HelpFormatter() formatter.write_heading("Interactive Shell Commands Mapping") formatter.indent() for cmdshort, cmdlong in SHELL_COMMAND_REMAP.items(): formatter.write_dl( [(" * " + cmdshort + " -", cmdlong)], col_spacing=2 ) return formatter.getvalue() _register_internal_command( ["q", "quit", "exit"], _exit_internal, "exit the interactive shell" ) _register_internal_command( ["?", "h", "help"], _help_internal, "displays general help information" ) _register_internal_command( ["c", "cmdmap"], _help_cmdmap_internal, "list command remapping associations", ) class ClickCompleter(Completer): def __init__(self, cli): self.cli = cli def get_completions(self, document, complete_event=None): # Code analogous to click._bashcomplete.do_complete try: args = shlex.split(document.text_before_cursor) except ValueError: # Invalid command, perhaps caused by missing closing quotation. return cursor_within_command = ( document.text_before_cursor.rstrip() == document.text_before_cursor ) if args and cursor_within_command: # We've entered some text and no space, give completions for the # current word. incomplete = args.pop() else: # We've not entered anything, either at all or for the current # command, so give all relevant completions for this context. incomplete = "" ctx = click._bashcomplete.resolve_ctx(self.cli, "", args) if ctx is None: return choices = [] if (len(args) == 1) and (ctx.command.name == "jsonrpc"): for it in _ksr_rpc_commands: choices.append(Completion(str(it), -len(incomplete))) if (len(args) == 2) and (args[0] == "srv") and (args[1] == "rpchelp"): for it in _ksr_rpc_commands: choices.append(Completion(str(it), -len(incomplete))) for param in ctx.command.params: if isinstance(param, click.Option): for options in (param.opts, param.secondary_opts): for o in options: choices.append( Completion( str(o), -len(incomplete), display_meta=param.help, ) ) elif isinstance(param, click.Argument): if isinstance(param.type, click.Choice): for choice in param.type.choices: choices.append( Completion(str(choice), -len(incomplete)) ) if isinstance(ctx.command, click.MultiCommand): for name in ctx.command.list_commands(ctx): command = ctx.command.get_command(ctx, name) choices.append( Completion( str(name), -len(incomplete), display_meta=getattr(command, "short_help"), ) ) for item in choices: if item.text.startswith(incomplete): yield item def bootstrap_prompt(prompt_kwargs, group): """ Bootstrap prompt_toolkit kwargs or use user defined values. :param prompt_kwargs: The user specified prompt kwargs. """ prompt_kwargs = prompt_kwargs or {} defaults = { "history": InMemoryHistory(), "completer": ClickCompleter(group), "message": [("class:prompt", "kamcli > "),], "style": Style.from_dict({"prompt": "bold",}), "auto_suggest": AutoSuggestFromHistory(), } for key in defaults: default_value = defaults[key] if key not in prompt_kwargs: prompt_kwargs[key] = default_value return prompt_kwargs def shell_repl( old_ctx, prompt_kwargs=None, allow_system_commands=True, allow_internal_commands=True, ): """ Start an interactive shell. All subcommands are available in it. :param old_ctx: The current Click context. :param prompt_kwargs: Parameters passed to :py:func:`prompt_toolkit.shortcuts.prompt`. If stdin is not a TTY, no prompt will be printed, but only commands read from stdin. """ # parent should be available, but we're not going to bother if not group_ctx = old_ctx.parent or old_ctx group = group_ctx.command isatty = sys.stdin.isatty() # name of the shell command to skip if executed inside the shell. shell_command_name = old_ctx.command.name prompt_kwargs = bootstrap_prompt(prompt_kwargs, group) if isatty: def get_command(): return prompt(**prompt_kwargs) else: get_command = sys.stdin.readline click.echo("Quick help:") click.echo(" ? - kamcli commands help") click.echo(" :h - internal commands help") click.echo(" :q - quit interactive shell") click.echo(" -- ") while True: try: command = get_command() except KeyboardInterrupt: continue except EOFError: break if not command: if isatty: continue else: break if command in SHELL_COMMAND_REMAP: command = SHELL_COMMAND_REMAP[command] if allow_system_commands and dispatch_repl_commands(command): continue if allow_internal_commands: try: result = handle_internal_commands(command) if isinstance(result, str): click.echo(result) continue except ExitReplException: break try: args = shlex.split(command) except ValueError as e: click.echo("{}: {}".format(type(e).__name__, e)) continue if shell_command_name == args[0]: click.echo( "error: recurrent execution of '{}' subcommand".format( shell_command_name ) ) continue try: with group.make_context(None, args, parent=group_ctx) as ctx: group.invoke(ctx) ctx.exit() except click.ClickException as e: e.show() except ClickExit: pass except SystemExit: pass except ExitReplException: break def exit(): """Exit the repl""" _exit_internal() def dispatch_repl_commands(command): """Execute system commands entered in the repl. System commands are all commands starting with "!". """ if command.startswith("!"): os.system(command[1:]) return True return False def handle_internal_commands(command): """Run repl-internal commands. Repl-internal commands are all commands starting with ":". """ if command.startswith(":") or command.startswith("/"): target = _get_registered_target(command[1:], default=None) if target: return target() @click.command("shell", short_help="Run in interactive shell mode") @click.option( "nohistory", "--no-history", "-N", is_flag=True, help="Do not save commands history", ) @click.option( "nosyntax", "--no-syntax", "-S", is_flag=True, help="Do not enable syntax highlighting for command line", ) @click.option( "noconnect", "--no-connect", "-C", is_flag=True, help="Do not connect to kamailio on starting the shell command", ) @click.option( "norpcautocomplete", "--no-rpc-auto-complete", "-R", is_flag=True, help="Do not fetch RPC commands for command auto-complete", ) @pass_context def cli(ctx, nohistory, nosyntax, noconnect, norpcautocomplete): """Run in shell mode \b """ global _ksr_rpc_commands prompt_kwargs = {} if not noconnect: noconnect = ctx.gconfig.getboolean( "shell", "noconnect", fallback=False ) if not norpcautocomplete: norpcautocomplete = ctx.gconfig.getboolean( "shell", "norpcautocomplete", fallback=False ) if not noconnect: proc = subprocess.Popen( sys.argv[0] + " -F json rpc --no-log core.version", stdout=subprocess.PIPE, shell=True, ) (output, err) = proc.communicate(timeout=10) proc.wait(timeout=10) if err is None and len(output) > 32: jdata = json.loads(output) click.echo("(info) connected to: " + jdata["result"]) proc = subprocess.Popen( sys.argv[0] + " -F json rpc --no-log core.uptime", stdout=subprocess.PIPE, shell=True, ) (output, err) = proc.communicate(timeout=10) proc.wait(timeout=10) if err is None and len(output) > 32: jdata = json.loads(output) uph = int(jdata["result"]["uptime"] / 3600) upm = int((jdata["result"]["uptime"] % 3600) / 60) ups = (jdata["result"]["uptime"] % 3600) % 60 click.echo( "(info) sip server uptime: " + str(uph) + "h " + str(upm) + "m " + str(ups) + "s" ) if not norpcautocomplete: proc = subprocess.Popen( sys.argv[0] + " -F json rpc --no-log system.listMethods", stdout=subprocess.PIPE, shell=True, ) (output, err) = proc.communicate(timeout=10) proc.wait(timeout=10) if err is None and len(output) > 32: jdata = json.loads(output) _ksr_rpc_commands = _ksr_rpc_commands + jdata["result"] else: click.echo( "(info) unable to fetch the list of rpc commands" ) else: click.echo("(info) unable to connect to kamailio") if not nohistory: nohistory = ctx.gconfig.getboolean( "shell", "nohistory", fallback=False ) if not nohistory: dirName = os.path.expanduser("~/.kamcli") if not os.path.exists(dirName): os.mkdir(dirName) click.echo("directory '" + dirName + "' created") prompt_kwargs.update( {"history": FileHistory(os.path.expanduser("~/.kamcli/history"))} ) if not nosyntax: nosyntax = ctx.gconfig.getboolean("shell", "nosyntax", fallback=False) if not nosyntax: prompt_kwargs.update({"lexer": PygmentsLexer(BashLexer)}) if "shell.cmdremap" in ctx._gconfig: SHELL_COMMAND_REMAP.update(ctx._gconfig["shell.cmdremap"]) shell_repl(click.get_current_context(), prompt_kwargs=prompt_kwargs) kamcli-3.0.0/kamcli/commands/cmd_shm.py000066400000000000000000000012531514641001400177740ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "shm", help="Shared memory (shm) management", short_help="Shared memory (shm) management", ) @pass_context def cli(ctx): pass @cli.command("stats", short_help="Show the stats for shared (shm) memory") @pass_context def shm_stats(ctx): """Show the stats for shared (shm) memory \b """ command_ctl(ctx, "shm.stats", []) @cli.command( "info", short_help="Show the info for shared (shm) memory manager" ) @pass_context def shm_info(ctx): """Show the info for shared (shm) memory manager \b """ command_ctl(ctx, "shm.info", []) kamcli-3.0.0/kamcli/commands/cmd_shv.py000066400000000000000000000026331514641001400200100ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "shv", help="Manage $shv(name) variables", short_help="Manage $shv(name) variables", ) @pass_context def cli(ctx): pass @cli.command("get", short_help="Get the value for $shv(name)") @click.argument("name", nargs=-1, metavar="") @pass_context def shv_get(ctx, name): """Get the value for $shv(name) \b Parameters: - the name of shv variable """ if not name: command_ctl(ctx, "pv.shvGet") else: for n in name: command_ctl(ctx, "pv.shvGet", [n]) @cli.command("sets", short_help="Set $shv(name) to string value") @click.argument("name", metavar="") @click.argument("sval", metavar="") @pass_context def shv_sets(ctx, name, sval): """Set $shv(name) to string value \b Parameters: - the name of shv variable - the string value """ command_ctl(ctx, "pv.shvSet", [name, "str", sval]) @cli.command("seti", short_help="Set $shv(name) to int value") @click.argument("name", metavar="") @click.argument("ival", metavar="", type=int) @pass_context def srv_seti(ctx, name, ival): """Set $shv(name) to int value \b Parameters: - the name of shv variable - the int value """ command_ctl(ctx, "pv.shvSet", [name, "int", ival]) kamcli-3.0.0/kamcli/commands/cmd_sipreq.py000066400000000000000000000045121514641001400205110ustar00rootroot00000000000000import click import json from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.command("sipreq", short_help="Send a SIP request via RPC command") @click.option( "nowait", "--nowait", "-n", is_flag=True, help="Do not wait for response", ) @click.option( "method", "--method", "-m", default="OPTIONS", help='SIP method (default: "OPTIONS")', ) @click.option( "furi", "--furi", "-f", default="", help='From URI (default: "")' ) @click.option( "curi", "--curi", "-c", default="", help='Contact URI (default: "")' ) @click.option( "duri", "--duri", "-d", default=".", help='Destination URI (default: "")' ) @click.option( "hdrs", "--hdrs", "-a", default="", help='Additional headers (default: "")' ) @click.option( "socket", "--socket", "-s", default=".", help='Socket for sending (default: "")', ) @click.option( "body", "--body", "-b", default="", help='Destination URI (default: "")' ) @click.argument("uri", nargs=1, metavar="[]") @pass_context def cli(ctx, nowait, method, furi, curi, duri, hdrs, socket, body, uri): """Send a SIP request via RPC command Parameters: - - the SIP URI of the target Note: additional headers must not include From, To or Contact headers, these are generated from the other parameters. If From URI is not provided, then it is generated using local host. The To URI is set to . If Contact URI is not provided, then it is set to From URI. If --curi=none, the Contact header is not added. \b """ lcmd = "tm.t_uac_wait_block" if nowait: lcmd = "tm.t_uac_start" lfrom = "" if len(furi) == 0: ldomain = ctx.gconfig.get("main", "domain", fallback="localhost") lfrom = "sip:daemon@" + ldomain else: lfrom = furi lcuri = lfrom if len(curi) != 0: if curi == "none": lcuri = "" else: lcuri = curi lhdrs = "From: <" + lfrom + ">\r\nTo: <" + uri + ">\r\n" if len(lcuri) != 0: lhdrs += "Contact: <" + lcuri + ">\r\n" if len(hdrs) != 0: lhdrs += hdrs if lhdrs[-2:] != "\r\n": lhdrs += "\r\n" plist = [ method, uri, duri, socket, lhdrs, body, ] command_ctl(ctx, lcmd, plist) kamcli-3.0.0/kamcli/commands/cmd_speeddial.py000066400000000000000000000140221514641001400211350ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.cli import parse_user_spec @click.group( "speeddial", help="Manage speed dial records", short_help="Manage speed dial records", ) @pass_context def cli(ctx): pass @cli.command("add", short_help="Add a speed dial record") @click.option( "table", "--table", default="speed_dial", help="Name of database table (default: speed_dial)", ) @click.option( "fname", "--fname", default="", help='Value for fname column (default: "")', ) @click.option( "lname", "--lname", default="", help='Value for lname column (default: "")', ) @click.argument("userid", metavar="") @click.argument("shortdial", metavar="") @click.argument("targeturi", metavar="") @click.argument("desc", metavar="", nargs=-1) @pass_context def speeddial_add(ctx, table, fname, lname, userid, shortdial, targeturi, desc): """Add a speed dial record \b Parameters: - username, AoR or SIP URI for subscriber - username, AoR or SIP URI for short dial - username, AoR or SIP URI for target [] - description for speed dial record """ udata = parse_user_spec(ctx, userid) sdata = parse_user_spec(ctx, shortdial) tdata = parse_user_spec(ctx, targeturi) ctx.vlog( "Adding for user [%s@%s] short dial [%s@%s] target [sip:%s@%s]", udata["username"], udata["domain"], sdata["username"], sdata["domain"], tdata["username"], tdata["domain"], ) e = create_engine(ctx.gconfig.get("db", "rwurl")) uri = "sip:{}@{}".format(tdata["username"], tdata["domain"]) if not desc: e.execute( "insert into {0} (username, domain, sd_username, " "sd_domain, new_uri, fname, lname) values ({1!r}, {2!r}, {3!r}, " "{4!r}, {5!r}, {6!r}, {7!r})".format( table, udata["username"], udata["domain"], sdata["username"], sdata["domain"], uri, fname, lname, ) ) else: e.execute( "insert into {0} (username, domain, sd_username, " "sd_domain, new_uri, fname, lname, description) values ({1!r}, {2!r}, {3!r}, " "{4!r}, {5!r}, {6!r}, {7!r}, {8!r})".format( table, udata["username"], udata["domain"], sdata["username"], sdata["domain"], uri, fname, lname, desc, ) ) @cli.command("rm", short_help="Remove speed dial records") @click.option( "table", "--table", default="speed_dial", help="Name of database table (default: speed_dial)", ) @click.argument("userid", metavar="") @click.argument("shortdial", metavar="", nargs=-1) @pass_context def speeddial_rm(ctx, table, userid, shortdial): """Remove a user from groups (revoke privilege) \b Parameters: - username, AoR or SIP URI for subscriber - username, AoR or SIP URI for short dial """ udata = parse_user_spec(ctx, userid) ctx.log( "Removing speed dial for record [%s@%s]", udata["username"], udata["domain"], ) e = create_engine(ctx.gconfig.get("db", "rwurl")) if not shortdial: e.execute( "delete from {0} where username={1!r} and domain={2!r}".format( table, udata["username"], udata["domain"], ) ) else: for s in shortdial: sdata = parse_user_spec(ctx, s) e.execute( "delete from {0} where username={1!r} and domain={2!r} " "and sd_username={3!r} and sd_domain={4!r}".format( table, udata["username"], udata["domain"], sdata["username"], sdata["domain"], ) ) @cli.command("show", short_help="Show speed dial records") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.option( "table", "--table", default="speed_dial", help="Name of database table (default: speed_dial)", ) @click.argument("userid", metavar="[]") @click.argument("shortdial", nargs=-1, metavar="[]") @pass_context def speeddial_show(ctx, oformat, ostyle, table, userid, shortdial): """Show details for speed dial records \b Parameters: - username, AoR or SIP URI for user or alias [] - username, AoR or SIP URI for short dial (optional) """ udata = parse_user_spec(ctx, userid) e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog( "Showing speed dial records for user [%s@%s]", udata["username"], udata["domain"], ) if not shortdial: res = e.execute( "select * from {0} where username={1!r} and domain={2!r}".format( table, udata["username"], udata["domain"], ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) else: for s in shortdial: sdata = parse_user_spec(ctx, s) res = e.execute( "select * from {0} where username={1!r} and domain={2!r} " "and sd_username={3!r} and sd_domain={4!r}".format( table, udata["username"], udata["domain"], sdata["username"], sdata["domain"], ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) kamcli-3.0.0/kamcli/commands/cmd_srv.py000066400000000000000000000075431514641001400200270ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl import sys import json import subprocess @click.group("srv", help="Common server interaction commands") @pass_context def cli(ctx): pass @cli.command("sockets", short_help="Show the list of listen sockets") @pass_context def srv_sockets(ctx): """Show the list of listen sockets \b """ command_ctl(ctx, "corex.list_sockets") @cli.command("aliases", short_help="Show the list of server domain aliases") @pass_context def srv_aliases(ctx): """Show the list of server domain aliases \b """ command_ctl(ctx, "corex.list_aliases") @cli.command("rpclist", short_help="Show the list of server rpc commands") @pass_context def srv_rpclist(ctx): """Show the list of server rpc commands \b """ command_ctl(ctx, "system.listMethods") @cli.command("rpchelp", short_help="Show the help text for rpc command") @click.argument("command", metavar="") @pass_context def srv_rpchelp(ctx, command): """Show the help text for rpc command \b """ command_ctl(ctx, "system.methodHelp", [command]) @cli.command("info", short_help="Show server info") @pass_context def srv_info(ctx): """Show server info \b """ command_ctl(ctx, "core.info") @cli.command("modules", short_help="Show server loaded modules") @pass_context def srv_modules(ctx): """Show server loaded modules \b """ command_ctl(ctx, "core.modules") @cli.command("version", short_help="Show server version") @pass_context def srv_version(ctx): """Show server version \b """ command_ctl(ctx, "core.version") @cli.command("ppdefines", short_help="Show pre-processor defines") @click.option( "full", "--full", is_flag=True, help="Show full format of the records." ) @pass_context def srv_ppdefines(ctx, full): """Show pre-processor defines \b """ if full: command_ctl(ctx, "core.ppdefines_full") else: command_ctl(ctx, "core.ppdefines") @cli.command("shm", short_help="Show shared memory details") @pass_context def srv_shm(ctx): """Show shared memory details \b """ command_ctl(ctx, "core.shmmem") @cli.command("debug", short_help="Control debug level of the server") @pass_context @click.argument("level", metavar="", nargs=-1, type=int) def srv_debug(ctx, level): """Control debug level of the server \b Parameters: - new debug level (optional) """ if not level: command_ctl(ctx, "corex.debug") else: command_ctl(ctx, "corex.debug", [level[0]]) @cli.command("runinfo", short_help="Show runtime info") @pass_context def srv_runinfo(ctx): """Show runtime info \b """ command_ctl(ctx, "core.runinfo") @cli.command("rundetails", short_help="Show runtime details") @pass_context def srv_rundetails(ctx): """Show runtime details \b """ proc = subprocess.Popen( sys.argv[0] + " -F json rpc --no-log core.version", stdout=subprocess.PIPE, shell=True, ) (output, err) = proc.communicate(timeout=10) proc.wait(timeout=10) if err is None and len(output) > 32: jdata = json.loads(output) click.echo("running: " + jdata["result"]) proc = subprocess.Popen( sys.argv[0] + " -F json rpc --no-log core.uptime", stdout=subprocess.PIPE, shell=True, ) (output, err) = proc.communicate(timeout=10) proc.wait(timeout=10) if err is None and len(output) > 32: jdata = json.loads(output) uph = int(jdata["result"]["uptime"] / 3600) upm = int((jdata["result"]["uptime"] % 3600) / 60) ups = (jdata["result"]["uptime"] % 3600) % 60 click.echo( "uptime: " + str(uph) + "h " + str(upm) + "m " + str(ups) + "s" ) kamcli-3.0.0/kamcli/commands/cmd_stats.py000066400000000000000000000027401514641001400203450ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.command("stats", short_help="Print internal statistics") @click.option( "single", "--single", "-s", is_flag=True, help="The name belong to one statistic (otherwise the name is " "for a group)", ) @click.option( "number", "--number", "-n", is_flag=True, help="The stats values are retrieved in number format", ) @click.argument("names", nargs=-1, metavar="[]") @pass_context def cli(ctx, single, number, names): """Print internal statistics \b Parameters: - [] - name of statistic or statistics group - if missing, all statistics are printed - it can be a list of names """ rcmd = "stats.fetch" if number: rcmd = "stats.fetchn" if names: for n in names: if n.endswith(":"): # enforce group name by ending with ':' command_ctl(ctx, rcmd, [n]) elif n.find(":") > 0: # get only stat name, when providing 'group:stat' command_ctl(ctx, rcmd, [n.split(":")[1]]) elif single: # single stat name flag command_ctl(ctx, rcmd, [n]) else: # default is group name command_ctl(ctx, rcmd, [n + ":"]) else: # no name, print all command_ctl(ctx, rcmd, ["all"]) kamcli-3.0.0/kamcli/commands/cmd_subscriber.py000066400000000000000000000256251514641001400213610ustar00rootroot00000000000000import click import hashlib from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.cli import parse_user_spec @click.group( "subscriber", help="Manage the subscribers", short_help="Manage the subscribers", ) @pass_context def cli(ctx): pass @cli.command("add", short_help="Add a new subscriber") @click.option( "dbtname", "--dbtname", "-T", default="subscriber", help='Database table name (default: "subscriber")', ) @click.option( "pwtext", "--text-password", "-t", type=click.Choice(["yes", "no"]), default="yes", help="Store password in clear text (default yes)", ) @click.argument("userid", metavar="") @click.argument("password", metavar="") @pass_context def subscriber_add(ctx, dbtname, pwtext, userid, password): """Add a new subscriber \b Parameters: - username, AoR or SIP URI for subscriber - the password """ udata = parse_user_spec(ctx, userid) ctx.vlog( "Adding subscriber [%s] in domain [%s] with password [%s]", udata["username"], udata["domain"], password, ) dig = "{}:{}:{}".format(udata["username"], udata["domain"], password) ha1 = hashlib.md5(dig.encode()).hexdigest() dig = "{}@{}:{}:{}".format( udata["username"], udata["domain"], udata["domain"], password ) ha1b = hashlib.md5(dig.encode()).hexdigest() e = create_engine(ctx.gconfig.get("db", "rwurl")) if pwtext == "yes": e.execute( "insert into {0} (username, domain, password, ha1, ha1b) " "values ({1!r}, {2!r}, {3!r}, {4!r}, {5!r})".format( dbtname.encode("ascii", "ignore").decode(), udata["username"], udata["domain"], password.encode("ascii", "ignore").decode(), ha1, ha1b, ) ) else: e.execute( "insert into {0} (username, domain, ha1, ha1b) values " "({1!r}, {2!r}, {3!r}, {4!r})".format( dbtname.encode("ascii", "ignore").decode(), udata["username"], udata["domain"], ha1, ha1b, ) ) @cli.command("rm", short_help="Remove an existing subscriber") @click.option( "dbtname", "--dbtname", "-T", default="subscriber", help='Database table name (default: "subscriber")', ) @click.option( "yes", "--yes", "-y", is_flag=True, help="Do not ask for confirmation", ) @click.argument("userid", metavar="") @pass_context def subscriber_rm(ctx, dbtname, yes, userid): """Remove an existing subscriber \b Parameters: - username, AoR or SIP URI for subscriber """ if not yes: print("Removing user. Are you sure? (y/n):", end=" ") option = input() if option != "y": ctx.vlog("Skip removing user [%s]", userid) return udata = parse_user_spec(ctx, userid) ctx.log("Removing subscriber [%s@%s]", udata["username"], udata["domain"]) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "delete from {0} where username={1!r} and domain={2!r}".format( dbtname.encode("ascii", "ignore").decode(), udata["username"], udata["domain"], ) ) @cli.command("passwd", short_help="Update the password for a subscriber") @click.option( "dbtname", "--dbtname", "-T", default="subscriber", help='Database table name (default: "subscriber")', ) @click.option( "pwtext", "--text-password", "-t", type=click.Choice(["yes", "no"]), default="yes", help="Store password in clear text (default yes)", ) @click.argument("userid", metavar="") @click.argument("password", metavar="") @pass_context def subscriber_passwd(ctx, dbtname, pwtext, userid, password): """Update password for a subscriber \b Parameters: - username, AoR or SIP URI for subscriber - the password """ udata = parse_user_spec(ctx, userid) ctx.log( "Updating subscriber [%s@%s] with password [%s]", udata["username"], udata["domain"], password, ) dig = "{}:{}:{}".format(udata["username"], udata["domain"], password) ha1 = hashlib.md5(dig.encode()).hexdigest() dig = "{}@{}:{}:{}".format( udata["username"], udata["domain"], udata["domain"], password ) ha1b = hashlib.md5(dig.encode()).hexdigest() e = create_engine(ctx.gconfig.get("db", "rwurl")) if pwtext == "yes": e.execute( "update {0} set password={1!r}, ha1={2!r}, ha1b={3!r} where " "username={4!r} and domain={5!r}".format( dbtname.encode("ascii", "ignore").decode(), password.encode("ascii", "ignore").decode(), ha1, ha1b, udata["username"], udata["domain"], ) ) else: e.execute( "update {0} set ha1={1!r}, ha1b={2!r} where " "username={3!r} and domain={4!r}".format( dbtname, ha1, ha1b, udata["username"], udata["domain"], ) ) @cli.command("show", short_help="Show details for subscribers") @click.option( "dbtname", "--dbtname", "-T", default="subscriber", help='Database table name (default: "subscriber")', ) @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("userid", nargs=-1, metavar="[]") @pass_context def subscriber_show(ctx, dbtname, oformat, ostyle, userid): """Show details for subscribers \b Parameters: [] - username, AoR or SIP URI for subscriber - it can be a list of userids - if not provided then all subscribers are shown """ if not userid: ctx.vlog("Showing all subscribers") e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute( "select * from {0}".format( dbtname.encode("ascii", "ignore").decode() ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) else: for u in userid: udata = parse_user_spec(ctx, u) ctx.vlog( "Showing subscriber [%s@%s]", udata["username"], udata["domain"], ) e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute( "select * from {0} where username={1!r} and domain={2!r}".format( dbtname.encode("ascii", "ignore").decode(), udata["username"], udata["domain"], ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command("setattrs", short_help="Set a string attribute for a subscriber") @click.option( "dbtname", "--dbtname", "-T", default="subscriber", help='Database table name (default: "subscriber")', ) @click.argument("userid", metavar="") @click.argument("attr", metavar="") @click.argument("val", metavar="") @pass_context def subscriber_setattrs(ctx, dbtname, userid, attr, val): """Set a string attribute a subscriber \b Parameters: - username, AoR or SIP URI for subscriber - the name of the attribute (column name in subscriber table) - the value to be set for the attribute """ udata = parse_user_spec(ctx, userid) ctx.log( "Updating subscriber [%s@%s] with str attribute [%s]=[%s]", udata["username"], udata["domain"], attr, val, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "update {0} set {1}={2!r} where username={3!r} and " "domain={4!r}".format( dbtname.encode("ascii", "ignore").decode(), attr.encode("ascii", "ignore").decode(), val.encode("ascii", "ignore").decode(), udata["username"], udata["domain"], ) ) @cli.command( "setattri", short_help="Set an integer attribute for a subscriber" ) @click.option( "dbtname", "--dbtname", "-T", default="subscriber", help='Database table name (default: "subscriber")', ) @click.argument("userid", metavar="") @click.argument("attr", metavar="") @click.argument("val", metavar="") @pass_context def subscriber_setattri(ctx, dbtname, userid, attr, val): """Set an integer attribute a subscriber \b Parameters: - username, AoR or SIP URI for subscriber - the name of the attribute (column name in subscriber table) - the value to be set for the attribute """ udata = parse_user_spec(ctx, userid) ctx.log( "Updating subscriber [%s@%s] with int attribute [%s]=[%s]", udata["username"], udata["domain"], attr, val, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "update {0} set {1}={2} where username={3!r} and " "domain={4!r}".format( dbtname.encode("ascii", "ignore").decode(), attr.encode("ascii", "ignore").decode(), val.encode("ascii", "ignore").decode(), udata["username"], udata["domain"], ) ) @cli.command( "setattrnull", short_help="Set an attribute to NULL for a subscriber" ) @click.option( "dbtname", "--dbtname", "-T", default="subscriber", help='Database table name (default: "subscriber")', ) @click.argument("userid", metavar="") @click.argument("attr", metavar="") @pass_context def subscriber_setattrnull(ctx, dbtname, userid, attr): """Set an attribute to NULL for a subscriber \b Parameters: - username, AoR or SIP URI for subscriber - the name of the attribute (column name in subscriber table) """ udata = parse_user_spec(ctx, userid) ctx.log( "Updating subscriber [%s@%s] with attribute [%s]=NULL", udata["username"], udata["domain"], attr, ) e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "update {0} set {1}=NULL where username={2!r} and " "domain={3!r}".format( dbtname.encode("ascii", "ignore").decode(), attr.encode("ascii", "ignore").decode(), udata["username"], udata["domain"], ) ) kamcli-3.0.0/kamcli/commands/cmd_tcp.py000066400000000000000000000015141514641001400177730ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "tcp", help="Manage TCP options and connections", short_help="Manage TCP options and connections", ) @pass_context def cli(ctx): pass @cli.command("options", short_help="Show details for TCP options in memory") @pass_context def tcp_options(ctx): """Show details for TCP options in memory \b """ command_ctl(ctx, "core.tcp_options", []) @cli.command("list", short_help="List current TCP connections") @pass_context def tcp_list(ctx): """List current TCP connections \b """ command_ctl(ctx, "core.tcp_list", []) @cli.command("info", short_help="Summary of TCP usage") @pass_context def tcp_info(ctx): """Summary of TCP usage \b """ command_ctl(ctx, "core.tcp_info", []) kamcli-3.0.0/kamcli/commands/cmd_tls.py000066400000000000000000000173051514641001400200140ustar00rootroot00000000000000import sys import os import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group("tls", help="Manage tls module") @pass_context def cli(ctx): pass @cli.command("showdb", short_help="Show TLS config records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @pass_context def tls_showdb(ctx, oformat, ostyle): """Show details for records in tlscfg table \b """ e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog("Showing all tlscfg records") res = e.execute("select * from tlscfg") ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command( "cfgprint", short_help="Print TLS config generated from database records" ) @click.option( "odir", "--odir", "-d", default=None, help="Output directory path for certificates content", ) @click.argument("cfgpath", nargs=-1, metavar="[]", type=click.Path()) @pass_context def tls_cfgprint(ctx, odir, cfgpath): """Print TLS config generated from database records \b [] - config file path (optional) """ e = create_engine(ctx.gconfig.get("db", "rwurl")) ctx.vlog("Generating TLS config from database records") res = e.execute("select * from tlscfg") if cfgpath: cfgpath = cfgpath[0] if not odir: if cfgpath: odir = os.path.dirname(str(cfgpath)) bstdout = sys.stdout if cfgpath: cfgsock = open(str(cfgpath), "w") sys.stdout = cfgsock pcount = 0 for row in res: if pcount > 0: print("\n") if ( row["profile_type"] and row["profile_type"].strip() and row["profile_name"] and row["profile_name"].strip() ): print( "[{0:s}:{1:s}]".format( row["profile_type"], row["profile_name"] ) ) if row["method"] and row["method"].strip(): print("method={0:s}".format(row["method"])) print("verify_certificate={0:d}".format(row["verify_certificate"])) print("verify_depth={0:d}".format(row["verify_depth"])) print( "require_certificate={0:d}".format(row["require_certificate"]) ) if row["file_type"] == 0: if row["certificate"] and row["certificate"].strip(): print("certificate={0:s}".format(row["certificate"])) if row["private_key"] and row["private_key"].strip(): print("private_key={0:s}".format(row["private_key"])) if row["ca_list"] and row["ca_list"].strip(): print("ca_list={0:s}".format(row["ca_list"])) if row["crl"] and row["crl"].strip(): print("crl={0:s}".format(row["crl"])) else: if row["certificate"] and row["certificate"].strip(): fpath = os.path.join( odir, "certificate_" + str(row["id"]) + ".pem" ) fout = open(fpath, "w") fout.write(row["certificate"]) fout.close() print("certificate={0:s}".format(fpath)) if row["private_key"] and row["private_key"].strip(): fpath = os.path.join( odir, "private_key_" + str(row["id"]) + ".pem" ) fout = open(fpath, "w") fout.write(row["private_key"]) fout.close() print("private_key={0:s}".format(fpath)) if row["ca_list"] and row["ca_list"].strip(): fpath = os.path.join( odir, "ca_list_" + str(row["id"]) + ".pem" ) fout = open(fpath, "w") fout.write(row["ca_list"]) fout.close() print("ca_list={0:s}".format(fpath)) if row["crl"] and row["crl"].strip(): fpath = os.path.join( odir, "crl_" + str(row["id"]) + ".pem" ) fout = open(fpath, "w") fout.write(row["crl"]) fout.close() print("crl={0:s}".format(fpath)) if row["cipher_list"] and row["cipher_list"].strip(): print("cipher_list={0:s}".format(row["cipher_list"])) if row["server_name"] and row["server_name"].strip(): print("server_name={0:s}".format(row["server_name"])) print("server_name_mode={0:d}".format(row["server_name_mode"])) if row["server_id"] and row["server_id"].strip(): print("server_id={0:s}".format(row["server_id"])) pcount += 1 if cfgpath: sys.stdout = bstdout cfgsock.close() print("done") @cli.command("cfgoptions", short_help="Show details for TLS options in memory") @pass_context def tls_cfgoptions(ctx): """Show details for TLS options in memory \b """ command_ctl(ctx, "tls.options", []) @cli.command("cfgreload", short_help="Reload tls configuration file") @pass_context def tls_cfgreload(ctx): """Reload tls configuration file \b """ command_ctl(ctx, "tls.reload", []) @cli.command("conlist", short_help="List current tls connections") @pass_context def tls_conlist(ctx): """List current tls connections \b """ command_ctl(ctx, "tls.list", []) @cli.command("info", short_help="Summary of tls usage") @pass_context def tls_info(ctx): """Summary of tls usage \b """ command_ctl(ctx, "tls.info", []) @cli.command( "sqlprint", short_help="Print SQL statement to create the db table" ) @pass_context def tls_sqlprint(ctx): """Print SQL statement to create the db table \b """ sqls = """ CREATE TABLE `tlscfg` ( `id` INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL, `profile_type` VARCHAR(64) NOT NULL, `profile_name` VARCHAR(128) NOT NULL, `method` VARCHAR(128), `verify_certificate` INT DEFAULT 0 NOT NULL, `verify_depth` INT DEFAULT 9 NOT NULL, `require_certificate` INT DEFAULT 0 NOT NULL, `cipher_list` VARCHAR(256), `server_name` VARCHAR(128), `server_name_mode` INT DEFAULT 0 NOT NULL, `server_id` VARCHAR(128), `file_type` INT DEFAULT 0 NOT NULL, `certificate` TEXT, `private_key` TEXT, `ca_list` TEXT, `crl` TEXT ); """ print(sqls) @cli.command( "gen-certs", short_help="Generate self signed certificates in current directory", ) @click.option( "domain", "--domain", "-d", default=None, help="Domain of the certificate", ) @click.option( "expiredays", "--expire-days", "-e", default=365, help="Validity of the certificate in days", ) @pass_context def tls_gen_certs(ctx, domain, expiredays): """Generate self signed certificates in current directory \b """ scmd = "" if not domain: scmd = ( "openssl req -x509 -newkey rsa:4096 -nodes -keyout kamailio-selfsigned.key -out kamailio-selfsigned.pem -days {0}" ).format(expiredays) else: scmd = ( 'openssl req -x509 -newkey rsa:4096 -nodes -subj "/CN={0}" -keyout kamailio-selfsigned.key -out kamailio-selfsigned.pem -days {1}' ).format(domain, expiredays) os.system(scmd) kamcli-3.0.0/kamcli/commands/cmd_trap.py000066400000000000000000000113341514641001400201540ustar00rootroot00000000000000import click import os import json import datetime from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.command( "trap", help="Store runtime details and gdb full backtrace for all Kamailio processes to a file", short_help="Get runtime details and gdb full backtrace", ) @click.option( "all", "--all", "-a", is_flag=True, help="Print all details in the trap file", ) @click.option( "norpcps", "--no-rpc-ps", "-P", is_flag=True, help="Skip rpc command to get the list of processes", ) @click.option( "sysps", "--sys-ps", "-s", is_flag=True, help="Get the system ps for each PID returned by RPC ps", ) @pass_context def cli(ctx, all, norpcps, sysps): """Store runtime details and gdb full backtrace for all Kamailio processes to a file \b """ ofile = ( "/tmp/gdb_kamailio_" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".txt" ) if all: command_ctl( ctx, "core.version", [], { "func": cmd_trap_rpc_print, "params": {"ofile": ofile, "otitle": "core.version"}, }, ) command_ctl( ctx, "core.uptime", [], { "func": cmd_trap_rpc_print, "params": {"ofile": ofile, "otitle": "core.uptime"}, }, ) if not norpcps: command_ctl( ctx, "core.psx", [], { "func": cmd_trap_print, "params": {"ofile": ofile, "sysps": sysps}, }, ) # callback to write backtraces to file using the result of an rpc command def cmd_trap_print(ctx, response, params=None): ofile = None sysps = False if params is not None: if "ofile" in params: ofile = params["ofile"] if "sysps" in params: sysps = params["sysps"] if ofile is None: ofile = ( "/tmp/gdb_kamailio_" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".txt" ) ctx.printf("Trap file: " + ofile) with open(ofile, "a") as outfile: outfile.write( "---start core.psx -------------------------------------------------------\n" ) outfile.write(response.decode()) outfile.write( "\n---end core.psx -------------------------------------------------------\n\n" ) rdata = json.loads(response.decode()) if "result" in rdata: ctx.printf( "Trapping " + str(len(rdata["result"])) + " Kamailio processes with gdb. It can take a while." ) for r in rdata["result"]: ctx.printnlf(".") os.system("echo >>" + ofile) os.system( 'echo "---start ' + str(r["PID"]) + ' -----------------------------------------------------" >>' + ofile ) if sysps: os.system( "ps -o pid,ni,pri,pcpu,stat,pmem,rss,vsz,args -w -p " + str(r["PID"]) + " >>" + ofile ) os.system( "gdb kamailio " + str(r["PID"]) + ' -batch --eval-command="bt full" >>' + ofile + " 2>&1" ) os.system( 'echo "---end ' + str(r["PID"]) + ' -------------------------------------------------------" >>' + ofile ) else: os.system("echo >>" + ofile) os.system( 'echo "Unable to get the list with PIDs of running Kamailio processes" >>' + ofile ) ctx.printf("") # callback to print to file the result of an rpc command def cmd_trap_rpc_print(ctx, response, params=None): ofile = None otitle = "SECTION" if params is not None: if "ofile" in params: ofile = params["ofile"] if "otitle" in params: otitle = params["otitle"] olinestart = ( "---start " + otitle + " -------------------------------------------------------\n" ) olineend = ( "\n---end " + otitle + " -------------------------------------------------------\n\n" ) if ofile is None: ofile = ( "/tmp/gdb_kamailio_" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".txt" ) with open(ofile, "a") as outfile: outfile.write(olinestart) outfile.write(response.decode()) outfile.write(olineend) kamcli-3.0.0/kamcli/commands/cmd_uacreg.py000066400000000000000000000140221514641001400204510ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.group( "uacreg", help="Manage uac remote registrations", short_help="Manage uac registrations", ) @pass_context def cli(ctx): pass @cli.command("add", short_help="Add a new remote registration account") @click.option("realm", "--realm", default="", help='Realm (default: "")') @click.option( "authha1", "--auth-ha1", is_flag=True, help="Auth password in HA1 format" ) @click.option( "flags", "--flags", type=int, default=0, help="Flags (default: 0)" ) @click.option( "regdelay", "--reg-delay", type=int, default=0, help="Registration delay (default: 0)", ) @click.option( "socket", "--socket", default="", help='Local socket (default: "")' ) @click.argument("l_uuid", metavar="") @click.argument("l_username", metavar="") @click.argument("l_domain", metavar="") @click.argument("r_username", metavar="") @click.argument("r_domain", metavar="") @click.argument("auth_username", metavar="") @click.argument("auth_password", metavar="") @click.argument("auth_proxy", metavar="") @click.argument("expires", metavar="", type=int) @pass_context def uacreg_add( ctx, realm, authha1, flags, regdelay, socket, l_uuid, l_username, l_domain, r_username, r_domain, auth_username, auth_password, auth_proxy, expires, ): """Add a new uac remote registration account \b Parameters: - local user unique id - local username - local domain - remote username - remote domain - auth username - auth password - auth proxy (sip address) - expires interval (int) """ ctx.vlog( "Adding a new uac remote registration account - local uuid: [%s]", l_uuid, ) pwval = "" ha1val = "" if authha1: ha1val = auth_password else: pwval = auth_password e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "insert into uacreg (l_uuid, l_username, l_domain, r_username, " "r_domain, realm, auth_username, auth_password, auth_ha1, auth_proxy, " "expires, flags, reg_delay, socket) values " "({0!r}, {1!r}, {2!r}, {3!r}, " "{4!r}, {5!r}, {6!r}, {7!r}, {8!r}, {9!r}, " "{10}, {11}, {12}, {13!r})".format( l_uuid.encode("ascii", "ignore").decode(), l_username.encode("ascii", "ignore").decode(), l_domain.encode("ascii", "ignore").decode(), r_username.encode("ascii", "ignore").decode(), r_domain.encode("ascii", "ignore").decode(), realm.encode("ascii", "ignore").decode(), auth_username.encode("ascii", "ignore").decode(), pwval.encode("ascii", "ignore").decode(), ha1val.encode("ascii", "ignore").decode(), auth_proxy.encode("ascii", "ignore").decode(), expires, flags, regdelay, socket.encode("ascii", "ignore").decode(), ) ) @cli.command( "passwd", short_help="Set the password for a remote registration account" ) @click.option( "authha1", "--auth-ha1", is_flag=True, help="Auth password in HA1 format" ) @click.argument("l_uuid", metavar="") @click.argument("auth_password", metavar="") @pass_context def uacreg_passwd(ctx, realm, authha1, l_uuid, auth_password): """Set password for a remote registration account \b Parameters: - local user unique id - auth password """ ctx.vlog( "Adding a new uac remote registration account - local uuid: [%s]", l_uuid, ) pwval = "" ha1val = "" if authha1: ha1val = auth_password else: pwval = auth_password e = create_engine(ctx.gconfig.get("db", "rwurl")) e.execute( "update uacreg set auth_password={0!r}, auth_ha1={1!r} " "where l_uuid={2!r}".format( pwval.encode("ascii", "ignore").decode(), ha1val.encode("ascii", "ignore").decode(), l_uuid.encode("ascii", "ignore").decode(), ) ) @cli.command("showdb", short_help="Show dialplan records in database") @click.option( "oformat", "--output-format", "-F", type=click.Choice(["raw", "json", "table", "dict"]), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("l_uuid", nargs=-1, metavar="[]") @pass_context def uacreg_showdb(ctx, oformat, ostyle, l_uuid): """Show details for records in uacreg database table \b Parameters: [] - local user unique id """ e = create_engine(ctx.gconfig.get("db", "rwurl")) if not l_uuid: ctx.vlog("Showing all uacreg records") res = e.execute("select * from uacreg") ioutils_dbres_print(ctx, oformat, ostyle, res) else: for record in l_uuid: ctx.vlog("Showing uacreg records for l_uuid: " + record) res = e.execute( "select * from uacreg where l_uuid={0!r}".format(record) ) ioutils_dbres_print(ctx, oformat, ostyle, res) @cli.command( "list", short_help="Show details for remote registration records in memory" ) @pass_context def uacreg_list(ctx): """Show details for remote registration records in memory \b """ command_ctl(ctx, "uac.reg_dump", []) @cli.command( "reload", short_help="Reload remote registration records from database into memory", ) @pass_context def uacreg_reload(ctx): """Reload remote registration records from database into memory """ command_ctl(ctx, "uac.reg_reload", []) kamcli-3.0.0/kamcli/commands/cmd_ul.py000066400000000000000000000124531514641001400176310ustar00rootroot00000000000000import click from sqlalchemy import create_engine from kamcli.ioutils import ioutils_dbres_print from kamcli.ioutils import ioutils_formats_list from kamcli.cli import pass_context from kamcli.cli import parse_user_spec from kamcli.iorpc import command_ctl @click.group( "ul", help="Manage user location records", short_help="Manage user location records", ) @pass_context def cli(ctx): pass @cli.command("show", short_help="Show details for location records in memory") @click.option( "brief", "--brief", is_flag=True, help="Show brief format of the records." ) @click.option( "table", "--table", default="location", help="Name of location table (default: location)", ) @click.argument("userid", nargs=-1, metavar="[]") @pass_context def ul_show(ctx, brief, table, userid): """Show details for location records in memory \b Parameters: [] - username, AoR or SIP URI for subscriber - it can be a list of userids - if not provided then all records are shown """ if not userid: ctx.vlog("Showing all records") if brief: command_ctl(ctx, "ul.dump", ["brief"]) else: command_ctl(ctx, "ul.dump", []) else: for u in userid: udata = parse_user_spec(ctx, u) ctx.vlog( "Showing record for [%s@%s]", udata["username"], udata["domain"], ) aor = udata["username"] + "@" + udata["domain"] command_ctl(ctx, "ul.lookup", [table, aor]) @cli.command("add", short_help="Add location record") @click.option( "table", "--table", default="location", help="Name of location table (default: location)", ) @click.option( "expires", "--expires", type=int, default=0, help="Expires value" ) @click.option("qval", "--q", type=float, default=1.0, help="Q value") @click.option("cpath", "--path", default="", help="Path value") @click.option("flags", "--flags", type=int, default=0, help="Flags value") @click.option( "bflags", "--bflags", type=int, default=0, help="Branch flags value" ) @click.option( "methods", "--methods", type=int, default=4294967295, help="Methods value" ) @click.argument("userid", nargs=1, metavar="") @click.argument("curi", nargs=1, metavar="") @pass_context def ul_add( ctx, table, expires, qval, cpath, flags, bflags, methods, userid, curi ): """Add location record \b Parameters: - username, AoR or SIP URI for subscriber - contact SIP URI """ udata = parse_user_spec(ctx, userid) ctx.vlog("Adding record for [%s@%s]", udata["username"], udata["domain"]) aor = udata["username"] + "@" + udata["domain"] command_ctl( ctx, "ul.add", [table, aor, curi, expires, qval, cpath, flags, bflags, methods], ) @cli.command("rm", short_help="Delete location records") @click.option( "table", "--table", default="location", help="Name of location table (default: location)", ) @click.argument("userid", nargs=1, metavar="") @click.argument("curi", nargs=-1, metavar="[]") @pass_context def ul_rm(ctx, table, userid, curi): """Show details for location records in memory \b Parameters: - username, AoR or SIP URI for subscriber [] - contact SIP URI - optional, it can be a list of URIs """ udata = parse_user_spec(ctx, userid) ctx.vlog("Showing record for [%s@%s]", udata["username"], udata["domain"]) aor = udata["username"] + "@" + udata["domain"] if curi: for c in curi: command_ctl(ctx, "ul.rm", [table, aor, c]) else: command_ctl(ctx, "ul.rm", [table, aor]) @cli.command( "showdb", short_help="Show details for location records in database" ) @click.option( "oformat", "--output-format", "-F", type=click.Choice(ioutils_formats_list), default=None, help="Format the output", ) @click.option( "ostyle", "--output-style", "-S", default=None, help="Style of the output (tabulate table format)", ) @click.argument("userid", nargs=-1, metavar="[]") @pass_context def ul_showdb(ctx, oformat, ostyle, userid): """Show details for location records in database \b Parameters: [] - username, AoR or SIP URI for subscriber - it can be a list of userids - if not provided then all records are shown """ if not userid: ctx.vlog("Showing all records") e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute("select * from location") ioutils_dbres_print(ctx, oformat, ostyle, res) else: for u in userid: udata = parse_user_spec(ctx, u) ctx.vlog( "Showing records for [%s@%s]", udata["username"], udata["domain"], ) e = create_engine(ctx.gconfig.get("db", "rwurl")) res = e.execute( "select * from location where username={0!r} and domain={1!r}".format( udata["username"], udata["domain"], ) ) ioutils_dbres_print(ctx, oformat, ostyle, res) kamcli-3.0.0/kamcli/commands/cmd_uptime.py000066400000000000000000000005101514641001400205030ustar00rootroot00000000000000import click from kamcli.cli import pass_context from kamcli.iorpc import command_ctl @click.command("uptime", short_help="Print the uptime for kamailio") @pass_context def cli(ctx): """Print the uptime for kamailio \b Show time details since kamailio was started. """ command_ctl(ctx, "core.uptime", []) kamcli-3.0.0/kamcli/dbutils.py000066400000000000000000000032421514641001400162270ustar00rootroot00000000000000import os from sqlalchemy import create_engine from sqlalchemy.sql import text from sqlalchemy.exc import SQLAlchemyError KDB_IGNORE_MISSING = [ "userblacklist", "userblocklist", ] def dbutils_exec_sqlfile(ctx, sqlengine, fname): if not os.path.exists(fname): for i in KDB_IGNORE_MISSING: if i in fname: return ctx.log( "sql statements file not found [%s]", fname, ) return sql_file = open(fname, "r") sql_command = "" for line in sql_file: if not line.startswith("--") and line.strip("\n"): sql_command += line.strip("\n") if sql_command.endswith(";"): try: sqlengine.execute(text(sql_command)) except SQLAlchemyError: ctx.log( "failed to execute sql statement [%s] from file [%s]", sql_command, fname, ) finally: sql_command = "" def dbutils_exec_sqltext(ctx, sqlengine, sqltext): sql_command = "" for line in sqltext.splitlines(): tline = line.strip(" \t\r\n") if len(tline) > 0 and not tline.startswith("--"): sql_command += " " + tline if sql_command.endswith(";"): try: sqlengine.execute(text(sql_command)) except SQLAlchemyError: ctx.log( "failed to execute sql statements [%s]", sql_command, ) finally: sql_command = "" kamcli-3.0.0/kamcli/iorpc.py000066400000000000000000000325011514641001400156750ustar00rootroot00000000000000import os import os.path import sys import shutil import socket import stat import time import threading import json from random import randint from configparser import NoOptionError ## # enable yaml output format if the lib can be loaded iorpc_yaml_format = True try: import yaml except ImportError: iorpc_yaml_format = False pass # yaml module doesn't exist, deal with it. ## # RPC/MI commands aliases # # "alias" : { # "rpc": "rpc command", # } # # - alias is used inside Python function # - command_ctl(...) will use rpc variant # based on config options COMMAND_NAMES = { "dispatcher.list": { "rpc": "dispatcher.list", }, "dispatcher.reload": { "rpc": "dispatcher.reload", }, "permissions.addressDump": { "rpc": "permissions.addressDump", }, "permissions.addressReload": { "rpc": "permissions.addressReload", }, "permissions.domainDump": { "rpc": "permissions.domainDump", }, "permissions.subnetDump": { "rpc": "permissions.subnetDump", }, "stats.clear_statistics": { "rpc": "stats.clear_statistics", }, "stats.get_statistics": { "rpc": "stats.get_statistics", }, "stats.reset_statistics": { "rpc": "stats.reset_statistics", }, "ul.add": { "rpc": "ul.add", }, "ul.dump": { "rpc": "ul.dump", }, "ul.rm": { "rpc": "ul.rm", }, "ul.lookup": { "rpc": "ul.lookup", }, } def command_ctl_name(alias, ctype): """Return the rpc command name by alias lookup""" v = COMMAND_NAMES.get(alias, None) if v is None: return alias return COMMAND_NAMES[alias]["rpc"] def command_ctl_response_print(response, oformat): """Print the rpc control command response \b Parameters: - response: the jsonrpc response - oformat: output format: * json: json pretty formating * yaml: yaml pretty formating (list like, more compact) * raw output - just print the response """ print() if oformat == "json": print( json.dumps( json.loads(response.decode()), indent=4, separators=(",", ": ") ) ) elif oformat == "yaml": if iorpc_yaml_format is True: print( yaml.safe_dump( json.loads(response.decode()), default_flow_style=False ) ) else: print( json.dumps( json.loads(response.decode()), indent=4, separators=(",", ": "), ) ) else: print(response) def command_ctl_response(ctx, response, oformat, cbexec={}): """Process a rpc control command response""" if not cbexec: command_ctl_response_print(response, oformat) else: if "func" in cbexec: if "params" in cbexec: cbexec["func"](ctx, response, cbexec["params"]) else: cbexec["func"](ctx, response) else: ctx.log("invalid callback structure - function is missing") class IOFifoThread(threading.Thread): """Thread to listen on a reply fifo file""" def __init__(self, ctx, rplpath, oformat, cbexec={}): threading.Thread.__init__(self) self.ctx = ctx self.rplpath = rplpath self.oformat = oformat self.cbexec = cbexec self.stop_signal = False def run(self): self.ctx.vlog("Starting to wait for reply on: " + self.rplpath) r = os.open(self.rplpath, os.O_RDONLY | os.O_NONBLOCK) scount = 0 rcount = 0 wcount = 0 rdata = "" while not self.stop_signal: rbuf = os.read(r, 4096).decode() if rbuf == "": if rcount != 0: wcount += 1 if wcount == 8: break time.sleep(0.100) else: scount += 1 if scount == 50: break time.sleep(0.100) else: rcount += 1 wcount = 0 rdata += rbuf if rcount == 0: self.ctx.vlog("timeout - nothing read") else: command_ctl_response(self.ctx, rdata, self.oformat, self.cbexec) ## # { # "jsonrpc": "2.0", # "method": "command", # "params": [p1, p2, p3], # "reply_name": "kamailio_jsonrpc_reply_fifo", # "id": 1 # } # def command_jsonrpc_fifo( ctx, dryrun, sndpath, rcvname, oformat, storepath, cmd, params=[], cbexec={}, ): scmd = '{\n "jsonrpc": "2.0",\n "method": "' + cmd + '",\n' if params: scmd += ' "params": [' comma = 0 for p in params: if comma == 1: scmd += ",\n" else: comma = 1 if type(p) is int: scmd += str(p) elif type(p) is float: scmd += str(p) else: if p.startswith("i:"): scmd += p[2:] elif p.startswith("s:"): scmd += '"' + p[2:] + '"' else: scmd += '"' + p + '"' scmd += "],\n" scmd += ' "reply_name": "' + rcvname + '",\n' if storepath and len(storepath) > 0: scmd += ' "store_path": "' + storepath + '",\n' scmd += ' "id": ' + str(randint(2, 10000)) + "\n" scmd += "}\n" if dryrun: print(json.dumps(json.loads(scmd), indent=4, separators=(",", ": "))) return rcvpath = ctx.gconfig.get("jsonrpc", "rpldir") + "/" + rcvname if os.path.exists(rcvpath): if stat.S_ISFIFO(os.stat(rcvpath).st_mode): os.unlink(rcvpath) else: ctx.log("File with same name as reply fifo exists") sys.exit() os.mkfifo(rcvpath, 0o666) os.chmod(rcvpath, 0o666) try: shutil.chown(rcvpath, group=ctx.gconfig.get("jsonrpc", "kamgroup")) except NoOptionError: pass # create new thread to read from reply fifo tiofifo = IOFifoThread(ctx, rcvpath, oformat) # start new threadd tiofifo.start() w = os.open(sndpath, os.O_WRONLY) os.write(w, scmd.encode()) waitrun = True while waitrun: try: tiofifo.join(500) if not tiofifo.is_alive(): waitrun = False break except KeyboardInterrupt: ctx.log("Ctrl-c received! Sending kill to threads...") tiofifo.stop_signal = True os.unlink(rcvpath) ## # # { # "jsonrpc": "2.0", # "method": "command", # "params": [p1, p2, p3], # "id": 1 # } def command_jsonrpc_socket( ctx, dryrun, srvaddr, rcvaddr, oformat, storepath, cmd, params=[], cbexec={}, ): scmd = '{\n "jsonrpc": "2.0",\n "method": "' + cmd + '",\n' if params: scmd += ' "params": [' comma = 0 for p in params: if comma == 1: scmd += ",\n" else: comma = 1 if type(p) in [int, float]: scmd += str(p) else: if p.startswith("i:"): scmd += p[2:] elif p.startswith("s:"): scmd += '"' + p[2:] + '"' else: scmd += '"' + p + '"' scmd += "],\n" if storepath and len(storepath) > 0: scmd += ' "store_path": "' + storepath + '",\n' scmd += ' "id": ' + str(randint(2, 10000)) + "\n" scmd += "}\n" if dryrun: print(json.dumps(json.loads(scmd), indent=4, separators=(",", ": "))) return sockclient = None response = None socktype = "IPv4" host = None port = None if srvaddr.startswith("udp:"): ctx.vlog("udp socket provided: " + srvaddr) sproto, saddr = srvaddr.split(":", 1) if saddr.find("[", 0, 2) == -1: ctx.vlog("IPv4 socket address") host, port = saddr.split(":") else: ctx.vlog("IPv6 socket address") ehost, port = saddr.rsplit(":", 1) host = ehost.strip("[]") socktype = "IPv6" # create datagram udp socket try: if socktype == "IPv6": sockclient = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) else: sockclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sockclient.settimeout(4.0) sockclient.sendto(scmd.encode(), (host, int(port))) # receive the response (content, sockserver) data = sockclient.recvfrom(84000) response = data[0] # sockserver = data[1] ctx.vlog("Server response: " + response.decode()) except socket.timeout: ctx.log("Timeout receiving response on udp socket") sys.exit() except socket.error as emsg: ctx.log("Error udp sock: " + str(emsg[0]) + " - " + emsg[1]) sys.exit() elif srvaddr.startswith("tcp:"): ctx.vlog("tcp socket provided: " + srvaddr) sproto, saddr = srvaddr.split(":", 1) if saddr.find("[", 0, 2) == -1: ctx.vlog("IPv4 socket address") host, port = saddr.split(":") else: ctx.vlog("IPv6 socket address") ehost, port = saddr.rsplit(":", 1) host = ehost.strip("[]") socktype = "IPv6" # create datagram udp socket try: if socktype == "IPv6": sockclient = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) else: sockclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sockclient.settimeout(4.0) sockclient.connect((host, int(port))) sockclient.sendall(scmd.encode()) # receive the response (content, sockserver) response = sockclient.recv(84000) ctx.vlog("Server response: " + response.decode()) except socket.timeout: ctx.log("Timeout receiving response on tcp socket") sys.exit() except socket.error as emsg: ctx.log("Error tcp sock: " + str(emsg[0]) + " - " + emsg[1]) sys.exit() else: ctx.vlog("unix socket provided: " + srvaddr) if not os.path.exists(srvaddr): ctx.vlog("server unix socket file not found") ctx.vlog( "be sure kamailio is running and listening on: " + srvaddr ) return # create datagram udp socket try: sockclient = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) sockclient.settimeout(4.0) rcvaddr = rcvaddr + "." + str(os.getpid()) ctx.vlog("unix socket reply: " + rcvaddr) sockclient.bind(rcvaddr) os.chmod(rcvaddr, 0o666) try: shutil.chown( rcvaddr, group=ctx.gconfig.get("jsonrpc", "kamgroup") ) except NoOptionError: pass # sockclient.connect( srvaddr ) # sockclient.send( scmd ) sockclient.sendto(scmd.encode(), srvaddr) # receive the response (content, sockserver) response = sockclient.recv(84000) sockclient.close() os.remove(rcvaddr) ctx.vlog("Server response: " + response.decode()) except socket.timeout: ctx.log("Timeout receiving response on unix sock") sockclient.close() os.remove(rcvaddr) sys.exit() except socket.error as emsg: ctx.log("Error unix sock: " + str(emsg[0]) + " - " + emsg[1]) sockclient.close() os.remove(rcvaddr) sys.exit() if response is None: ctx.vlog("timeout - nothing read") else: command_ctl_response(ctx, response, oformat, cbexec) def command_ctl(ctx, cmd, params=[], cbexec={}): """Execute a rpc control command \b Parameters: - ctx: kamcli execution context - cmd: the string with rpc control command - params: an array with the parameters for the rpc control command - cbexec: dictionary with callaback function and its parameters to handle the response of the rpc control commands. If not provided, the response will be printed with the function command_ctl_response_print(). The callback function has to be provided by 'func' key in the dictionary and its parameters by 'params' key. """ if ctx.gconfig.get("jsonrpc", "transport") == "socket": command_jsonrpc_socket( ctx, False, ctx.gconfig.get("jsonrpc", "srvaddr"), ctx.gconfig.get("jsonrpc", "rcvaddr"), ctx.gconfig.get("jsonrpc", "outformat"), "", command_ctl_name(cmd, "rpc"), params, cbexec, ) else: command_jsonrpc_fifo( ctx, False, ctx.gconfig.get("jsonrpc", "path"), ctx.gconfig.get("jsonrpc", "rplnamebase"), ctx.gconfig.get("jsonrpc", "outformat"), "", command_ctl_name(cmd, "rpc"), params, cbexec, ) kamcli-3.0.0/kamcli/ioutils.py000066400000000000000000000061221514641001400162510ustar00rootroot00000000000000import sys import json # import pprint ioutils_tabulate_format = True try: from tabulate import tabulate except ImportError: ioutils_tabulate_format = False pass # module doesn't exist, deal with it. ## # enable yaml output format if the lib can be loaded ioutils_yaml_format = True try: import yaml except ImportError: ioutils_yaml_format = False pass # yaml module doesn't exist, deal with it. ioutils_formats_list = ["raw", "json", "table", "dict", "yaml"] def ioutils_dbres_print(ctx, oformat, ostyle, res): """print a database result using different formats and styles""" if oformat is None: oformat = ctx.gconfig.get("db", "outformat", fallback=None) if oformat is None: if ioutils_tabulate_format is True: oformat = "table" else: oformat = "json" if oformat == "table": if ioutils_tabulate_format is False: ctx.log("Package tabulate is not installed") sys.exit() if oformat == "yaml": if ioutils_yaml_format is False: ctx.log("Package yaml is not installed") sys.exit() if ostyle is None: ostyle = ctx.gconfig.get("db", "outstyle", fallback="grid") if ostyle is None: ostyle = "grid" if oformat == "json": jdata = [] for row in res: jdata.append(dict(row)) print(json.dumps(jdata, indent=4)) print() elif oformat == "yaml": ydata = [] for row in res: ydata.append(dict(row)) print(yaml.dump(ydata, indent=4)) print() elif oformat == "dict": for row in res: print(dict(row)) # pprint.pprint(dict(row), indent=4) print() elif oformat == "table": arows = res.fetchall() dcols = dict((k, k) for k in res.keys()) drows = [dict(r) for r in arows] gstring = tabulate(drows, headers=dcols, tablefmt=ostyle) print(gstring) else: allrows = res.fetchall() print(allrows) def ioutils_dict_print(ctx, oformat, ostyle, res): """print a dictionary using different formats and styles""" if oformat is None: oformat = "json" if oformat == "table": if ioutils_tabulate_format is False: ctx.log("Package tabulate is not installed") sys.exit() if oformat == "yaml": if ioutils_yaml_format is False: ctx.log("Package yaml is not installed") sys.exit() if ostyle is None: ostyle = "grid" if oformat == "json": jdata = [] jdata.append(res) print(json.dumps(jdata, indent=4)) print() elif oformat == "yaml": ydata = [] ydata.append(res) print(yaml.dump(ydata, indent=4)) print() elif oformat == "dict": print(res) # pprint.pprint(dict(row), indent=4) print() elif oformat == "table": gstring = tabulate([res.values()], headers=res.keys(), tablefmt=ostyle) print(gstring) else: print(res) print() kamcli-3.0.0/kamcli/kamcli.ini000066400000000000000000000103431514641001400161500ustar00rootroot00000000000000### main options [main] ; SIP domain to be used when an AoR has no domain domain=kamailio.org ### subcommand aliases [cmdaliases] # alias = subcommand # - 'kamcli alias ...' becomes equivalent of 'kamcli subcommand ...' mt = mtree pl = pipelimit sd = speeddial ### database connectivity - URLs are used for SQL Alchemy [db] ; type of database ; - for MySQL: mysql, ; - for PostgreSQL: postgresql ; - for SQLite: sqlite type=mysql ; driver to be used fro connecting ; - for MySQL: mysqldb ; - for PostgreSQL: psycopg2 ; - for SQLite: pysqlite driver=mysqldb ; host of database server host=localhost ; port of database server ; - not enforced - see rwurl, rourl, adminurl ; - for MySQL: 3306 ; - for PostgreSQL: 5432 dbport=3306 ; kamailio database name for SQL server backends dbname=kamailio ; kamailio database path for SQL file backends (e.g., sqlite) dbpath=/etc/kamailio/kamailio.db ; read/write user rwuser=kamailio ; password for read/write user rwpassword=kamailiorw ; read only user rouser=kamailioro ; password for read only user ropassword=kamailioro ; admin user adminuser=root ; password for admin user adminpassword= ; database URLs ; - built using above attributes, don't change unless you know what you do ; - full format for SQL server backends (mysql, postgres, ...): ; rwurl=%(type)s+%(driver)s://%(rwuser)s:%(rwpassword)s@%(host)s:%(dbport)s/%(dbname)s ; rourl=%(type)s+%(driver)s://%(rouser)s:%(ropassword)s@%(host)s:%(dbport)s/%(dbname)s ; adminurl=%(type)s+%(driver)s://%(adminuser)s:%(adminpassword)s@%(host)s:%(dbport)s ; - full format for SQL file backends (sqlite, ...): ; rwurl=%(type)s+%(driver)s:///%(dbpath)s ; rourl=%(type)s+%(driver)s:///%(dbpath)s ; adminurl=%(type)s+%(driver)s:///%(dbpath)s rwurl=%(type)s+%(driver)s://%(rwuser)s:%(rwpassword)s@%(host)s/%(dbname)s rourl=%(type)s+%(driver)s://%(rouser)s:%(ropassword)s@%(host)s/%(dbname)s adminurl=%(type)s+%(driver)s://%(adminuser)s:%(adminpassword)s@%(host)s ; host from where kamcli is used accesshost= ; path to the folder with SQL scripts for creating database tables ; - used by `db create` subcommand if not provided via `-s` cli argument ; - example value for mysql: /usr/local/share/kamailio/mysql ; - example value for postgresql: /usr/local/share/kamailio/postgres ; - example value for sqlite: /usr/local/share/kamailio/db_sqlite scriptsdirectory=/usr/local/share/kamailio/mysql ; outformat - the format to print database result ; - can be: table, json, yaml, dict or raw outformat=table ; outstyle - the style to print database result with tabulate package ; - default: grid # outstyle=grid ### control tool settings [ctl] ; type - can be: jsonrpc type=jsonrpc ; kamgroup - group of the running kamailio server process kamgroup=kamailio ### jsonrpc settings [jsonrpc] ; transport - can be: fifo, socket transport=socket ; path - where kamailio is listening for JSONRPC FIFO commands path=/var/run/kamailio/kamailio_rpc.fifo rplnamebase=kamailio_rpc_reply.fifo rpldir=/tmp ; srvaddr - where kamailio is listening for JSONRPC socket commands ; - it has to be a path to unix socket file, udp:ipaddr:port ; or tcp:ipaddr:port srvaddr=/var/run/kamailio/kamailio_rpc.sock ; srvaddr=udp:127.0.0.1:9062 ; srvaddr=tcp:127.0.0.1:9062 ; rcvaddr - where kamcli is listening for the JSONRPC responses ; - it has to be a path to unix socket file or udp:ipaddr:port ; - pid of kamcli is added at the end to allow multiple use at same time rcvaddr=/var/run/kamailio/kamailio_rpc_reply.sock ; rcvaddr=udp:127.0.0.1:9064 ; outformat - the format to print RPC result ; - can be: json, yaml or raw ; - yaml is more compact output outformat=yaml ### internal cmd shell settings [shell] ; do not connect to Kamailio on start up (yes|no) # noconnect=yes ; do not fetch RPC commands on start up for auto-complete (yes|no) ; - done only if 'noconnect=no' # norpcautocomplete=yes ; do not track history of commands (yes|no) # nohistory=yes ; do not enable syntax higlighting for shell command line (yes|no) # nosyntax=yes ### command re-mapping for cmd shell # - short name for full command with parameters [shell.cmdremap] dv=db show "version" u=uptime ### apiban settings [apiban] ; key - the APIBan key # key=abcde... ; htname - htable name (if not set, defaults to 'ipban') # htname=ipban kamcli-3.0.0/pkg/000077500000000000000000000000001514641001400135275ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/000077500000000000000000000000001514641001400142615ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/bionic/000077500000000000000000000000001514641001400155245ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/bionic/changelog000066400000000000000000000010471514641001400174000ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/bionic/compat000066400000000000000000000000021514641001400167220ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/bionic/control000066400000000000000000000012401514641001400171240ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/bionic/copyright000066400000000000000000000022341514641001400174600ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/bionic/kamcli.examples000066400000000000000000000000221514641001400205160ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/bionic/rules000077500000000000000000000001511514641001400166010ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/bookworm/000077500000000000000000000000001514641001400161205ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/bookworm/changelog000066400000000000000000000010471514641001400177740ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/bookworm/compat000066400000000000000000000000021514641001400173160ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/bookworm/control000066400000000000000000000012401514641001400175200ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/bookworm/copyright000066400000000000000000000022341514641001400200540ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/bookworm/kamcli.examples000066400000000000000000000000221514641001400211120ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/bookworm/rules000077500000000000000000000001511514641001400171750ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/bullseye/000077500000000000000000000000001514641001400161055ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/bullseye/changelog000066400000000000000000000010471514641001400177610ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/bullseye/compat000066400000000000000000000000021514641001400173030ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/bullseye/control000066400000000000000000000012401514641001400175050ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/bullseye/copyright000066400000000000000000000022341514641001400200410ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/bullseye/kamcli.examples000066400000000000000000000000221514641001400210770ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/bullseye/rules000077500000000000000000000001511514641001400171620ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/buster/000077500000000000000000000000001514641001400155655ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/buster/changelog000066400000000000000000000010471514641001400174410ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/buster/compat000066400000000000000000000000021514641001400167630ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/buster/control000066400000000000000000000012401514641001400171650ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/buster/copyright000066400000000000000000000022341514641001400175210ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/buster/kamcli.examples000066400000000000000000000000221514641001400205570ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/buster/rules000077500000000000000000000001511514641001400166420ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/debian/000077500000000000000000000000001514641001400155035ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/debian/backports/000077500000000000000000000000001514641001400174735ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/debian/backports/bionic000077500000000000000000000002661514641001400206700ustar00rootroot00000000000000#!/bin/bash # # Target dist: Ubuntu bionic DIST=bionic rm -rf ${DIST} cp -r debian ${DIST} wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/backports/bookworm000077500000000000000000000002721514641001400212610ustar00rootroot00000000000000#!/bin/bash # # Target dist: Debian bookworm DIST=bookworm rm -rf ${DIST} cp -r debian ${DIST} wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/backports/bullseye000077500000000000000000000002721514641001400212460ustar00rootroot00000000000000#!/bin/bash # # Target dist: Debian bullseye DIST=bullseye rm -rf ${DIST} cp -r debian ${DIST} wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/backports/buster000077500000000000000000000002661514641001400207310ustar00rootroot00000000000000#!/bin/bash # # Target dist: Debian buster DIST=buster rm -rf ${DIST} cp -r debian ${DIST} wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/backports/focal000077500000000000000000000002641514641001400205070ustar00rootroot00000000000000#!/bin/bash # # Target dist: Ubuntu focal DIST=focal rm -rf ${DIST} cp -r debian ${DIST} wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/backports/jammy000077500000000000000000000002641514641001400205400ustar00rootroot00000000000000#!/bin/bash # # Target dist: Ubuntu jammy DIST=jammy rm -rf ${DIST} cp -r debian ${DIST} wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/backports/noble000077500000000000000000000002641514641001400205220ustar00rootroot00000000000000#!/bin/bash # # Target dist: Ubuntu noble DIST=noble rm -rf ${DIST} cp -r debian ${DIST} wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/backports/stretch000077500000000000000000000002701514641001400210740ustar00rootroot00000000000000#!/bin/bash # # Target dist: Debian stretch DIST=stretch rm -rf ${DIST} cp -r debian ${DIST} wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/backports/trixie000077500000000000000000000002661514641001400207310ustar00rootroot00000000000000#!/bin/bash # # Target dist: Debian trixie DIST=trixie rm -rf ${DIST} cp -r debian ${DIST} wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/backports/xenial000077500000000000000000000003541514641001400207030ustar00rootroot00000000000000#!/bin/bash # # Target dist: Ubuntu xenial DIST=xenial rm -rf ${DIST} cp -r debian ${DIST} echo "pyaml python-yaml" >> ${DIST}/pydist-overrides wrap-and-sort -sat -d ${DIST} # clean backports scripts rm -rf ${DIST}/backports exit 0 kamcli-3.0.0/pkg/deb/debian/changelog000066400000000000000000000010471514641001400173570ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/debian/compat000066400000000000000000000000021514641001400167010ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/debian/control000066400000000000000000000012401514641001400171030ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/debian/copyright000066400000000000000000000022341514641001400174370ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/debian/kamcli.examples000066400000000000000000000000221514641001400204750ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/debian/rules000077500000000000000000000001511514641001400165600ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/focal/000077500000000000000000000000001514641001400153455ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/focal/changelog000066400000000000000000000010471514641001400172210ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/focal/compat000066400000000000000000000000021514641001400165430ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/focal/control000066400000000000000000000012401514641001400167450ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/focal/copyright000066400000000000000000000022341514641001400173010ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/focal/kamcli.examples000066400000000000000000000000221514641001400203370ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/focal/rules000077500000000000000000000001511514641001400164220ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/jammy/000077500000000000000000000000001514641001400153765ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/jammy/changelog000066400000000000000000000010471514641001400172520ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/jammy/compat000066400000000000000000000000021514641001400165740ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/jammy/control000066400000000000000000000012401514641001400167760ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/jammy/copyright000066400000000000000000000022341514641001400173320ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/jammy/kamcli.examples000066400000000000000000000000221514641001400203700ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/jammy/rules000077500000000000000000000001511514641001400164530ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/noble/000077500000000000000000000000001514641001400153605ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/noble/changelog000066400000000000000000000010471514641001400172340ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/noble/compat000066400000000000000000000000021514641001400165560ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/noble/control000066400000000000000000000012401514641001400167600ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/noble/copyright000066400000000000000000000022341514641001400173140ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/noble/kamcli.examples000066400000000000000000000000221514641001400203520ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/noble/rules000077500000000000000000000001511514641001400164350ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/stretch/000077500000000000000000000000001514641001400157355ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/stretch/changelog000066400000000000000000000010471514641001400176110ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/stretch/compat000066400000000000000000000000021514641001400171330ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/stretch/control000066400000000000000000000012401514641001400173350ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/stretch/copyright000066400000000000000000000022341514641001400176710ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/stretch/kamcli.examples000066400000000000000000000000221514641001400207270ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/stretch/rules000077500000000000000000000001511514641001400170120ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/trixie/000077500000000000000000000000001514641001400155655ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/trixie/changelog000066400000000000000000000010471514641001400174410ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/trixie/compat000066400000000000000000000000021514641001400167630ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/trixie/control000066400000000000000000000012401514641001400171650ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/trixie/copyright000066400000000000000000000022341514641001400175210ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/trixie/kamcli.examples000066400000000000000000000000221514641001400205570ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/trixie/rules000077500000000000000000000001511514641001400166420ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pkg/deb/xenial/000077500000000000000000000000001514641001400155415ustar00rootroot00000000000000kamcli-3.0.0/pkg/deb/xenial/changelog000066400000000000000000000010471514641001400174150ustar00rootroot00000000000000kamcli (3.0.0~dev0) unstable; urgency=medium * devel version -- Victor Seva Wed, 02 Oct 2019 15:54:12 +0200 kamcli (2.0.0) unstable; urgency=medium * Official release -- Victor Seva Wed, 02 Oct 2019 14:24:24 +0200 kamcli (1.1.0) unstable; urgency=medium * Official release -- Victor Seva Tue, 16 Oct 2018 10:10:19 +0200 kamcli (1.1.0~dev0) unstable; urgency=medium * Initial release (Closes: #910283) -- Victor Seva Sat, 29 Sep 2018 11:22:00 +0200 kamcli-3.0.0/pkg/deb/xenial/compat000066400000000000000000000000021514641001400167370ustar00rootroot000000000000009 kamcli-3.0.0/pkg/deb/xenial/control000066400000000000000000000012401514641001400171410ustar00rootroot00000000000000Source: kamcli Maintainer: Victor Seva Section: misc Priority: optional X-Python3-Version: >= 3.2 Standards-Version: 4.2.1.1 Build-Depends: debhelper (>= 9~), dh-python, python3-all, python3-setuptools, Package: kamcli Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, Description: Kamailio Command Line Interface Control Tool kamcli is aiming at being a modern and extensible alternative to the shell script kamctl. . It requires that jsonrpcs module of Kamailio is loaded and configured to listen on an Unix domain socket or FIFO file. The way to interact with Kamailio has to be set inside kamcli config file (kamcli.ini). kamcli-3.0.0/pkg/deb/xenial/copyright000066400000000000000000000022341514641001400174750ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: kam-cli Upstream-Contact: Daniel-Constantin Mierla Source: https://github.com/kamailio/kamcli Files: * Copyright: 2015-2019 Daniel-Constantin Mierla License: GPL-2 Files: pkg/deb/* Copyright: 2018-2019 Victor Seva License: GPL-2 License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA X-Comment: On Debian systems, the complete text of the GNU Library General Public License version 2 can be found in `/usr/share/common-licenses/LGPL-2'. kamcli-3.0.0/pkg/deb/xenial/kamcli.examples000066400000000000000000000000221514641001400205330ustar00rootroot00000000000000kamcli/kamcli.ini kamcli-3.0.0/pkg/deb/xenial/pydist-overrides000066400000000000000000000000221514641001400207720ustar00rootroot00000000000000pyaml python-yaml kamcli-3.0.0/pkg/deb/xenial/rules000077500000000000000000000001511514641001400166160ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh "$@" --with python3 --buildsystem=pybuild kamcli-3.0.0/pyproject.toml000066400000000000000000000003751514641001400156670ustar00rootroot00000000000000[tool.black] target-version = ['py37'] line-length = 79 include = '\.pyi?$' exclude = ''' /( \.git | \.tox )/ ''' [tool.tox] legacy_tox_ini = ''' [tox] envlist = style [testenv:style] deps = pre-commit commands = pre-commit run --all-files ''' kamcli-3.0.0/requirements/000077500000000000000000000000001514641001400154715ustar00rootroot00000000000000kamcli-3.0.0/requirements/base.txt000066400000000000000000000001741514641001400171460ustar00rootroot00000000000000Click~=7.0 prompt-toolkit>=3.0.2 pyaml>=19.4.1 PyYAML>=5.1.2 Pygments>=2.6.0 SQLAlchemy<2.0.0 tabulate>=0.8.5 wheel>=0.35.0 kamcli-3.0.0/requirements/requirements.txt000066400000000000000000000000161514641001400207520ustar00rootroot00000000000000-r ./base.txt kamcli-3.0.0/requirements/requirements_dev.txt000066400000000000000000000000321514641001400216060ustar00rootroot00000000000000-r ./base.txt pre-commit kamcli-3.0.0/setup.py000066400000000000000000000006571514641001400144700ustar00rootroot00000000000000from setuptools import setup setup( name="kamcli", version="3.0.0", packages=["kamcli", "kamcli.commands"], include_package_data=True, install_requires=[ "setuptools", "click", "prompt-toolkit", "pyaml", "pygments", "sqlalchemy", "tabulate", "wheel", ], entry_points=""" [console_scripts] kamcli=kamcli.cli:cli """, )