pax_global_header 0000666 0000000 0000000 00000000064 13545125701 0014515 g ustar 00root root 0000000 0000000 52 comment=5300cc869f5a7c1779fd6fe6e1279511043ee93d
kamcli-2.0.0/ 0000775 0000000 0000000 00000000000 13545125701 0012754 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/.gitignore 0000664 0000000 0000000 00000001433 13545125701 0014745 0 ustar 00root root 0000000 0000000 # 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/
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-2.0.0/ChangeLog 0000664 0000000 0000000 00000000614 13545125701 0014527 0 ustar 00root root 0000000 0000000 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-2.0.0/LICENSE 0000664 0000000 0000000 00000043177 13545125701 0013775 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
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.
{description}
Copyright (C) {year} {fullname}
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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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.
{signature of Ty Coon}, 1 April 1989
Ty Coon, 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-2.0.0/README.md 0000664 0000000 0000000 00000020637 13545125701 0014243 0 ustar 00root root 0000000 0000000 ## 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 or FIFO file. The way to interact with Kamailio has to be set
inside `kamcli` config file (kamcli.ini).
### Important Note ###
The master branch of `kamcli` is under ongoing work to upgrade the code to work with Python3.
Migration to Python3 is required because Python2 has been deprecated and it is going to be
removed from major Linux distributions.
Therefore, for the moment, not all commands may work with Python3, if you find any problem,
file a report on Issue Tracker.
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.
### Features
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:
* **address** - manage permissions address records
* **aliasdb** - manage database aliases
* **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)
* **domain** - manage domain records
* **group** - manage group membership records (acl)
* **moni** - continuous refresh of the values for a list of statistics
* **mtree** - manage memory trees (mtree)
* **ps** - print the details for kamailio running processes
* **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
* **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
* **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)
* python3-pip
* python3-dev (optional - needed to install mysqlclient via pip)
* python3-venv (optional - needed to install virtual environment)
PIP Packages (install via pip3):
* _extra packages requied by kamcli (part of OS or virtual environment)_
* click
* sqlalchemy
* mysql-python (optional - needed if you want to connect to MySQL database)
* tabulate (optional - needed if you want to have table-formatted output for various commands)
* pyaml (optional - needed if you want to print json result as yaml (more compact))
#### Install Virtual Environment
It is recommended to install in a virtual environment at least for development.
Some useful details about installing Click in virtual environament are
available at:
* http://click.pocoo.org/4/quickstart/#virtualenv
For example, create the virtual environemnt 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 --editable .
```
The *pip3 install* command installs the dependencies appart of the
database connector module needed on top of sqlalchemy. You need to
install your wanted database module -- e.g., for MySQL use pip3 to
install *mysqlclient*.
To deactivate the virtual environment, run:
```
$ deactivate
```
#### Install on Debian
Should work 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-venv python3-dev
mkdir kamclienv
cd kamclienv
python3 -m venv venv
. venv/bin/activate
pip3 install click
pip3 install sqlalchemy
pip3 install mysqlclient
pip3 install tabulate
pip3 install pyaml
git clone https://github.com/kamailio/kamcli.git
cd kamcli
pip3 install --editable .
```
To see if all was installed properly, run:
```
kamcli --help
```
### Configuration File
Kamcli uses a configuration file with INI format. The name is kamcli.ini and it looks for it in:
* /etc/kamcli/kamcli.ini
* ~/.kamcli/kamcli.ini
* the value of --config command line parameter
The installation process doesn't deploy the configuration file yet.
A sample kamailio.ini is available in sources, at `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
```
#### Examples of Commands
Sample commands to understand quicker the capabilities and how to use it:
```
kamcli -d --config=kamcli/kamcli.ini --help
kamcli --config=kamcli/kamcli.ini subscriber show
kamcli --config=kamcli/kamcli.ini subscriber add test test00
kamcli --config=kamcli/kamcli.ini subscriber show test
kamcli --config=kamcli/kamcli.ini subscriber show --help
kamcli -d --config=kamcli/kamcli.ini subscriber passwd test01 test10
kamcli -d --config=kamcli/kamcli.ini subscriber add -t no test02 test02
kamcli -d --config=kamcli/kamcli.ini subscriber setattrs test01 rpid +123
kamcli -d --config=kamcli/kamcli.ini subscriber setattrnull test01 rpid
kamcli -d --config=kamcli/kamcli.ini jsonrpc --help
kamcli -d --config=kamcli/kamcli.ini jsonrpc core.psx
kamcli -d --config=kamcli/kamcli.ini jsonrpc system.listMethods
kamcli -d --config=kamcli/kamcli.ini jsonrpc stats.get_statistics
kamcli -d --config=kamcli/kamcli.ini jsonrpc stats.get_statistics all
kamcli -d --config=kamcli/kamcli.ini jsonrpc stats.get_statistics shmem:
kamcli -d --config=kamcli/kamcli.ini jsonrpc --dry-run system.listMethods
kamcli -d --config=kamcli/kamcli.ini config raw
kamcli -d --config=kamcli/kamcli.ini config show main db
kamcli -d --config=kamcli/kamcli.ini --no-default-configs config show main db
kamcli -d --config=kamcli/kamcli.ini db connect
kamcli -d --config=kamcli/kamcli.ini db show -F table version
kamcli -d --config=kamcli/kamcli.ini db show -F json subscriber
kamcli -d --config=kamcli/kamcli.ini db showcreate version
kamcli -d --config=kamcli/kamcli.ini db showcreate -F table version
kamcli -d --config=kamcli/kamcli.ini db showcreate -F table -S html version
kamcli -d --config=kamcli/kamcli.ini db clirun "describe version"
kamcli -d --config=kamcli/kamcli.ini db clishow version
kamcli -d --config=kamcli/kamcli.ini db clishowg subscriber
kamcli -d --config=kamcli/kamcli.ini ul showdb
kamcli -d --config=kamcli/kamcli.ini ul show
kamcli -d --config=kamcli/kamcli.ini ul rm test
kamcli -d --config=kamcli/kamcli.ini ul add test sip:test@127.0.0.1
kamcli -d --config=kamcli/kamcli.ini stats
kamcli -d --config=kamcli/kamcli.ini stats usrloc
kamcli -d --config=kamcli/kamcli.ini stats -s registered_users
kamcli -d --config=kamcli/kamcli.ini stats usrloc:registered_users
```
### Kamailio Configuration
It requires to load the `jsnorpcs` 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)
```
### License
GPLv2
Copyright: asipto.com
kamcli-2.0.0/docs/ 0000775 0000000 0000000 00000000000 13545125701 0013704 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/docs/Devel.md 0000664 0000000 0000000 00000003652 13545125701 0015273 0 ustar 00root root 0000000 0000000 ## KAMCLI
Kamailio Command Line Interface Control Tool
### Development Guidelines
#### Indentation
* user 4 whitespaces (no tabs) for indentation
#### 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-2.0.0/kamcli/ 0000775 0000000 0000000 00000000000 13545125701 0014214 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/kamcli/__init__.py 0000664 0000000 0000000 00000000000 13545125701 0016313 0 ustar 00root root 0000000 0000000 kamcli-2.0.0/kamcli/cli.py 0000664 0000000 0000000 00000012160 13545125701 0015335 0 ustar 00root root 0000000 0000000 import os
import sys
import click
try:
import ConfigParser as configparser
except ImportError:
import configparser
def read_global_config(config_paths):
"""Get config."""
parser = configparser.SafeConfigParser()
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')
COMMAND_ALIASES = {
"subs": "subscriber",
"rpc": "jsonrpc",
}
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)
@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
def global_read_config(ctx, param, value):
"""Callback that is used whenever --config is passed. We use this to
always load the correct config. This means that the config is loaded
even if the group itself never executes so our aliases stay always
available.
"""
if value is None:
value = os.path.join(os.path.dirname(__file__), 'kamcli.ini')
ctx.read_config(value)
return value
@click.command(cls=KamCLI, context_settings=CONTEXT_SETTINGS,
short_help='Kamailio command line interface control tool')
@click.option('--wdir', type=click.Path(exists=True, file_okay=False,
resolve_path=True),
help='Working directory.')
@click.option('--debug', '-d', is_flag=True,
help='Enable debug mode.')
@click.option('--config', '-c',
default=None, help="Configuration file.")
@click.option('nodefaultconfigs', '--no-default-configs', is_flag=True,
help='Skip loading default configuration files.')
@click.version_option()
@pass_context
def cli(ctx, debug, wdir, config, nodefaultconfigs):
"""Kamailio command line interface control tool.
\b
Help per command: kamcli --help
\b
Default configuration files:
- /etc/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("/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))
kamcli-2.0.0/kamcli/commands/ 0000775 0000000 0000000 00000000000 13545125701 0016015 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/kamcli/commands/__init__.py 0000664 0000000 0000000 00000000000 13545125701 0020114 0 ustar 00root root 0000000 0000000 kamcli-2.0.0/kamcli/commands/cmd_address.py 0000664 0000000 0000000 00000011337 13545125701 0020644 0 ustar 00root root 0000000 0000000 import click
import hashlib
import json
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
from kamcli.iorpc import command_ctl
##
#
#
@click.group('address', 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'))
if not mask:
if not port:
e.execute('delete from address where grp={0} and ip_addr={1!r}'.format(group, address.encode('ascii','ignore').decode()))
else:
e.execute('delete from address where grp={0} and ip_addr={1!r} and port={2}'.format(group, address.encode('ascii','ignore').decode(), port))
else:
if not port:
e.execute('delete from address where grp={0} and ip_addr={1!r} and mask={2}'.format(group, address.encode('ascii','ignore').decode(), mask))
else:
e.execute('delete from address where setid={0} and destination={1!r} and mask={2} and port={3}'.format(group, address.encode('ascii','ignore').decode(), 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=%d', group)
ioutils_dbres_print(ctx, oformat, ostyle, res)
##
#
#
@cli.command('list', short_help='Show details for address records in memory')
@click.option('tag', '--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-2.0.0/kamcli/commands/cmd_aliasdb.py 0000664 0000000 0000000 00000011264 13545125701 0020615 0 ustar 00root root 0000000 0000000 import 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')
@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 ' + table + ' (username, domain, alias_username, alias_domain) values (%s, %s, %s, %s)',
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 ' + table + ' where alias_username=%s and alias_domain=%s',
udata['username'], udata['domain'])
else:
e.execute('delete from ' + table + ' where username=%s and domain=%s',
udata['username'], udata['domain'])
else:
for a in aliasid:
adata = parse_user_spec(ctx, a)
e.execute('delete from ' + table + ' where username=%s and domain=%s and alias_username=%s and alias_domain=%s',
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 ' + 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 ' + table + ' where alias_username=%s and alias_domain=%s',
udata['username'], udata['domain'])
else:
ctx.vlog('Showing records for user [%s@%s]', udata['username'], udata['domain'])
res = e.execute('select * from ' + table + ' where username=%s and domain=%s',
udata['username'], udata['domain'])
ioutils_dbres_print(ctx, oformat, ostyle, res)
kamcli-2.0.0/kamcli/commands/cmd_config.py 0000664 0000000 0000000 00000002447 13545125701 0020466 0 ustar 00root root 0000000 0000000 import os
import sys
import click
from kamcli.cli import pass_context
from kamcli.cli import COMMAND_ALIASES
@click.group('config', 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(COMMAND_ALIASES)
print() kamcli-2.0.0/kamcli/commands/cmd_db.py 0000664 0000000 0000000 00000012432 13545125701 0017601 0 ustar 00root root 0000000 0000000 import os
import sys
import click
import hashlib
import pprint
import json
from sqlalchemy import create_engine
from sqlalchemy.schema import CreateTable
from kamcli.cli import pass_context
from kamcli.cli import parse_user_spec
from kamcli.ioutils import ioutils_dbres_print
from kamcli.ioutils import ioutils_formats_list
##
#
#
@click.group('db', help='Raw database operations')
@pass_context
def cli(ctx):
pass
##
#
#
@cli.command('connect', 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 == "postgres":
ctx.log("unsupported database type [%s]", dbtype)
sys.exit()
else:
ctx.log("unsupported database type [%s]", dbtype)
sys.exit()
os.system(scmd)
##
#
#
@cli.command('clirun', 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 == "postgres":
ctx.log("unsupported database type [%s]", dbtype)
sys.exit()
else:
ctx.log("unsupported database type [%s]", dbtype)
sys.exit()
os.system(scmd)
##
#
#
@cli.command('clishow', 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 == "postgres":
ctx.log("unsupported database type [%s]", dbtype)
sys.exit()
else:
ctx.log("unsupported database type [%s]", dbtype)
sys.exit()
os.system(scmd)
##
#
#
@cli.command('clishowg', 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 = '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 == "postgres":
ctx.log("unsupported database type [%s]", dbtype)
sys.exit()
else:
ctx.log("unsupported database type [%s]", dbtype)
sys.exit()
os.system(scmd)
##
#
#
@cli.command('show', 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', 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_showcreate(ctx, oformat, ostyle, table):
ctx.vlog('Show create of database table [%s]', table)
e = create_engine(ctx.gconfig.get('db', 'rwurl'))
res = e.execute('show create table {0}'.format(table))
ioutils_dbres_print(ctx, oformat, ostyle, res)
##
#
#
def db_engine_exec_file(ctx, sqlengine, fname):
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))
sqlengine.commit()
except:
ctx.log("failed to execute sql statements from file [%s]", fname)
finally:
sql_command = ''
##
#
#
@cli.command('runfile', 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'))
db_engine_exec_file(ctx, e, fname)
kamcli-2.0.0/kamcli/commands/cmd_dialog.py 0000664 0000000 0000000 00000004577 13545125701 0020466 0 ustar 00root root 0000000 0000000 import 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
from kamcli.iorpc import command_ctl
##
#
#
@click.group('dialog', help='Manage dialog module (active calls tracking)')
@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('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_list(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-2.0.0/kamcli/commands/cmd_dialplan.py 0000664 0000000 0000000 00000011635 13545125701 0021004 0 ustar 00root root 0000000 0000000 import click
import json
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
from kamcli.iorpc import command_ctl
##
#
#
@click.group('dialplan', help='Manage dialplan module (regexp translations)')
@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 dispatcher_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=%d', 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 dispatcher_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 dispatcher_list(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-2.0.0/kamcli/commands/cmd_dispatcher.py 0000664 0000000 0000000 00000007356 13545125701 0021353 0 ustar 00root root 0000000 0000000 import 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
from kamcli.iorpc import command_ctl
##
#
#
@click.group('dispatcher', help='Manage dispatcher module (load balancer)')
@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=%d', 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', [ ])
kamcli-2.0.0/kamcli/commands/cmd_domain.py 0000664 0000000 0000000 00000005312 13545125701 0020462 0 ustar 00root root 0000000 0000000 import 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
from kamcli.iorpc import command_ctl
##
#
#
@click.group('domain', help='Manage domain module (multi-domain records)')
@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="%s"', 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-2.0.0/kamcli/commands/cmd_group.py 0000664 0000000 0000000 00000006216 13545125701 0020353 0 ustar 00root root 0000000 0000000 import 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')
@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 (%s, %s, %s)', 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=%s and domain=%s', udata['username'], udata['domain'])
else:
e.execute('delete from grp where username=%s and domain=%s and grp=%s', 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=%s and domain=%s', udata['username'], udata['domain'])
ioutils_dbres_print(ctx, oformat, ostyle, res)
kamcli-2.0.0/kamcli/commands/cmd_jsonrpc.py 0000664 0000000 0000000 00000003510 13545125701 0020667 0 ustar 00root root 0000000 0000000 import os
import sys
import 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 command')
@click.option('dryrun', '--dry-run', is_flag=True,
help='Do not execute the command, only print it')
@click.argument('cmd', nargs=1, metavar='[]')
@click.argument('params', nargs=-1, metavar='[]')
@pass_context
def cli(ctx, dryrun, 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:
- jsonrpc system.listMethods
- jsonrpc core.psx
- jsonrpc stats.get_statistics all
- jsonrpc pv.shvSet counter i:123
"""
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'),
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'),
command_ctl_name(cmd, 'rpc'), params)
kamcli-2.0.0/kamcli/commands/cmd_moni.py 0000664 0000000 0000000 00000002754 13545125701 0020164 0 ustar 00root root 0000000 0000000 import 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
"""
clear = lambda : 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)
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-2.0.0/kamcli/commands/cmd_mtree.py 0000664 0000000 0000000 00000011472 13545125701 0020333 0 ustar 00root root 0000000 0000000 import click
import hashlib
import json
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
from kamcli.iorpc import command_ctl
##
#
#
@click.group('mtree', help='Manage mtree module (memory trees)')
@pass_context
def cli(ctx):
pass
##
#
#
@cli.command('add', short_help='Add a new mtree record')
@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_add(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'))
if not tname:
e.execute('insert into {0!r} ({1!r}, {2!r}) values ({3!r}, {4!r})'.format(dbtname.encode('ascii','ignore').decode(), coltprefix.encode('ascii','ignore').decode(), coltvalue.encode('ascii','ignore').decode(), tprefix.encode('ascii','ignore').decode(), tvalue.encode('ascii','ignore').decode()))
else:
e.execute('insert into {0!r} (tname, {1!r}, {2!r}) values ({3!r}, {4!r}, {5!r})'.format(dbtname.encode('ascii','ignore').decode(), tname.encode('ascii','ignore').decode(), coltprefix.encode('ascii','ignore').decode(), coltvalue.encode('ascii','ignore').decode(), tprefix.encode('ascii','ignore').decode(), tvalue.encode('ascii','ignore').decode()))
##
#
#
@cli.command('rm', short_help='Remove a record from mtree table')
@click.option('coltprefix', '--coltprefix', default='tprefix',
help='Column name for prefix (default: "tprefix")')
@click.argument('dbtname', metavar='')
@click.argument('tprefix', metavar='')
@pass_context
def mtree_rm(ctx, coltprefix, dbtname, tprefix):
"""Remove a record from tree database table
\b
Parameters:
- name of tree database table
- tree prefix value to match the record
"""
e = create_engine(ctx.gconfig.get('db', 'rwurl'))
e.execute('delete from {0!r} where {1!r}={2!r}'.format(dbtname.encode('ascii','ignore').decode(), coltprefix.encode('ascii','ignore').decode(), tprefix.encode('ascii','ignore').decode()))
##
#
#
@cli.command('showdb', 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_showdb(ctx, oformat, ostyle, coltprefix, dbtable, 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!r}'.format(dbtname.encode('ascii','ignore')))
else:
ctx.vlog('Showing tree database records for prefix')
res = e.execute('select * from {0!r} where {1!r}={2!r}'.format(dbtname.encode('ascii','ignore').decode(), coltprefix.encode('ascii','ignore').decode(), tprefix.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-2.0.0/kamcli/commands/cmd_ps.py 0000664 0000000 0000000 00000001365 13545125701 0017641 0 ustar 00root root 0000000 0000000 import sys
import 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)
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-2.0.0/kamcli/commands/cmd_rpcmethods.py 0000664 0000000 0000000 00000000763 13545125701 0021370 0 ustar 00root root 0000000 0000000 import os
import time
import 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-2.0.0/kamcli/commands/cmd_rtpengine.py 0000664 0000000 0000000 00000003160 13545125701 0021205 0 ustar 00root root 0000000 0000000 import click
import hashlib
import json
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
from kamcli.iorpc import command_ctl
##
#
#
@click.group('rtpengine', 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-2.0.0/kamcli/commands/cmd_speeddial.py 0000664 0000000 0000000 00000011507 13545125701 0021150 0 ustar 00root root 0000000 0000000 import 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')
@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.argument('userid', metavar='')
@click.argument('shortdial', metavar='')
@click.argument('targeturi', metavar='')
@click.argument('desc', metavar='', nargs=-1)
@pass_context
def speeddial_add(ctx, table, 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'))
if not desc:
e.execute('insert into ' + table + ' (username, domain, sd_username, sd_domain, new_uri) values (%s, %s, %s, %s, %s)',
udata['username'], udata['domain'], sdata['username'], sdata['domain'],
'sip:' + tdata['username'] + '@' + tdata['domain'])
else:
e.execute('insert into ' + table + ' (username, domain, sd_username, sd_domain, new_uri, description) values (%s, %s, %s, %s, %s, %s)',
udata['username'], udata['domain'], sdata['username'], sdata['domain'],
'sip:' + tdata['username'] + '@' + tdata['domain'], 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 ' + table + ' where username=%s and domain=%s',
udata['username'], udata['domain'])
else:
for s in shortdial:
sdata = parse_user_spec(ctx, s)
e.execute('delete from ' + table + ' where username=%s and domain=%s and sd_username=%s and sd_domain=%s',
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 ' + table + ' where username=%s and domain=%s',
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 ' + table + ' where username=%s and domain=%s and sd_username=%s and sd_domain=%s',
udata['username'], udata['domain'], sdata['username'], sdata['domain'])
ioutils_dbres_print(ctx, oformat, ostyle, res)
kamcli-2.0.0/kamcli/commands/cmd_srv.py 0000664 0000000 0000000 00000002561 13545125701 0020030 0 ustar 00root root 0000000 0000000 import click
from kamcli.cli import pass_context
from kamcli.iorpc import command_ctl
##
#
#
@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('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_info(ctx):
"""Show server loaded modules
\b
"""
command_ctl(ctx, 'core.modules')
##
#
#
@cli.command('version', short_help='Show server version')
@pass_context
def srv_info(ctx):
"""Show server version
\b
"""
command_ctl(ctx, 'core.version') kamcli-2.0.0/kamcli/commands/cmd_stats.py 0000664 0000000 0000000 00000002532 13545125701 0020352 0 ustar 00root root 0000000 0000000 import 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.argument('names', nargs=-1, metavar='[]')
@pass_context
def cli(ctx, single, 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
"""
if names:
for n in names:
if n.endswith(":"):
# enforce group name by ending with ':'
command_ctl(ctx, 'stats.get_statistics', [ n ])
elif n.find(":")>0:
# get only stat name, when providing 'group:stat'
command_ctl(ctx, 'stats.get_statistics', [ n.split(":")[1] ])
elif single:
# single stat name flag
command_ctl(ctx, 'stats.get_statistics', [ n ])
else:
# default is group name
command_ctl(ctx, 'stats.get_statistics', [ n+":" ])
else:
# no name, print all
command_ctl(ctx, 'stats.get_statistics', [ 'all' ])
kamcli-2.0.0/kamcli/commands/cmd_subscriber.py 0000664 0000000 0000000 00000017122 13545125701 0021360 0 ustar 00root root 0000000 0000000 import click
import hashlib
import json
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')
@pass_context
def cli(ctx):
pass
##
#
#
@cli.command('add', short_help='Add a new 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, 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)
ha1 = hashlib.md5((udata['username'] + ":" + udata['domain'] + ":" + password).encode()).hexdigest()
ha1b = hashlib.md5((udata['username'] + "@" + udata['domain'] + ":" + udata['domain'] + ":" + password).encode()).hexdigest()
e = create_engine(ctx.gconfig.get('db', 'rwurl'))
if pwtext == 'yes' :
e.execute('insert into subscriber (username, domain, password, ha1, ha1b) values (%s, %s, %s, %s, %s)', udata['username'], udata['domain'], password, ha1, ha1b)
else:
e.execute('insert into subscriber (username, domain, ha1, ha1b) values (%s, %s, %s, %s)', udata['username'], udata['domain'], ha1, ha1b)
##
#
#
@cli.command('rm', short_help='Remove an existing subscriber')
@click.argument('userid', metavar='')
@pass_context
def subscriber_rm(ctx, userid):
"""Remove an existing subscriber
\b
Parameters:
- username, AoR or SIP URI for subscriber
"""
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 subscriber where username=%s and domain=%s', udata['username'], udata['domain'])
##
#
#
@cli.command('passwd', short_help='Update the password for a 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, 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)
ha1 = hashlib.md5((udata['username'] + ":" + udata['domain'] + ":" + password).encode()).hexdigest()
ha1b = hashlib.md5((udata['username'] + "@" + udata['domain'] + ":" + udata['domain'] + ":" + password).encode()).hexdigest()
e = create_engine(ctx.gconfig.get('db', 'rwurl'))
if pwtext == 'yes' :
e.execute('update subscriber set password=%s, ha1=%s, ha1b=%s where username=%s and domain=%s', password, ha1, ha1b, udata['username'], udata['domain'])
else:
e.execute('update subscriber set ha1=%s, ha1b=%s where username=%s and domain=%s', ha1, ha1b, udata['username'], udata['domain'])
##
#
#
@cli.command('show', short_help='Show details for subscribers')
@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, 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 subscriber')
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 subscriber where username=%s and domain=%s', udata['username'], udata['domain'])
ioutils_dbres_print(ctx, oformat, ostyle, res)
##
#
#
@cli.command('setattrs', short_help='Set a string attribute for a subscriber')
@click.argument('userid', metavar='')
@click.argument('attr', metavar='')
@click.argument('val', metavar='')
@pass_context
def subscriber_setattrs(ctx, 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 subscriber set {0}={1!r} where username={2!r} and domain={3!r}'.format(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.argument('userid', metavar='')
@click.argument('attr', metavar='')
@click.argument('val', metavar='')
@pass_context
def subscriber_setattri(ctx, 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 subscriber set {0}={1} where username={2!r} and domain={3!r}'.format(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.argument('userid', metavar='')
@click.argument('attr', metavar='')
@pass_context
def subscriber_setattrnull(ctx, 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 subscriber set {0}=NULL where username={1!r} and domain={2!r}'.format(attr.encode('ascii','ignore').decode(), udata['username'], udata['domain']))
kamcli-2.0.0/kamcli/commands/cmd_tcp.py 0000664 0000000 0000000 00000001453 13545125701 0020003 0 ustar 00root root 0000000 0000000 import click
from kamcli.cli import pass_context
from kamcli.iorpc import command_ctl
##
#
#
@click.group('tcp', 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-2.0.0/kamcli/commands/cmd_tls.py 0000664 0000000 0000000 00000015024 13545125701 0020016 0 ustar 00root root 0000000 0000000 import 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)
kamcli-2.0.0/kamcli/commands/cmd_uacreg.py 0000664 0000000 0000000 00000013063 13545125701 0020463 0 ustar 00root root 0000000 0000000 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('uacreg', help='Manage uac remote registration')
@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 dispatcher_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 l in l_uuid:
ctx.vlog('Showing uacreg records for l_uuid: ' + l)
res = e.execute('select * from uacreg where l_uuid="%s"', l)
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-2.0.0/kamcli/commands/cmd_ul.py 0000664 0000000 0000000 00000012120 13545125701 0017626 0 ustar 00root root 0000000 0000000 import 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')
@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=%s and domain=%s', udata['username'], udata['domain'])
ioutils_dbres_print(ctx, oformat, ostyle, res)
kamcli-2.0.0/kamcli/commands/cmd_uptime.py 0000664 0000000 0000000 00000000517 13545125701 0020520 0 ustar 00root root 0000000 0000000 import 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-2.0.0/kamcli/iorpc.py 0000664 0000000 0000000 00000026050 13545125701 0015705 0 ustar 00root root 0000000 0000000 import os
import os.path
import sys
import socket
import stat
import time
import threading
import json
from random import randint
##
# enable yaml output format if the lib can be loaded
iorpc_yaml_format = True
try:
import yaml
except ImportError as e:
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 == 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), indent=4, separators=(',', ': ')))
elif oformat == "yaml":
if iorpc_yaml_format is True:
print(yaml.safe_dump(json.loads(response), default_flow_style=False))
else:
print(json.dumps(json.loads(response), 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")
##
# Thread to listen on a reply fifo file
class IOFifoThread (threading.Thread):
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)
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, 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'
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)
# 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)
waitrun = True
while waitrun:
try:
tiofifo.join(500)
if not tiofifo.isAlive():
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, 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 += ' "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, (host, int(port)))
# receive the response (content, sockserver)
data = sockclient.recvfrom(84000)
response = data[0]
sockserver = data[1]
ctx.vlog('Server response: ' + response)
except socket.timeout as emsg:
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()
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, 0o660)
#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 as emsg:
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-2.0.0/kamcli/ioutils.py 0000664 0000000 0000000 00000002365 13545125701 0016264 0 ustar 00root root 0000000 0000000 import os
import sys
import json
# import pprint
ioutils_tabulate_format = True
try:
from tabulate import tabulate
except ImportError as e:
ioutils_tabulate_format = False
pass # module doesn't exist, deal with it.
ioutils_formats_list = ['raw', 'json', 'table', 'dict']
##
# print a database result using different formats and styles
#
def ioutils_dbres_print(ctx, oformat, ostyle, res):
if oformat is None:
if ioutils_tabulate_format is True:
oformat = 'table'
else:
oformat = 'json'
else:
if oformat == 'table':
if ioutils_tabulate_format is False:
ctx.log("Package tabulate is not installed")
sys.exit()
if ostyle is None:
ostyle = 'grid'
if oformat == 'json':
for row in res:
print(json.dumps(dict(row), indent=4))
print()
elif oformat == 'dict':
for row in res:
print (dict(row))
# pprint.pprint(dict(row), indent=4)
print()
elif oformat == 'table':
allrows = res.fetchall()
gstring = tabulate(allrows, headers=res.keys(), tablefmt=ostyle)
print(gstring)
else:
allrows = res.fetchall()
print(allrows)
kamcli-2.0.0/kamcli/kamcli.ini 0000664 0000000 0000000 00000003150 13545125701 0016154 0 ustar 00root root 0000000 0000000 ### main options
[main]
; SIP domain to be used when an AoR has no domain
domain=test.com
### database connectivity - URLs are used for SQL Alchemy
[db]
; type of database
type=mysql
; driver to be used fro connecting
driver=mysqldb
; host of database server
host=localhost
; kamailio database name
dbname=kamailio
; read/write user
rwuser=kamailio
; password for read/write user
rwpassword=kamailiorw
; read only user
rouser=kamailioro
; password for read only user
ropassword=kamailioro
; database URLs - built using above attributes, don't change unless you know what you do
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
### control tool settings
[ctl]
; type - can be: jsonrpc
type=jsonrpc
### 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 or udp:ipaddr:port
srvaddr=/var/run/kamailio/kamailio_rpc.sock
; srvaddr=udp:127.0.0.1:9062
; rcvaddr - where kamclie 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 - how RPC result is printed, can be: json or yaml
; - yaml is more compact output
outformat=yaml
kamcli-2.0.0/pkg/ 0000775 0000000 0000000 00000000000 13545125701 0013535 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/pkg/deb/ 0000775 0000000 0000000 00000000000 13545125701 0014267 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/pkg/deb/bionic/ 0000775 0000000 0000000 00000000000 13545125701 0015532 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/pkg/deb/bionic/changelog 0000664 0000000 0000000 00000000641 13545125701 0017405 0 ustar 00root root 0000000 0000000 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-2.0.0/pkg/deb/bionic/compat 0000664 0000000 0000000 00000000002 13545125701 0016730 0 ustar 00root root 0000000 0000000 9
kamcli-2.0.0/pkg/deb/bionic/control 0000664 0000000 0000000 00000001240 13545125701 0017132 0 ustar 00root root 0000000 0000000 Source: 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-2.0.0/pkg/deb/bionic/copyright 0000664 0000000 0000000 00000002234 13545125701 0017466 0 ustar 00root root 0000000 0000000 Format: 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-2.0.0/pkg/deb/bionic/kamcli.examples 0000664 0000000 0000000 00000000022 13545125701 0020524 0 ustar 00root root 0000000 0000000 kamcli/kamcli.ini
kamcli-2.0.0/pkg/deb/bionic/rules 0000775 0000000 0000000 00000000151 13545125701 0016607 0 ustar 00root root 0000000 0000000 #!/usr/bin/make -f
include /usr/share/dpkg/pkg-info.mk
%:
dh "$@" --with python3 --buildsystem=pybuild
kamcli-2.0.0/pkg/deb/buster/ 0000775 0000000 0000000 00000000000 13545125701 0015573 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/pkg/deb/buster/changelog 0000664 0000000 0000000 00000000641 13545125701 0017446 0 ustar 00root root 0000000 0000000 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-2.0.0/pkg/deb/buster/compat 0000664 0000000 0000000 00000000002 13545125701 0016771 0 ustar 00root root 0000000 0000000 9
kamcli-2.0.0/pkg/deb/buster/control 0000664 0000000 0000000 00000001240 13545125701 0017173 0 ustar 00root root 0000000 0000000 Source: 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-2.0.0/pkg/deb/buster/copyright 0000664 0000000 0000000 00000002234 13545125701 0017527 0 ustar 00root root 0000000 0000000 Format: 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-2.0.0/pkg/deb/buster/kamcli.examples 0000664 0000000 0000000 00000000022 13545125701 0020565 0 ustar 00root root 0000000 0000000 kamcli/kamcli.ini
kamcli-2.0.0/pkg/deb/buster/rules 0000775 0000000 0000000 00000000151 13545125701 0016650 0 ustar 00root root 0000000 0000000 #!/usr/bin/make -f
include /usr/share/dpkg/pkg-info.mk
%:
dh "$@" --with python3 --buildsystem=pybuild
kamcli-2.0.0/pkg/deb/debian/ 0000775 0000000 0000000 00000000000 13545125701 0015511 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/pkg/deb/debian/backports/ 0000775 0000000 0000000 00000000000 13545125701 0017501 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/pkg/deb/debian/backports/bionic 0000775 0000000 0000000 00000000266 13545125701 0020676 0 ustar 00root root 0000000 0000000 #!/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-2.0.0/pkg/deb/debian/backports/buster 0000775 0000000 0000000 00000000266 13545125701 0020737 0 ustar 00root root 0000000 0000000 #!/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-2.0.0/pkg/deb/debian/backports/stretch 0000775 0000000 0000000 00000000270 13545125701 0021102 0 ustar 00root root 0000000 0000000 #!/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-2.0.0/pkg/deb/debian/backports/xenial 0000775 0000000 0000000 00000000354 13545125701 0020711 0 ustar 00root root 0000000 0000000 #!/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-2.0.0/pkg/deb/debian/changelog 0000664 0000000 0000000 00000000641 13545125701 0017364 0 ustar 00root root 0000000 0000000 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-2.0.0/pkg/deb/debian/compat 0000664 0000000 0000000 00000000002 13545125701 0016707 0 ustar 00root root 0000000 0000000 9
kamcli-2.0.0/pkg/deb/debian/control 0000664 0000000 0000000 00000001240 13545125701 0017111 0 ustar 00root root 0000000 0000000 Source: 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-2.0.0/pkg/deb/debian/copyright 0000664 0000000 0000000 00000002234 13545125701 0017445 0 ustar 00root root 0000000 0000000 Format: 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-2.0.0/pkg/deb/debian/kamcli.examples 0000664 0000000 0000000 00000000022 13545125701 0020503 0 ustar 00root root 0000000 0000000 kamcli/kamcli.ini
kamcli-2.0.0/pkg/deb/debian/rules 0000775 0000000 0000000 00000000151 13545125701 0016566 0 ustar 00root root 0000000 0000000 #!/usr/bin/make -f
include /usr/share/dpkg/pkg-info.mk
%:
dh "$@" --with python3 --buildsystem=pybuild
kamcli-2.0.0/pkg/deb/stretch/ 0000775 0000000 0000000 00000000000 13545125701 0015743 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/pkg/deb/stretch/changelog 0000664 0000000 0000000 00000000641 13545125701 0017616 0 ustar 00root root 0000000 0000000 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-2.0.0/pkg/deb/stretch/compat 0000664 0000000 0000000 00000000002 13545125701 0017141 0 ustar 00root root 0000000 0000000 9
kamcli-2.0.0/pkg/deb/stretch/control 0000664 0000000 0000000 00000001240 13545125701 0017343 0 ustar 00root root 0000000 0000000 Source: 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-2.0.0/pkg/deb/stretch/copyright 0000664 0000000 0000000 00000002234 13545125701 0017677 0 ustar 00root root 0000000 0000000 Format: 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-2.0.0/pkg/deb/stretch/kamcli.examples 0000664 0000000 0000000 00000000022 13545125701 0020735 0 ustar 00root root 0000000 0000000 kamcli/kamcli.ini
kamcli-2.0.0/pkg/deb/stretch/rules 0000775 0000000 0000000 00000000151 13545125701 0017020 0 ustar 00root root 0000000 0000000 #!/usr/bin/make -f
include /usr/share/dpkg/pkg-info.mk
%:
dh "$@" --with python3 --buildsystem=pybuild
kamcli-2.0.0/pkg/deb/xenial/ 0000775 0000000 0000000 00000000000 13545125701 0015547 5 ustar 00root root 0000000 0000000 kamcli-2.0.0/pkg/deb/xenial/changelog 0000664 0000000 0000000 00000000641 13545125701 0017422 0 ustar 00root root 0000000 0000000 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-2.0.0/pkg/deb/xenial/compat 0000664 0000000 0000000 00000000002 13545125701 0016745 0 ustar 00root root 0000000 0000000 9
kamcli-2.0.0/pkg/deb/xenial/control 0000664 0000000 0000000 00000001240 13545125701 0017147 0 ustar 00root root 0000000 0000000 Source: 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-2.0.0/pkg/deb/xenial/copyright 0000664 0000000 0000000 00000002234 13545125701 0017503 0 ustar 00root root 0000000 0000000 Format: 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-2.0.0/pkg/deb/xenial/kamcli.examples 0000664 0000000 0000000 00000000022 13545125701 0020541 0 ustar 00root root 0000000 0000000 kamcli/kamcli.ini
kamcli-2.0.0/pkg/deb/xenial/pydist-overrides 0000664 0000000 0000000 00000000022 13545125701 0021000 0 ustar 00root root 0000000 0000000 pyaml python-yaml
kamcli-2.0.0/pkg/deb/xenial/rules 0000775 0000000 0000000 00000000151 13545125701 0016624 0 ustar 00root root 0000000 0000000 #!/usr/bin/make -f
include /usr/share/dpkg/pkg-info.mk
%:
dh "$@" --with python3 --buildsystem=pybuild
kamcli-2.0.0/setup.py 0000664 0000000 0000000 00000000532 13545125701 0014466 0 ustar 00root root 0000000 0000000 from setuptools import setup
setup(
name='kamcli',
version='2.0.0',
packages=['kamcli', 'kamcli.commands'],
include_package_data=True,
install_requires=[
'click',
'pyaml',
'sqlalchemy',
'tabulate',
],
entry_points='''
[console_scripts]
kamcli=kamcli.cli:cli
''',
)