├── tests ├── unit │ ├── .gitkeep │ ├── CMakeLists.txt │ ├── test_media_helper.c │ ├── test_dns_helper.c │ └── test_sip_helper.c ├── acceptance │ ├── .gitkeep │ ├── test.subscribe_terminate.pl │ ├── test.usage_opts.pl │ ├── CMakeLists.txt │ ├── test.ping_options_tcp.pl │ ├── test.ping_retrans.pl │ ├── test.notify_auth.pl │ ├── test.message_auth.pl │ ├── test.refer_auth.pl │ ├── test.invite_cancel.pl │ ├── test.publish_auth.pl │ ├── test.subscribe_auth.pl │ ├── test.register_auth.pl │ ├── test.refer_basic.pl │ ├── test.invite_auth.pl │ ├── CMakeGetopts.cmake │ ├── test.ping_auth.pl │ ├── sipp_scenarios │ │ ├── options.408.xml │ │ ├── message.basic.xml │ │ ├── notify.basic.xml │ │ ├── options.basic.xml │ │ ├── publish.basic.xml │ │ ├── register.no_auth.xml │ │ ├── refer.basic.xml │ │ ├── subscribe.terminated.xml │ │ ├── invite.cancel.xml │ │ ├── subscribe.mwi.xml │ │ ├── subscribe.basic.xml │ │ ├── register.auth.xml │ │ ├── message.auth.xml │ │ ├── notify.auth.xml │ │ ├── refer.auth.xml │ │ ├── options.auth.xml │ │ ├── publish.auth.xml │ │ ├── invite.basic.xml │ │ ├── invite.proxy.xml │ │ ├── invite.auth.xml │ │ └── subscribe.auth.xml │ ├── test.invite_proxy.pl │ ├── test.subscribe_mwi.pl │ ├── test.ping_options.pl │ ├── test.message_basic.pl │ ├── test.subscribe_basic.pl │ ├── test.ping_src_port_host.pl │ ├── test.register_no_auth.pl │ ├── CMakeSIPPScenarios.cmake │ ├── test.invite_basic.pl │ ├── test.notify_basic.pl │ └── test.publish_basic.pl └── CMakeLists.txt ├── .syntastic_c_config ├── sippak.png ├── dist ├── sippak-1.0.0-beta-Linux.deb ├── sippak-1.0.0-beta-Linux.rpm ├── sippak-1.1.0-beta-Linux.deb ├── sippak-1.1.0-beta-Linux.rpm ├── sippak-1.0.0-beta-Linux.tar.gz ├── sippak-1.1.0-beta-Linux.tar.gz ├── nix │ ├── default.nix │ └── sippak.nix ├── PKGBUILD └── CMakeLists.txt ├── .gitignore ├── Dockerfile ├── src ├── app │ ├── CMakeLists.txt │ └── dns.c ├── mod │ ├── CMakeLists.txt │ ├── logger.h │ ├── sip_mangler.c │ ├── ping.c │ ├── register.c │ ├── publish.c │ ├── refer.c │ └── message.c ├── CMakeLists.txt └── main.c ├── .github └── workflows │ └── main.yml ├── Makefile ├── CMakeLists.txt └── CODE_OF_CONDUCT.md /tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/acceptance/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.syntastic_c_config: -------------------------------------------------------------------------------- 1 | -Ibuild/src/include 2 | -------------------------------------------------------------------------------- /sippak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staskobzar/sippak/HEAD/sippak.png -------------------------------------------------------------------------------- /dist/sippak-1.0.0-beta-Linux.deb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staskobzar/sippak/HEAD/dist/sippak-1.0.0-beta-Linux.deb -------------------------------------------------------------------------------- /dist/sippak-1.0.0-beta-Linux.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staskobzar/sippak/HEAD/dist/sippak-1.0.0-beta-Linux.rpm -------------------------------------------------------------------------------- /dist/sippak-1.1.0-beta-Linux.deb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staskobzar/sippak/HEAD/dist/sippak-1.1.0-beta-Linux.deb -------------------------------------------------------------------------------- /dist/sippak-1.1.0-beta-Linux.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staskobzar/sippak/HEAD/dist/sippak-1.1.0-beta-Linux.rpm -------------------------------------------------------------------------------- /dist/sippak-1.0.0-beta-Linux.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staskobzar/sippak/HEAD/dist/sippak-1.0.0-beta-Linux.tar.gz -------------------------------------------------------------------------------- /dist/sippak-1.1.0-beta-Linux.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/staskobzar/sippak/HEAD/dist/sippak-1.1.0-beta-Linux.tar.gz -------------------------------------------------------------------------------- /dist/nix/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11"; 3 | pkgs = import nixpkgs { 4 | config = { }; 5 | overlays = [ ]; 6 | }; 7 | in { sippak = pkgs.callPackage ./sippak.nix { }; } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/nix/result 3 | 4 | CMakeCache.txt 5 | CMakeFiles 6 | CMakeScripts 7 | Testing 8 | Makefile 9 | cmake_install.cmake 10 | install_manifest.txt 11 | compile_commands.json 12 | CTestTestfile.cmake 13 | 14 | # Swap 15 | [._]*.s[a-v][a-z] 16 | [._]*.sw[a-p] 17 | [._]s[a-v][a-z] 18 | [._]sw[a-p] 19 | 20 | # Session 21 | Session.vim 22 | 23 | # Temporary 24 | .netrwhist 25 | *~ 26 | # Auto-generated tag files 27 | tags 28 | 29 | # gcc coverage testing tool files 30 | *.gcno 31 | *.gcda 32 | *.gcov 33 | vgcore.* 34 | -------------------------------------------------------------------------------- /dist/nix/sippak.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib, fetchzip, cmake, pjsip, cmocka, dbus, pkg-config, }: 2 | 3 | stdenv.mkDerivation { 4 | pname = "sippak"; 5 | version = "1.1.0"; 6 | 7 | src = fetchzip { 8 | url = "https://github.com/staskobzar/sippak/archive/refs/tags/v1.1.0.zip"; 9 | sha256 = "sha256-B9aANfpdYzUN5hm4NvPNcXi/iSQSTjNfLoKUpyNvTKk="; 10 | }; 11 | 12 | buildInputs = [ pjsip cmocka cmake dbus ]; 13 | 14 | nativeBuildInputs = [ pkg-config ]; 15 | 16 | buildPhase = '' 17 | runHook preInstall 18 | make 19 | make install 20 | runHook postInstall 21 | ''; 22 | } 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | 5 | WORKDIR /app 6 | 7 | ADD . /app 8 | 9 | RUN apt-get update 10 | 11 | RUN apt-get -y --no-install-recommends install build-essential automake ncurses-dev cmake libcmocka0 git \ 12 | pkg-config autoconf libterm-ui-perl libasound2-dev libalsaplayer-dev openssl libssl-dev apt-utils xterm 13 | 14 | RUN apt-get -y install curl ncurses-dev libsctp-dev libpcap-dev ca-certificates sip-tester 15 | 16 | RUN mkdir /build 17 | 18 | RUN cd /build && git clone -b 2.7.x --depth 1 https://github.com/pjsip/pjproject.git 19 | 20 | RUN cd /build/pjproject && ./configure --prefix=/usr && make dep && make && make install 21 | 22 | RUN ldconfig 23 | 24 | RUN make clean && make && make test && make install 25 | 26 | RUN rm -rf /build -------------------------------------------------------------------------------- /dist/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Stas Kobzar 2 | pkgname=sippak 3 | pkgver=v1.0.0.beta 4 | pkgrel=1 5 | pkgdesc='SIP protocol command line utility.' 6 | arch=('x86_64') 7 | url='https://github.com/staskobzar/sippak.git' 8 | license=('GPL3') 9 | groups=('voip') 10 | depends=('alsa-lib' 'openssl') 11 | makedepends=('git' 'pkgconf' 'cmake>=3.5' 'pjproject>=2.7') 12 | source=("${pkgname}::git+https://github.com/staskobzar/sippak.git") 13 | noextract=() 14 | md5sums=('SKIP') 15 | 16 | pkgver() { 17 | cd "${srcdir}/${pkgname}" 18 | git describe --tags --abbrev=0 | sed 's/\([^-]*-g\)/r\1/;s/-/./g' 19 | } 20 | 21 | build() { 22 | cd "${srcdir}/${pkgname}" 23 | mkdir build 24 | cd build 25 | cmake .. 26 | make 27 | } 28 | 29 | package() { 30 | cd "${srcdir}/${pkgname}/build" 31 | make DESTDIR=$pkgdir install 32 | } 33 | 34 | # vim:set ts=2 sw=2 et: 35 | -------------------------------------------------------------------------------- /src/app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | add_library (app OBJECT 21 | getopts.c 22 | usage.c 23 | dns.c 24 | sip_helper.c 25 | media_helper.c 26 | ) 27 | 28 | -------------------------------------------------------------------------------- /src/mod/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | add_library (mod OBJECT 21 | logger.c 22 | sip_mangler.c 23 | ping.c 24 | publish.c 25 | subscribe.c 26 | notify.c 27 | register.c 28 | refer.c 29 | message.c 30 | invite.c 31 | ) 32 | -------------------------------------------------------------------------------- /tests/acceptance/test.subscribe_terminate.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak subscribe method SIP flow 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/subscribe.terminated.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak publish basic 27 | $output = `$sippak SUBSCRIBE -X 0 sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^SUBSCRIBE sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 31 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE packet sent."); 32 | 33 | $regex = '^Expires: 0$'; 34 | ok ($output =~ m/$regex/m, "Subscription expires is set to 0."); 35 | 36 | $regex = 'Subscription is terminated'; 37 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE subscription is terminated."); 38 | 39 | done_testing(); 40 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | 21 | include(CTest) 22 | 23 | pkg_check_modules (CMOCKA cmocka>=1.0.1) 24 | if (CMOCKA_FOUND) 25 | add_subdirectory (unit) 26 | else (CMOCKA_FOUND) 27 | message (WARNING "CMocka lib is not found. Will not continue with unit testing.") 28 | endif (CMOCKA_FOUND) 29 | 30 | add_subdirectory (acceptance) 31 | -------------------------------------------------------------------------------- /tests/acceptance/test.usage_opts.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Make sure usage prints all application parameters and options 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Test::More; 9 | 10 | my $getopts_file = $ARGV[0]; 11 | my $sippak = $ARGV[1]; 12 | my $struct_found = 0; 13 | my @opts = (); 14 | my $output = ""; 15 | 16 | open(GETOPTS, $getopts_file) or die("Can not open getopts.c"); 17 | 18 | while(){ 19 | if (m/struct\s+pj_getopt_option\s+sippak_long_opts\[\]/) { 20 | $struct_found = 1; 21 | next; 22 | } 23 | 24 | if (m/\{\s*NULL\s*,(\s*0\s*,){2}\s*0\s*\}/) { 25 | $struct_found = 0; 26 | } 27 | 28 | if ($struct_found) { 29 | if (my @params = m/ 30 | \{\s* 31 | "([^"]+)"\s*, # long name 32 | \s*([\w\d]+)\s*, # has arg 33 | \s*([\w\d]+)\s*, # flag 34 | \s*'?([\w\d]+)'?\s* # val or short name 35 | \}, 36 | /x) { 37 | push(@opts, $params[0]); 38 | } 39 | } 40 | } 41 | $output = `$sippak --help`; 42 | foreach my $opt (@opts) { 43 | ok ($output =~ m/\-\-$opt\b/, "Match options --$opt"); 44 | } 45 | done_testing(); 46 | -------------------------------------------------------------------------------- /tests/acceptance/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | 21 | set (EXECMD ${CMAKE_BINARY_DIR}/src/${PROJECT_NAME}) 22 | 23 | # test CLI opts and arguments 24 | include(CMakeGetopts.cmake) 25 | 26 | # test sipp scenarious 27 | find_program (SIPP NAMES sipp) 28 | if (SIPP) 29 | include (CMakeSIPPScenarios.cmake) 30 | else (SIPP) 31 | message (WARNING "Application \"sipp\" not found. Will not run sipp scenarious.") 32 | endif (SIPP) 33 | 34 | -------------------------------------------------------------------------------- /tests/acceptance/test.ping_options_tcp.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command via TCP 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/options.basic.xml"; 15 | my $sippargs = "-timeout 10s -t t1 -p 5061 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run test with TCP 27 | $output = `$sippak --proto=tcp sip:alice\@127.0.0.1:5061`; 28 | 29 | # test request 30 | $regex = '^Via: SIP\/2\.0\/TCP '; 31 | ok ($output =~ m/$regex/m, "Basic OPTIONS packet sent via TCP."); 32 | 33 | # test response 34 | $regex = '^SIP\/2\.0 200 OK Basic OPTIONS Test$'; 35 | ok ($output =~ m/$regex/m, "Basic OPTIONS Response 200 OK via TCP."); 36 | 37 | # user agent 38 | system("$sipp $sippargs -sf $scenario"); 39 | $output = `$sippak --proto=tcp -A foobar sip:alice\@127.0.0.1:5061`; 40 | 41 | $regex = '^User-Agent: foobar$'; 42 | ok ($output =~ m/$regex/m, "User agent header set."); 43 | 44 | done_testing(); 45 | -------------------------------------------------------------------------------- /tests/acceptance/test.ping_retrans.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command and timeout in 10 transactions 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Socket; 9 | use Cwd qw(abs_path); 10 | use File::Basename; 11 | use Test::More; 12 | 13 | my $sippak = $ARGV[0]; 14 | my $sipp = $ARGV[1]; 15 | my $scenario = $ARGV[2] . "/options.408.xml"; 16 | my $sippargs = "-p 5060 -m 1 -bg"; 17 | my $output = ""; 18 | my $regex = ""; 19 | 20 | sub sipp_quit { 21 | my $sipp_port = 8888; 22 | my $sipp_addr = inet_aton("127.0.0.1"); 23 | my $msg = "q"; 24 | 25 | socket(SIPPSock, PF_INET, SOCK_DGRAM, getprotobyname("udp")) 26 | or die("socket: $!"); 27 | 28 | send(SIPPSock, $msg, 0, 29 | sockaddr_in($sipp_port, $sipp_addr)) == length($msg) or 30 | die("Cannot send to sipp socket."); 31 | } 32 | 33 | # run sipp scenario in background mode 34 | system("$sipp $sippargs -sf $scenario"); 35 | if ($? == -1) { 36 | print "Failed execute sipp\n"; 37 | exit(1); 38 | } 39 | 40 | # run sippak ping basic 41 | $output = `$sippak sip:alice\@127.0.0.1:5060`; 42 | sipp_quit(); 43 | 44 | $regex = 'Retransmitions count: 10'; 45 | ok ($output =~ m/$regex/m, "Retransmitions count output."); 46 | 47 | ok ($output =~ m/408 Request Timeout/m, "Retransmitions timeout message output."); 48 | 49 | done_testing(); 50 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | 21 | configure_file ( 22 | "${CMAKE_CURRENT_SOURCE_DIR}/include/sippak.h.in" 23 | "${CMAKE_CURRENT_BINARY_DIR}/include/sippak.h" 24 | @ONLY) 25 | 26 | # PJPROJECT compile flags 27 | pkg_check_modules (PJSIP REQUIRED libpjproject>=2.7.1) 28 | 29 | add_subdirectory (mod) 30 | add_subdirectory (app) 31 | 32 | add_executable (${PROJECT_NAME} main.c 33 | $ 34 | $ 35 | ) 36 | target_link_libraries (${PROJECT_NAME} ${PJSIP_LIBRARIES} resolv ${EXTRA_LIBS}) 37 | 38 | install (TARGETS ${PROJECT_NAME} DESTINATION bin) 39 | 40 | -------------------------------------------------------------------------------- /tests/acceptance/test.notify_auth.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak NOTIFY command with presence info and check-sync 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/notify.auth.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak notify basic presence with pidf 27 | $output = `$sippak notify sip:alice\@127.0.0.1:5060`; 28 | 29 | # auth failed 30 | $regex = '^SIP/2.0 407 Proxy Authentication Required$'; 31 | ok ($output =~ m/$regex/m, "Proxy Authentication challange issued."); 32 | 33 | $regex = '^SIP/2.0 407 Proxy Authentication Retry$'; 34 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed."); 35 | 36 | $regex = 'Authentication failed'; 37 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed message."); 38 | 39 | # auth success 40 | system("$sipp $sippargs -sf $scenario"); 41 | $output = `$sippak notify -u alice -p pa55w0rd sip:alice\@127.0.0.1:5060`; 42 | 43 | $regex = '^SIP/2.0 200 OK Auth$'; 44 | ok ($output =~ m/$regex/m, "Proxy Authentication Success."); 45 | 46 | done_testing(); 47 | -------------------------------------------------------------------------------- /tests/acceptance/test.message_auth.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak message method 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Socket; 9 | use Cwd qw(abs_path); 10 | use File::Basename; 11 | use Test::More; 12 | 13 | my $sippak = $ARGV[0]; 14 | my $sipp = $ARGV[1]; 15 | my $scenario = $ARGV[2] . "/message.auth.xml"; 16 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 17 | my $output = ""; 18 | my $regex = ""; 19 | 20 | # run sipp scenario in background mode 21 | system("$sipp $sippargs -sf $scenario"); 22 | if ($? == -1) { 23 | print "Failed execute sipp\n"; 24 | exit(1); 25 | } 26 | 27 | # run sippak message requires parameter --body 28 | $output = `$sippak message --body="Hello" sip:alice\@127.0.0.1:5060`; 29 | 30 | # auth failed 31 | $regex = '^SIP/2.0 407 Message Proxy Authentication$'; 32 | ok ($output =~ m/$regex/m, "Proxy Authentication challange issued."); 33 | 34 | $regex = '^SIP/2.0 407 Proxy Authentication Retry$'; 35 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed."); 36 | 37 | $regex = 'Authentication failed'; 38 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed message."); 39 | 40 | # auth success 41 | system("$sipp $sippargs -sf $scenario"); 42 | $output = `$sippak message -u alice -p pa55w0rd --body="Hello" sip:alice\@127.0.0.1:5060`; 43 | 44 | $regex = '^SIP\/2.0 200 OK Auth$'; 45 | ok ($output =~ m/$regex/m, "Proxy Authentication Success."); 46 | 47 | done_testing(); 48 | -------------------------------------------------------------------------------- /tests/acceptance/test.refer_auth.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command and timeout in 10 transactions 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Socket; 9 | use Cwd qw(abs_path); 10 | use File::Basename; 11 | use Test::More; 12 | 13 | my $sippak = $ARGV[0]; 14 | my $sipp = $ARGV[1]; 15 | my $scenario = $ARGV[2] . "/refer.auth.xml"; 16 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 17 | my $output = ""; 18 | my $regex = ""; 19 | 20 | # run sipp scenario in background mode 21 | system("$sipp $sippargs -sf $scenario"); 22 | if ($? == -1) { 23 | print "Failed execute sipp\n"; 24 | exit(1); 25 | } 26 | 27 | # run sippak refer click-to-dial with authentication 28 | $output = `$sippak refer --to=sip:bob\@sip.com sip:alice\@127.0.0.1:5060`; 29 | 30 | # auth failed 31 | $regex = '^SIP/2.0 407 Refer Proxy Authentication$'; 32 | ok ($output =~ m/$regex/m, "Proxy Authentication challange issued."); 33 | 34 | $regex = '^SIP/2.0 407 Proxy Authentication Retry$'; 35 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed."); 36 | 37 | $regex = 'Authentication failed'; 38 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed message."); 39 | 40 | # auth success 41 | system("$sipp $sippargs -sf $scenario"); 42 | $output = `$sippak refer -u alice -p pa55w0rd --to=sip:bob\@sip.com sip:alice\@127.0.0.1:5060`; 43 | 44 | $regex = '^SIP/2.0 202 OK Accepted$'; 45 | ok ($output =~ m/$regex/m, "Proxy Authentication Success."); 46 | 47 | done_testing(); 48 | -------------------------------------------------------------------------------- /src/mod/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef __MOD_LOGGER_LOGGER_H 2 | #define __MOD_LOGGER_LOGGER_H 3 | 4 | static void term_set_color(int level); 5 | static void term_restore_color(void); 6 | 7 | #define PRINT_COLOR(color, ...) term_set_color (color); printf(__VA_ARGS__); 8 | 9 | #define COLOR_BRIGHT_WHITE PJ_TERM_COLOR_BRIGHT | \ 10 | PJ_TERM_COLOR_R | \ 11 | PJ_TERM_COLOR_G | \ 12 | PJ_TERM_COLOR_B 13 | 14 | #define COLOR_BLUE PJ_TERM_COLOR_B 15 | #define COLOR_CYAN PJ_TERM_COLOR_G | PJ_TERM_COLOR_B 16 | #define COLOR_GREEN PJ_TERM_COLOR_G 17 | #define COLOR_RED PJ_TERM_COLOR_R 18 | #define COLOR_YELLOW PJ_TERM_COLOR_R | PJ_TERM_COLOR_G 19 | #define COLOR_BRIGHT_BLUE PJ_TERM_COLOR_BRIGHT | PJ_TERM_COLOR_B | PJ_TERM_COLOR_R 20 | 21 | static pj_bool_t ENABLE_COLORS = PJ_FALSE; 22 | static char TRAIL_CHR = '.'; // end of line 23 | static pj_bool_t PRINT_TRAIL_CHR = PJ_FALSE; 24 | 25 | static void print_sipmsg_head (pjsip_msg *msg); 26 | static void print_sipmsg_headers (const pjsip_msg *msg); 27 | static void print_generic_header (const char *header, int len); 28 | static void print_content_len_hdr (unsigned int len); 29 | static void print_content_type_hdr (const pjsip_msg_body *body); 30 | static void print_sipmsg_body (pjsip_msg *msg, pj_bool_t is_tx); 31 | static void print_hdr_clid (pjsip_cid_hdr *cid); 32 | static void print_trail_chr (); 33 | 34 | static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata); 35 | static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /tests/acceptance/test.invite_cancel.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak basic invite SIP flow 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/invite.cancel.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak publish basic 27 | $output = `$sippak INVITE --cancel sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^INVITE sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 31 | ok ($output =~ m/$regex/m, "Basic INVITE packet sent."); 32 | 33 | $regex = '^SIP\/2.0 180 Ringing$'; 34 | ok ($output =~ m/$regex/m, "Ringing 180 early state."); 35 | 36 | $regex = '^SIP\/2.0 200 Canceled$'; 37 | ok ($output =~ m/$regex/m, "Cancel invite session is confirmed."); 38 | 39 | $regex = '^CANCEL sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 40 | ok ($output =~ m/$regex/m, "INVITE request canceled."); 41 | 42 | $regex = '^SIP\/2.0 200 Canceled$'; 43 | ok ($output =~ m/$regex/m, "Cancel confirmed with 200."); 44 | 45 | $regex = '^SIP\/2.0 487 Request Terminated$'; 46 | ok ($output =~ m/$regex/m, "Cancel response 487 sent."); 47 | 48 | $regex = '^ACK sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 49 | ok ($output =~ m/$regex/m, "Response 487 acknowlaged."); 50 | 51 | done_testing(); 52 | -------------------------------------------------------------------------------- /tests/acceptance/test.publish_auth.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command with 407 Proxy Authentication handle 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/publish.auth.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak ping basic with source port option 27 | # short option 28 | $output = `$sippak PUBLISH sip:alice\@127.0.0.1:5060`; 29 | 30 | # auth failed 31 | $regex = '^SIP/2.0 407 Proxy Authentication Required$'; 32 | ok ($output =~ m/$regex/m, "Proxy Authentication challange issued."); 33 | 34 | $regex = 'Proxy-Authorization: Digest username="alice"'; 35 | ok ($output =~ m/$regex/m, "Poxy Auth digest with result resent."); 36 | 37 | $regex = '^SIP/2.0 407 Proxy Authentication Retry$'; 38 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed."); 39 | 40 | $regex = 'Authentication failed'; 41 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed message."); 42 | 43 | # auth success 44 | system("$sipp $sippargs -sf $scenario"); 45 | $output = `$sippak PUBLISH -u alice -p pa55w0rd sip:alice\@127.0.0.1:5060`; 46 | 47 | $regex = '^SIP/2.0 200 OK Auth$'; 48 | ok ($output =~ m/$regex/m, "Proxy Authentication Success."); 49 | 50 | done_testing(); 51 | -------------------------------------------------------------------------------- /tests/acceptance/test.subscribe_auth.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command with 407 Proxy Authentication handle 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/subscribe.auth.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak ping basic with source port option 27 | # short option 28 | $output = `$sippak SUBSCRIBE sip:alice\@127.0.0.1:5060`; 29 | 30 | # auth failed 31 | $regex = '^SIP/2.0 407 Proxy Authentication Required$'; 32 | ok ($output =~ m/$regex/m, "Proxy Authentication challange issued."); 33 | 34 | $regex = 'Proxy-Authorization: Digest username="alice"'; 35 | ok ($output =~ m/$regex/m, "Poxy Auth digest with result resent."); 36 | 37 | $regex = '^SIP/2.0 407 Proxy Authentication Retry$'; 38 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed."); 39 | 40 | $regex = 'Authentication failed'; 41 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed message."); 42 | 43 | # auth success 44 | system("$sipp $sippargs -sf $scenario"); 45 | $output = `$sippak SUBSCRIBE -u alice -p pa55w0rd sip:alice\@127.0.0.1:5060`; 46 | 47 | $regex = '^SIP/2.0 200 OK SUBSCRIBE Auth$'; 48 | ok ($output =~ m/$regex/m, "Proxy Authentication Success."); 49 | 50 | done_testing(); 51 | -------------------------------------------------------------------------------- /dist/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | 21 | # create distribution packages 22 | 23 | SET(SIPPAK_PKG_VER "${sippak_VERSION}-beta") 24 | 25 | set (CPACK_GENERATOR "RPM;DEB;TGZ") 26 | set (CPACK_PACKAGE_VERSION ${SIPPAK_PKG_VER}) 27 | set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING") 28 | set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "SIP protocol command line utility.") 29 | 30 | set (CPACK_DEBIAN_PACKAGE_MAINTAINER "Stas Kobzar ") 31 | set (CPACK_DEBIAN_PACKAGE_DEPENDS "libalsaplayer0,libasound2") 32 | set (CPACK_DEBIAN_PACKAGE_SECTION "net") 33 | 34 | set (CPACK_RPM_PACKAGE_VENDOR "Stas Kobzar ") 35 | set (CPACK_RPM_PACKAGE_REQUIRES "alsa-lib") 36 | set (CPACK_RPM_PACKAGE_LICENSE "GNU General Public License v3.0") 37 | set (CPACK_RPM_PACKAGE_GROUP "Networking Tools") 38 | 39 | include (CPack) 40 | -------------------------------------------------------------------------------- /tests/acceptance/test.register_auth.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak NOTIFY command with presence info and check-sync 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/register.auth.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak notify basic presence with pidf 27 | $output = `$sippak register -p passw0rd sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^REGISTER sip:127\.0\.0\.1:5060 SIP\/2.0$'; 31 | ok ($output =~ m/$regex/m, "Basic REGISTER packet sent."); 32 | 33 | $regex = '^SIP/2.0 401 Authorization Required$'; 34 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed."); 35 | 36 | $regex = '^SIP/2.0 401 Authorization Retry$'; 37 | ok ($output =~ m/$regex/m, "Authorization re-challange issued."); 38 | 39 | $regex = 'Authentication failed'; 40 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed message."); 41 | 42 | # auth success 43 | system("$sipp $sippargs -sf $scenario"); 44 | $output = `$sippak register -p pa55w0rd sip:alice\@127.0.0.1:5060`; 45 | 46 | $regex = '^SIP/2.0 401 Authorization Required$'; 47 | ok ($output =~ m/$regex/m, "Authorization challange issued for good password."); 48 | 49 | $regex = '^SIP/2.0 200 Registered Ok$'; 50 | ok ($output =~ m/$regex/m, "Registration successfull."); 51 | 52 | done_testing(); 53 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v3 27 | 28 | - name: Install 29 | run: | 30 | sudo apt-get -qq update 31 | sudo apt-get -y --no-install-recommends install build-essential automake ncurses-dev cmake libcmocka0 zip pkg-config autoconf libterm-ui-perl libasound2-dev libalsaplayer-dev openssl libssl-dev apt-utils xterm 32 | sudo apt-get -y install curl ncurses-dev libsctp-dev libpcap-dev ca-certificates sip-tester wget unzip 33 | wget https://github.com/pjsip/pjproject/archive/refs/tags/2.9.zip 34 | unzip 2.9.zip 35 | cd pjproject-2.9 && ./configure --prefix=/usr && make dep && make && sudo make install 36 | 37 | - name: Build 38 | run: make 39 | 40 | - name: Test 41 | run: make test 42 | -------------------------------------------------------------------------------- /tests/acceptance/test.refer_basic.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command and timeout in 10 transactions 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Socket; 9 | use Cwd qw(abs_path); 10 | use File::Basename; 11 | use Test::More; 12 | 13 | my $sippak = $ARGV[0]; 14 | my $sipp = $ARGV[1]; 15 | my $scenario = $ARGV[2] . "/refer.basic.xml"; 16 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 17 | my $output = ""; 18 | my $regex = ""; 19 | 20 | # run sippak refer click-to-dial basic 21 | $output = `$sippak refer sip:alice\@127.0.0.1:5060`; 22 | 23 | $regex = 'Failed. Requires parameter "--to" to initiate REFER request.'; 24 | ok ($output =~ m/$regex/m, "Refer method fails when no param --to set."); 25 | 26 | # run sipp scenario in background mode 27 | system("$sipp $sippargs -sf $scenario"); 28 | if ($? == -1) { 29 | print "Failed execute sipp\n"; 30 | exit(1); 31 | } 32 | 33 | # refer click to dial send 34 | $output = `$sippak refer --to=sip:bob\@bar.fr sip:alice\@127.0.0.1:5060`; 35 | 36 | $regex = '^REFER sip:alice\@127\.0\.0\.1:5060 SIP\/2.0$'; 37 | ok ($output =~ m/$regex/m, "Basic REFER packet sent."); 38 | 39 | $regex = '^Refer-To: sip:bob@bar.fr$'; 40 | ok ($output =~ m/$regex/m, "Basic REFER has valid Refer-To header."); 41 | 42 | $regex = '^Refer-Sub: false$'; 43 | ok ($output =~ m/$regex/m, "Basic REFER header Refer-Sub must be false."); 44 | 45 | # Header User-Agent add 46 | system("$sipp $sippargs -sf $scenario"); 47 | $output = `$sippak refer --user-agent="SIP foo" --to=sip:bob\@bar.fr sip:alice\@127.0.0.1:5060`; 48 | 49 | $regex = '^User-Agent: SIP foo$'; 50 | ok ($output =~ m/$regex/m, "Add User-Agent header."); 51 | 52 | done_testing(); 53 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build build-cov clean ctags tests 2 | 3 | BUILD_DIR=build 4 | all: build 5 | 6 | install: 7 | @cd ${BUILD_DIR} && make install 8 | 9 | package: build 10 | @cd build && make package 11 | @cp build/sippak-*.rpm dist 12 | @cp build/sippak-*.deb dist 13 | @cp build/sippak-*.tar.gz dist 14 | 15 | build: 16 | @test -d ${BUILD_DIR} || mkdir ${BUILD_DIR} 17 | @cd ${BUILD_DIR} && cmake .. && make 18 | 19 | build-debug: 20 | @test -d ${BUILD_DIR} || mkdir ${BUILD_DIR} 21 | @cd ${BUILD_DIR} && cmake .. -DCMAKE_BUILD_TYPE=Debug && make 22 | 23 | build-cov: clean 24 | @mkdir ${BUILD_DIR} 25 | @cd ${BUILD_DIR} && cmake .. -DCMAKE_BUILD_TYPE=Coverage \ 26 | -DCMAKE_C_FLAGS="-Werror -fprofile-arcs -ftest-coverage -g -O0" 27 | @cd ${BUILD_DIR} && cmake --build . 28 | @cd ${BUILD_DIR} && ctest . 29 | @lcov --capture --directory "${BUILD_DIR}/src" --output-file ${BUILD_DIR}/cov_full.info \ 30 | --gcov-tool gcov 31 | @lcov -r ${BUILD_DIR}/cov_full.info '/usr/local/include/pj*' -o ${BUILD_DIR}/cov.info 32 | @mkdir ${BUILD_DIR}/coverage 33 | @genhtml --output-directory ${BUILD_DIR}/coverage --title "sippak utility" \ 34 | --legend --show-details ${BUILD_DIR}/cov.info 35 | # @xdg-open ${BUILD_DIR}/coverage/index.html 36 | 37 | codecov: build-cov 38 | @bash <(curl -s https://codecov.io/bash) -t 49d36735-c7ba-47a3-8b19-d07e2de188fa 39 | 40 | test: 41 | @cd build && env CTEST_OUTPUT_ON_FAILURE=1 ctest 42 | 43 | .PHONY: dev-test 44 | # watchman-make -p 'src/**/*' 'tests/**/*' 'CMakeLists.txt' 'Makefile' -t dev-test 45 | dev-test: build 46 | @./build/tests/unit/test_getopts 47 | @./build/tests/unit/test_dns_helper 48 | @./build/tests/unit/test_auth_calc 49 | 50 | ctags: 51 | @echo -n 'Generating ctags...' 52 | @ctags -R src 2>/dev/null 53 | @echo ' done.' 54 | 55 | clean: 56 | @echo "Clean build directory..." 57 | @rm -rf ${BUILD_DIR} 58 | @rm -rf *gcov 59 | -------------------------------------------------------------------------------- /tests/acceptance/test.invite_auth.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak basic invite SIP flow 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/invite.auth.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak publish basic 27 | $output = `$sippak INVITE sip:alice\@127.0.0.1:5060`; 28 | 29 | # test auth failed 30 | $regex = '^INVITE sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 31 | ok ($output =~ m/$regex/m, "Basic INVITE packet sent."); 32 | 33 | $regex = '^SIP\/2.0 407 Proxy Authentication Required$'; 34 | ok ($output =~ m/$regex/m, "Proxy auth response 407"); 35 | 36 | $regex = '^SIP\/2.0 407 Proxy Authentication Failed$'; 37 | ok ($output =~ m/$regex/m, "Proxy auth failed response 407"); 38 | 39 | # test auth success 40 | system("$sipp $sippargs -sf $scenario"); 41 | $output = `$sippak INVITE -u alice -p pa55w0rd sip:alice\@127.0.0.1:5060`; 42 | 43 | $regex = '^INVITE sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 44 | ok ($output =~ m/$regex/m, "Basic INVITE packet sent."); 45 | 46 | $regex = '^SIP\/2.0 200 OK Invite$'; 47 | ok ($output =~ m/$regex/m, "Invite session is confirmed."); 48 | 49 | $regex = '^ACK sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 50 | ok ($output =~ m/$regex/m, "Invite 200 in acknowlaged."); 51 | 52 | $regex = '^BYE sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 53 | ok ($output =~ m/$regex/m, "Session is terminated with BYE."); 54 | 55 | $regex = '^SIP\/2.0 200 OK bye$'; 56 | ok ($output =~ m/$regex/m, "BYE method is confirmed."); 57 | 58 | done_testing(); 59 | -------------------------------------------------------------------------------- /tests/acceptance/CMakeGetopts.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | 21 | # output version and exit 22 | add_test (Print_Version ${EXECMD} -V ) 23 | set_tests_properties (Print_Version PROPERTIES PASS_REGULAR_EXPRESSION 24 | "${PROJECT_NAME} ${PROJECT_VERSION}") 25 | set_tests_properties (Print_Version PROPERTIES FAIL_REGULAR_EXPRESSION 26 | "Usage: ") 27 | 28 | # print usage when no arguments 29 | add_test (NoArgs_Usage ${EXECMD}) 30 | set_tests_properties (NoArgs_Usage PROPERTIES PASS_REGULAR_EXPRESSION 31 | "Usage: ${PROJECT_NAME}") 32 | 33 | # print usage short argument 34 | add_test (Short_Opt_Help_Usage ${EXECMD} -h) 35 | set_tests_properties (Short_Opt_Help_Usage PROPERTIES PASS_REGULAR_EXPRESSION 36 | "Usage: ${PROJECT_NAME}") 37 | 38 | # print usage long argument 39 | add_test (Long_Opt_Help_Usage ${EXECMD} --help) 40 | set_tests_properties (Long_Opt_Help_Usage PROPERTIES PASS_REGULAR_EXPRESSION 41 | "Usage: ${PROJECT_NAME}") 42 | 43 | # usage has all options 44 | add_test (Usage_Has_All_Opts 45 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.usage_opts.pl 46 | ${CMAKE_SOURCE_DIR}/src/app/getopts.c ${EXECMD}) 47 | 48 | -------------------------------------------------------------------------------- /tests/acceptance/test.ping_auth.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command with 407 Proxy Authentication handle 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/options.auth.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak ping basic with source port option 27 | # short option 28 | $output = `$sippak -P 8899 sip:alice\@127.0.0.1:5060`; 29 | 30 | # auth failed 31 | $regex = '^SIP/2.0 407 Proxy Authentication Required$'; 32 | ok ($output =~ m/$regex/m, "Proxy Authentication challange issued."); 33 | 34 | $regex = 'Proxy-Authorization: Digest username="alice"'; 35 | ok ($output =~ m/$regex/m, "Poxy Auth digest with result resent."); 36 | 37 | $regex = '^SIP/2.0 407 Proxy Authentication Retry$'; 38 | ok ($output =~ m/$regex/m, "Proxy Authentication Failed."); 39 | 40 | # auth success 41 | system("$sipp -trace_logs -log_file /tmp/sipp.log $sippargs -sf $scenario"); 42 | $output = `$sippak -u alice -p pa55w0rd sip:alice\@127.0.0.1:5060`; 43 | 44 | $regex = '^SIP/2.0 200 OK Auth$'; 45 | ok ($output =~ m/$regex/m, "Proxy Authentication Success."); 46 | 47 | # fail with given invalid password 48 | system("$sipp $sippargs -sf $scenario"); 49 | $output = `$sippak -u unknown -p invalid sip:alice\@127.0.0.1:5060`; 50 | 51 | $regex = '^SIP/2.0 407 Proxy Authentication Required$'; 52 | ok ($output =~ m/$regex/m, "Proxy Authentication with given uaser/pass challange issued."); 53 | 54 | $regex = '^SIP/2.0 407 Proxy Authentication Retry$'; 55 | ok ($output =~ m/$regex/m, "Proxy Authentication with given uaser/pass Failed."); 56 | 57 | done_testing(); 58 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/options.408.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/acceptance/test.invite_proxy.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak handle proxy set 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/invite.proxy.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak publish basic 27 | $output = `$sippak INVITE --proxy=sip:127.0.0.1:5060 sip:alice\@pbx.corporate.big`; 28 | 29 | # test request 30 | $regex = '^INVITE sip:alice\@pbx.corporate.big SIP\/2\.0$'; 31 | ok ($output =~ m/$regex/m, "Basic INVITE packet sent to RURI with domain."); 32 | 33 | $regex = '^SIP\/2.0 180 Ringing$'; 34 | ok ($output =~ m/$regex/m, "Ringing 180 early response."); 35 | 36 | $regex = '^SIP\/2.0 200 OK Invite$'; 37 | ok ($output =~ m/$regex/m, "Invite session is confirmed."); 38 | 39 | $regex = '^ACK sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 40 | ok ($output =~ m/$regex/m, "Invite 200 in acknowlaged."); 41 | 42 | $regex = '^BYE sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 43 | ok ($output =~ m/$regex/m, "Session is terminated with BYE."); 44 | 45 | $regex = '^SIP\/2.0 200 OK bye$'; 46 | ok ($output =~ m/$regex/m, "BYE method is confirmed."); 47 | 48 | # test multiple proxies 49 | system("$sipp $sippargs -sf $scenario"); 50 | $output = `$sippak INVITE --proxy=sip:127.0.0.1:5060 -R sip:foo.com:2525 -R sips:125.1.1.100:8080 sip:alice\@pbx.corporate.big`; 51 | 52 | $regex = '^Route: $'; 53 | ok ($output =~ m/$regex/m, "First proxy with loose route parameter"); 54 | 55 | $regex = '^Route: $'; 56 | ok ($output =~ m/$regex/m, "Second proxy as Router header."); 57 | 58 | $regex = '^Route: $'; 59 | ok ($output =~ m/$regex/m, "Third proxy as Router header"); 60 | 61 | done_testing(); 62 | -------------------------------------------------------------------------------- /tests/acceptance/test.subscribe_mwi.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak subscribe MWI event SIP flow 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/subscribe.mwi.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak subscribe basic short opt 27 | $output = `$sippak SUBSCRIBE -E message-summary sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^Event: message-summary$'; 31 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE short opt MWI with event message-summary packet sent."); 32 | 33 | # test response 34 | $regex = '^SIP\/2\.0 200 OK Basic SUB MWI Test$'; 35 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE Response 200 OK received for message-summary short opt."); 36 | 37 | # run sippak subscribe basic short opt alias 38 | system("$sipp $sippargs -sf $scenario"); 39 | $output = `$sippak SUBSCRIBE -E mwi sip:alice\@127.0.0.1:5060`; 40 | 41 | # test request 42 | $regex = '^Event: message-summary$'; 43 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE short opt MWI with event mwi packet sent."); 44 | 45 | # test response 46 | $regex = '^SIP\/2\.0 200 OK Basic SUB MWI Test$'; 47 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE Response 200 OK received on mwi short opt."); 48 | 49 | # run sippak subscribe basic long opt 50 | system("$sipp $sippargs -sf $scenario"); 51 | $output = `$sippak SUBSCRIBE --event=mwi sip:alice\@127.0.0.1:5060`; 52 | 53 | # test request 54 | $regex = '^Event: message-summary$'; 55 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE long opt MWI with event mwi packet sent."); 56 | 57 | system("$sipp $sippargs -sf $scenario"); 58 | $output = `$sippak SUBSCRIBE --event=message-summary sip:alice\@127.0.0.1:5060`; 59 | 60 | # test request 61 | $regex = '^Event: message-summary$'; 62 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE long opt MWI with event message-summary packet sent."); 63 | 64 | done_testing(); 65 | -------------------------------------------------------------------------------- /src/mod/sip_mangler.c: -------------------------------------------------------------------------------- 1 | /** 2 | * sippak -- SIP command line utility. 3 | * Copyright (C) 2018, Stas Kobzar 4 | * 5 | * This file is part of sippak. 6 | * 7 | * sippak is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * sippak is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with sippak. If not, see . 19 | */ 20 | 21 | /** 22 | * @file ping.c 23 | * @brief sippak mangling SIP packets before send 24 | * 25 | * @author Stas Kobzar 26 | */ 27 | #include "sippak.h" 28 | 29 | #define NAME "mod_sip_mangler" 30 | 31 | static struct sippak_app *local_app; 32 | static pj_status_t on_tx_request(pjsip_tx_data *tdata); 33 | 34 | static pjsip_module mod_sip_mangler = 35 | { 36 | NULL, NULL, /* prev, next. */ 37 | { "mod-sip-mangler", 15 }, /* Name. */ 38 | -1, /* Id */ 39 | PJSIP_MOD_PRIORITY_TSX_LAYER, /* Priority */ 40 | NULL, /* load() */ 41 | NULL, /* start() */ 42 | NULL, /* stop() */ 43 | NULL, /* unload() */ 44 | NULL, /* on_rx_request() */ 45 | NULL, /* on_rx_response() */ 46 | &on_tx_request, /* on_tx_request. */ 47 | NULL, /* on_tx_response() */ 48 | NULL, /* on_tsx_state() */ 49 | }; 50 | 51 | static pj_status_t on_tx_request(pjsip_tx_data *tdata) 52 | { 53 | sippak_add_sip_headers (tdata, local_app); 54 | 55 | return PJ_SUCCESS; 56 | } 57 | 58 | /* Ping */ 59 | PJ_DEF(pj_status_t) sippak_mod_sip_mangler_register (struct sippak_app *app) 60 | { 61 | local_app = app; 62 | return pjsip_endpt_register_module(app->endpt, &mod_sip_mangler); 63 | } 64 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/message.basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/notify.basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/options.basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/publish.basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/register.no_auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ;expires=900,;expires=80 37 | Content-Length: 0 38 | 39 | ]]> 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/refer.basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | 21 | cmake_minimum_required (VERSION 3.5) 22 | 23 | project (sippak VERSION 1.1.0 LANGUAGES C) 24 | set (CMAKE_C_STANDARD 99) 25 | set (PROJECT_URL "https://gitlab.com/staskobzar/sippak") 26 | set (PROJECT_AUTHOR "Stas Kobzar ") 27 | string (TIMESTAMP PROJECT_BUILTTIME "%Y-%m-%d %H:%M:%SZ UTC" UTC) 28 | 29 | include (FindPkgConfig) 30 | 31 | set(EXTRA_LIBS "") 32 | # MacOS frameworks to use with pjmedia 33 | if(APPLE) 34 | find_library(CORE_AUDIO CoreAudio) 35 | set(EXTRA_LIBS ${EXTRA_LIBS} ${CORE_AUDIO}) 36 | find_library(CORE_SRV CoreServices) 37 | set(EXTRA_LIBS ${EXTRA_LIBS} ${CORE_SRV}) 38 | find_library(AUDIO_UNIT AudioUnit) 39 | set(EXTRA_LIBS ${EXTRA_LIBS} ${AUDIO_UNIT}) 40 | find_library(AUDIO_TOOLBOX AudioToolbox) 41 | set(EXTRA_LIBS ${EXTRA_LIBS} ${AUDIO_TOOLBOX}) 42 | find_library(FOUNDATION Foundation) 43 | set(EXTRA_LIBS ${EXTRA_LIBS} ${FOUNDATION}) 44 | find_library(APP_KIT AppKit) 45 | set(EXTRA_LIBS ${EXTRA_LIBS} ${APP_KIT}) 46 | find_library(AVFOUNDATION AVFoundation) 47 | set(EXTRA_LIBS ${EXTRA_LIBS} ${AVFOUNDATION}) 48 | find_library(CORE_GRAPH CoreGraphics) 49 | set(EXTRA_LIBS ${EXTRA_LIBS} ${CORE_GRAPH}) 50 | find_library(QUARTZ_CORE QuartzCore) 51 | set(EXTRA_LIBS ${EXTRA_LIBS} ${QUARTZ_CORE}) 52 | find_library(CORE_VIDEO CoreVideo) 53 | set(EXTRA_LIBS ${EXTRA_LIBS} ${CORE_VIDEO}) 54 | find_library(CORE_MEDIA CoreMedia) 55 | set(EXTRA_LIBS ${EXTRA_LIBS} ${CORE_MEDIA}) 56 | find_library(VID_TOOLBOX VideoToolbox) 57 | set(EXTRA_LIBS ${EXTRA_LIBS} ${VID_TOOLBOX}) 58 | endif(APPLE) 59 | 60 | include_directories( ${CMAKE_CURRENT_BINARY_DIR}/src/include ) 61 | add_subdirectory (src) 62 | 63 | enable_testing() 64 | add_subdirectory (tests) 65 | 66 | add_subdirectory (dist) 67 | 68 | -------------------------------------------------------------------------------- /tests/acceptance/test.ping_options.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command without colors and trailing dot 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/options.basic.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak ping basic 27 | $output = `$sippak --ns=127.0.0.1 sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^OPTIONS sip:alice\@127\.0\.0\.1:5060 SIP\/2.0$'; 31 | ok ($output =~ m/$regex/m, "Basic OPTIONS packet sent."); 32 | 33 | # test response 34 | $regex = '^SIP\/2\.0 200 OK Basic OPTIONS Test$'; 35 | ok ($output =~ m/$regex/m, "Basic OPTIONS Response 200 OK received."); 36 | 37 | # ------------------------------------------------------------------ 38 | # run sippak ping output with trailing dots 39 | system("$sipp $sippargs -sf $scenario"); 40 | $output = `$sippak --trail-dot sip:alice\@127.0.0.1:5060`; 41 | 42 | # test request 43 | $regex = '^OPTIONS sip:alice\@127\.0\.0\.1:5060 SIP\/2.0\.$'; 44 | ok ($output =~ m/$regex/m, "Basic OPTIONS with trailing dot output."); 45 | 46 | # test response 47 | $regex = '^SIP\/2\.0 200 OK Basic OPTIONS Test\.$'; 48 | ok ($output =~ m/$regex/m, "Basic OPTIONS Response 200 OK with trailing dot output."); 49 | 50 | # ------------------------------------------------------------------ 51 | # run sippak ping output with color decoration 52 | system("$sipp $sippargs -sf $scenario"); 53 | $output = `$sippak --color sip:alice\@127.0.0.1:5060`; 54 | 55 | $regex = '^(\x1b\[[0-9;]+m){2}OPTIONS \x1b\[[0-9;]+msip:alice\@127\.0\.0\.1:5060'; 56 | ok ($output =~ m/$regex/m, "Basic OPTIONS Request with colored output."); 57 | 58 | $regex = '^(\x1b\[[0-9;]+m){2}SIP/2.0 (\x1b\[[0-9;]+m)+200'; 59 | ok ($output =~ m/$regex/m, "Basic OPTIONS Response with colored output."); 60 | 61 | # Header User-Agent add 62 | system("$sipp $sippargs -sf $scenario"); 63 | $output = `$sippak --user-agent="SIP UA 1.x.x" sip:alice\@127.0.0.1:5060`; 64 | 65 | $regex = '^User-Agent: SIP UA 1.x.x$'; 66 | ok ($output =~ m/$regex/m, "Add User-Agent header."); 67 | 68 | # custom header 69 | system("$sipp $sippargs -sf $scenario"); 70 | $output = `$sippak --header="X-Foo: Bar header" sip:alice\@127.0.0.1:5060`; 71 | 72 | $regex = '^X-Foo: Bar header$'; 73 | ok ($output =~ m/$regex/m, "Add custom header."); 74 | 75 | done_testing(); 76 | -------------------------------------------------------------------------------- /tests/unit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | 21 | function (ADD_CMOCKA_TEST test_name test_src) 22 | add_executable(${test_name} ${test_src} ${ARGV}) 23 | target_link_libraries(${test_name} ${CMOCKA_LIBRARIES} ${PJSIP_LIBRARIES} ${EXTRA_LIBS}) 24 | add_test(unit_${test_name} ${test_name}) 25 | if (USE_MEMTEST) 26 | add_test (${test_name}_valgrind valgrind 27 | --error-exitcode=1 --read-var-info=yes 28 | --leak-check=full --show-leak-kinds=all 29 | ./${test_name} 30 | ) 31 | endif (USE_MEMTEST) 32 | endfunction (ADD_CMOCKA_TEST) 33 | 34 | # test CLI arguments 35 | add_cmocka_test(test_getopts test_getopts.c 36 | ${CMAKE_SOURCE_DIR}/src/app/media_helper.c 37 | ${CMAKE_SOURCE_DIR}/src/app/getopts.c) 38 | 39 | # use bogus resolv.conf for tests 40 | add_cmocka_test(test_dns_helper test_dns_helper.c 41 | ${CMAKE_SOURCE_DIR}/src/app/media_helper.c 42 | ${CMAKE_SOURCE_DIR}/src/app/getopts.c 43 | ${CMAKE_SOURCE_DIR}/src/app/dns.c 44 | ) 45 | target_link_libraries (test_dns_helper resolv) 46 | 47 | # test sip helper functions 48 | add_cmocka_test(test_sip_helper test_sip_helper.c 49 | ${CMAKE_SOURCE_DIR}/src/app/media_helper.c 50 | ${CMAKE_SOURCE_DIR}/src/app/getopts.c 51 | ${CMAKE_SOURCE_DIR}/src/app/sip_helper.c 52 | ) 53 | 54 | # test media helper functions 55 | add_definitions(-DPJMEDIA_HAS_SPEEX_CODEC 56 | -DPJMEDIA_HAS_ILBC_CODEC 57 | -DPJMEDIA_HAS_GSM_CODEC 58 | -DPJMEDIA_HAS_G711_CODEC 59 | -DPJMEDIA_HAS_G722_CODEC 60 | -DPJMEDIA_HAS_INTEL_IPP 61 | -DPJMEDIA_HAS_L16_CODEC 62 | -DPJMEDIA_HAS_OPENCORE_AMRNB_CODEC 63 | -DPJMEDIA_HAS_SILK_CODEC 64 | -DPJMEDIA_HAS_OPUS_CODEC 65 | -DPJMEDIA_HAS_BCG729 66 | -DSIPPAK_UNIT_TESTS 67 | ) 68 | add_cmocka_test(test_media_helper test_media_helper.c 69 | ${CMAKE_SOURCE_DIR}/src/app/media_helper.c 70 | ) 71 | 72 | -------------------------------------------------------------------------------- /tests/acceptance/test.message_basic.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command and timeout in 10 transactions 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Socket; 9 | use Cwd qw(abs_path); 10 | use File::Basename; 11 | use Test::More; 12 | 13 | my $sippak = $ARGV[0]; 14 | my $sipp = $ARGV[1]; 15 | my $scenario = $ARGV[2] . "/message.basic.xml"; 16 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 17 | my $output = ""; 18 | my $regex = ""; 19 | 20 | # run sippak message requires parameter --body 21 | $output = `$sippak message sip:alice\@127.0.0.1:5060`; 22 | 23 | $regex = 'Failed. Requires parameter "--body" to initiate MESSAGE.'; 24 | ok ($output =~ m/$regex/m, "Message method fails when no param --body set."); 25 | 26 | # run sipp scenario in background mode 27 | system("$sipp $sippargs -sf $scenario"); 28 | if ($? == -1) { 29 | print "Failed execute sipp\n"; 30 | exit(1); 31 | } 32 | 33 | # refer click to dial send 34 | $output = `$sippak message --body="Hello, there! Ca va?" sip:alice\@127.0.0.1:5060`; 35 | 36 | $regex = '^MESSAGE sip:alice\@127\.0\.0\.1:5060 SIP\/2.0$'; 37 | ok ($output =~ m/$regex/m, "Basic MESSAGE packet sent."); 38 | 39 | $regex = '^Content-Type: text\/plain$'; 40 | ok ($output =~ m/$regex/m, "Basic MESSAGE content type is text/plain."); 41 | 42 | $regex = '^Content-Length: 20$'; 43 | ok ($output =~ m/$regex/m, "Basic MESSAGE has valid content-length value."); 44 | 45 | $regex = '^Hello, there! Ca va\?$'; 46 | ok ($output =~ m/$regex/m, "Basic MESSAGE has good body text."); 47 | 48 | $regex = '^SIP\/2\.0 200 OK Basic Message Test$'; 49 | ok ($output =~ m/$regex/m, "Basic MESSAGE has valid response."); 50 | 51 | # long message is cut to 128 char 52 | system("$sipp $sippargs -sf $scenario"); 53 | 54 | my $body = "Instant Messaging refers to the transfer of messages between "; 55 | $body .= "users in near realtime. These messages are usually, but not "; 56 | $body .= "required to be, short. IMs are often used in a conversational mode, "; 57 | $body .= "that is, the transfer of messages back and forth is fast enough for "; 58 | $body .= "participants to maintain an interactive conversation."; 59 | $output = `$sippak message --body="$body" sip:alice\@127.0.0.1:5060`; 60 | 61 | $regex = 'Message body is too long. Max allowed characters is 128'; 62 | ok ($output =~ m/$regex/m, "Basic MESSAGE with too long body warning."); 63 | 64 | $regex = '^Instant Messaging refers to the transfer of messages between users in near realtime. These messages are usually, but not require$'; 65 | ok ($output =~ m/$regex/m, "Basic MESSAGE with too long body is trimmed."); 66 | 67 | # Header User-Agent add 68 | system("$sipp $sippargs -sf $scenario"); 69 | $output = `$sippak message --body="Hello World" --user-agent="sippak" sip:alice\@127.0.0.1:5060`; 70 | 71 | $regex = '^User-Agent: sippak$'; 72 | ok ($output =~ m/$regex/m, "Add User-Agent header."); 73 | 74 | done_testing(); 75 | -------------------------------------------------------------------------------- /tests/acceptance/test.subscribe_basic.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak subscribe method SIP flow 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/subscribe.basic.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak publish basic 27 | $output = `$sippak SUBSCRIBE sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^SUBSCRIBE sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 31 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE packet sent."); 32 | 33 | $regex = '^Event: presence$'; 34 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE Event header."); 35 | 36 | # test response 37 | $regex = '^SIP\/2\.0 200 OK Basic SUBSCRIBE Test$'; 38 | ok ($output =~ m/$regex/m, "Basic SUBSCRIBE Response 200 OK received."); 39 | 40 | # invalid event setup 41 | system("$sipp $sippargs -sf $scenario"); 42 | $output = `$sippak SUBSCRIBE -E foo sip:alice\@127.0.0.1:5060`; 43 | 44 | $regex = '^Event: presence$'; 45 | ok ($output =~ m/$regex/m, "Subscription event unknown use presence."); 46 | 47 | $regex = 'Presence event "foo" is not supported. Will use "presence"'; 48 | ok ($output =~ m/$regex/m, "Subscription event unknown use presence warning."); 49 | 50 | # expires setup 51 | system("$sipp $sippargs -sf $scenario"); 52 | $output = `$sippak SUBSCRIBE -X 654 sip:alice\@127.0.0.1:5060`; 53 | 54 | $regex = '^Expires: 654$'; 55 | ok ($output =~ m/$regex/m, "Subscription expires header setup."); 56 | 57 | # Header User-Agent add 58 | system("$sipp $sippargs -sf $scenario"); 59 | $output = `$sippak SUBSCRIBE --user-agent="SIP SUB 1.1" sip:alice\@127.0.0.1:5060`; 60 | 61 | $regex = '^User-Agent: SIP SUB 1.1$'; 62 | ok ($output =~ m/$regex/m, "Add User-Agent header."); 63 | 64 | # Custom headers add 65 | system("$sipp $sippargs -sf $scenario"); 66 | $output = `$sippak SUBSCRIBE --header="X-SIP-foo: Subscriber" sip:alice\@127.0.0.1:5060`; 67 | 68 | $regex = '^X-SIP-foo: Subscriber$'; 69 | ok ($output =~ m/$regex/m, "Add custom header."); 70 | 71 | # TODO: Set outbound proxy for SUBSCRIBE and Route headers 72 | # # Outbound proxy set 73 | # system("$sipp $sippargs -sf $scenario"); 74 | # $output = `$sippak SUBSCRIBE --proxy=sip:127.0.0.1:5060 sip:alice\@sip.sub.pbx`; 75 | # 76 | # $regex = '^SUBSCRIBE sip:alice\@sip.sub.pbx SIP\/2\.0$'; 77 | # ok ($output =~ m/$regex/m, "Use outbound proxy."); 78 | # 79 | # # Route headers with multiple proxies 80 | # system("$sipp $sippargs -sf $scenario"); 81 | # $output = `$sippak SUBSCRIBE -R sip:127.0.0.1:5060 -R sips:router01.sip.local:5856 sip:alice\@sip.home.pbx`; 82 | # 83 | # $regex = '^Route: $'; 84 | # ok ($output =~ m/$regex/m, "Route header for second proxy."); 85 | 86 | done_testing(); 87 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/subscribe.terminated.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 44 | 45 | 46 | 47 | 48 | ;tag=2d87f2a5aac2daedab4d366e4bc2064e.8aa7 53 | To[$3] 54 | Call-ID: [call_id] 55 | Cseq: 1 NOTIFY 56 | Contact: sip:presence-server@127.0.0.1:5060 57 | Max-Forwards: 70 58 | Event: presence 59 | Subscription-State: terminated;reason=timeout 60 | Content-Type: application/pidf+xml 61 | Content-Length: 0 62 | 63 | ]]> 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/invite.cancel.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 39 | Content-Length: 0 40 | 41 | ]]> 42 | 43 | 44 | 45 | 46 | 47 | 48 | 59 | 60 | 61 | 62 | 73 | 74 | 75 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/subscribe.mwi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 44 | 45 | 46 | 47 | 48 | ;tag=2d87f2a5aac2daedab4d366e4bc2064e.8aa7 53 | To[$3] 54 | Call-ID: [call_id] 55 | Cseq: 1 NOTIFY 56 | Contact: sip:presence-server@127.0.0.1:5060 57 | Max-Forwards: 70 58 | Event: message-summary 59 | Subscription-State: active;expires=3600 60 | Content-Type: application/pidf+xml 61 | Content-Length: 546 62 | 63 | Messages-Waiting: yes 64 | Message-Account: sip:*97@cc.humark.clusterpbx.ca 65 | Voice-Message: 5/0 (0/0) 66 | 67 | ]]> 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at staskobzar@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /tests/acceptance/test.ping_src_port_host.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command with custom port and username 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/options.basic.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak ping basic with source port option 27 | # short option 28 | $output = `$sippak -P 8899 sip:alice\@127.0.0.1:5060`; 29 | 30 | # test request 31 | $regex = '^Contact: \$'; 32 | ok ($output =~ m/$regex/m, "Contact header has correct port with short option."); 33 | 34 | $regex = '^Via: SIP\/2\.0\/UDP [^:]+:8899;'; 35 | ok ($output =~ m/$regex/m, "Via header has correct port with short option."); 36 | 37 | # long option 38 | system("$sipp $sippargs -sf $scenario"); 39 | $output = `$sippak --local-port=8899 sip:alice\@127.0.0.1:5060`; 40 | 41 | # test request 42 | $regex = '^Contact: $'; 43 | ok ($output =~ m/$regex/m, "Contact header has correct port with long option."); 44 | 45 | $regex = '^Via: SIP\/2\.0\/UDP [^:]+:8899;'; 46 | ok ($output =~ m/$regex/m, "Via header has correct port with short option."); 47 | 48 | # username long param 49 | system("$sipp $sippargs -sf $scenario"); 50 | $output = `$sippak --username=bob sip:alice\@127.0.0.1:5060`; 51 | 52 | $regex = '^Contact: ]+>$'; 53 | ok ($output =~ m/$regex/m, "Username in Contact header short opt."); 54 | $regex = '^From: ]+>;tag='; 55 | ok ($output =~ m/$regex/m, "Username in From header short opt."); 56 | 57 | # username short param 58 | system("$sipp $sippargs -sf $scenario"); 59 | $output = `$sippak -u john -P 8889 sip:alice\@127.0.0.1:5060`; 60 | 61 | $regex = '^Contact: $'; 62 | ok ($output =~ m/$regex/m, "Username in Contact header long opt."); 63 | $regex = '^From: ;tag='; 64 | ok ($output =~ m/$regex/m, "Username in From header long opt."); 65 | 66 | # hostname setup short param 67 | # NOTE! On MacOS enable interface before test: 68 | # sudo ifconfig lo0 alias 127.0.0.8 up 69 | system("$sipp $sippargs -sf $scenario"); 70 | $output = `$sippak -u john -l 127.0.0.8 -P 9988 sip:alice\@127.0.0.1:5060`; 71 | 72 | $regex = '^Contact: $'; 73 | ok ($output =~ m/$regex/m, "Host in Contact header short opt."); 74 | 75 | # hostname setup long param 76 | # NOTE! On MacOS enable interface before test: 77 | # sudo ifconfig lo0 alias 127.0.0.18 up 78 | system("$sipp $sippargs -sf $scenario"); 79 | $output = `$sippak -u john --local-host 127.0.0.18 -P 9977 sip:alice\@127.0.0.1:5060`; 80 | 81 | $regex = '^Contact: $'; 82 | ok ($output =~ m/$regex/m, "Host in Contact header long opt."); 83 | 84 | # from display name long option 85 | system("$sipp $sippargs -sf $scenario"); 86 | $output = `$sippak -u 5554477544 --from-name="Gianni Schicchi" -P 9977 sip:alice\@127.0.0.1:5060`; 87 | 88 | $regex = '^From: "Gianni Schicchi" '; 89 | ok ($output =~ m/$regex/m, "From header display name long option."); 90 | 91 | # from display name short option 92 | system("$sipp $sippargs -sf $scenario"); 93 | $output = `$sippak -u 5555 -F Lauretta sip:alice\@127.0.0.1:5060`; 94 | 95 | $regex = '^From: "Lauretta" '; 96 | ok ($output =~ m/$regex/m, "From header display name short option."); 97 | 98 | done_testing(); 99 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/subscribe.basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 44 | 45 | 46 | 47 | 48 | ;tag=2d87f2a5aac2daedab4d366e4bc2064e.8aa7 53 | To[$3] 54 | Call-ID: [call_id] 55 | Cseq: 1 NOTIFY 56 | Contact: sip:presence-server@127.0.0.1:5060 57 | Max-Forwards: 70 58 | Event: presence 59 | Subscription-State: active;expires=321 60 | Content-Type: application/pidf+xml 61 | Content-Length: 546 62 | 63 | 64 | 65 | 66 | 67 | closed 68 | 69 | 2018-05-20T10:34:20.518Z 70 | Out for lunch 71 | 72 | 73 | 74 | 75 | 76 | Out for lunch 77 | 78 | 79 | 80 | ]]> 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/register.auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 99 | 100 | -------------------------------------------------------------------------------- /tests/unit/test_media_helper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include "sippak.h" 8 | 9 | static void support_media_codec (void **state) 10 | { 11 | (void) *state; 12 | assert_int_equal(PJ_TRUE, sippak_support_codec(SIPPAK_CODEC_SPEEX)); 13 | assert_int_equal(PJ_TRUE, sippak_support_codec(SIPPAK_CODEC_G711)); 14 | assert_int_equal(PJ_FALSE, sippak_support_codec(SIPPAK_CODEC_G7221)); 15 | } 16 | 17 | static void set_codecs_array_ordered (void **state) 18 | { 19 | (void) *state; 20 | pj_status_t status; 21 | struct sippak_app app; 22 | 23 | status = sippak_set_media_codecs_cfg ("silk,g711,speex", &app); 24 | assert_int_equal (PJ_SUCCESS, status); 25 | assert_int_equal (app.cfg.media.cnt, 3); 26 | assert_int_equal (app.cfg.media.codec[0], SIPPAK_CODEC_SILK); 27 | assert_int_equal (app.cfg.media.codec[1], SIPPAK_CODEC_G711); 28 | assert_int_equal (app.cfg.media.codec[2], SIPPAK_CODEC_SPEEX); 29 | } 30 | 31 | static void invalid_codec_value_given (void **state) 32 | { 33 | (void) *state; 34 | pj_status_t status; 35 | struct sippak_app app; 36 | 37 | status = sippak_set_media_codecs_cfg ("silk,foo,speex", &app); 38 | assert_int_equal (PJ_CLI_EINVARG, status); 39 | } 40 | 41 | static void set_use_all_codecs (void **state) 42 | { 43 | (void) *state; 44 | pj_status_t status; 45 | struct sippak_app app; 46 | 47 | status = sippak_set_media_codecs_cfg ("all", &app); 48 | assert_int_equal (PJ_SUCCESS, status); 49 | assert_int_equal (NUM_CODECS_AVAIL, app.cfg.media.cnt); 50 | } 51 | 52 | static void set_use_all_codecs_from_mix (void **state) 53 | { 54 | (void) *state; 55 | pj_status_t status; 56 | struct sippak_app app; 57 | 58 | status = sippak_set_media_codecs_cfg ("g711,all,silk", &app); 59 | assert_int_equal (PJ_SUCCESS, status); 60 | assert_int_equal (NUM_CODECS_AVAIL, app.cfg.media.cnt); 61 | } 62 | 63 | static void fail_on_duplicated_codec (void **state) 64 | { 65 | (void) *state; 66 | pj_status_t status; 67 | struct sippak_app app; 68 | 69 | status = sippak_set_media_codecs_cfg ("g711,opus,silk,opus,G711", &app); 70 | assert_int_equal (PJ_SUCCESS, status); 71 | assert_int_equal (app.cfg.media.cnt, 3); 72 | assert_int_equal (app.cfg.media.codec[0], SIPPAK_CODEC_G711); 73 | assert_int_equal (app.cfg.media.codec[1], SIPPAK_CODEC_OPUS); 74 | assert_int_equal (app.cfg.media.codec[2], SIPPAK_CODEC_SILK); 75 | } 76 | 77 | static void parse_all_codecs_icase (void **state) 78 | { 79 | (void) *state; 80 | pj_status_t status; 81 | struct sippak_app app; 82 | char *codecs = "speex,ILbc,gsm,G711,g722,IPP,l16,AMR,silk,opus,bcg729"; 83 | app.cfg.media.cnt = 0; 84 | 85 | status = sippak_set_media_codecs_cfg (codecs, &app); 86 | assert_int_equal (PJ_SUCCESS, status); 87 | assert_int_equal (app.cfg.media.cnt, 11); 88 | assert_int_equal (app.cfg.media.codec[0], SIPPAK_CODEC_SPEEX); 89 | assert_int_equal (app.cfg.media.codec[1], SIPPAK_CODEC_ILBC); 90 | assert_int_equal (app.cfg.media.codec[2], SIPPAK_CODEC_GSM); 91 | assert_int_equal (app.cfg.media.codec[3], SIPPAK_CODEC_G711); 92 | assert_int_equal (app.cfg.media.codec[4], SIPPAK_CODEC_G722); 93 | assert_int_equal (app.cfg.media.codec[5], SIPPAK_CODEC_IPP); 94 | assert_int_equal (app.cfg.media.codec[6], SIPPAK_CODEC_L16); 95 | assert_int_equal (app.cfg.media.codec[7], SIPPAK_CODEC_OCAMR); 96 | assert_int_equal (app.cfg.media.codec[8], SIPPAK_CODEC_SILK); 97 | assert_int_equal (app.cfg.media.codec[9], SIPPAK_CODEC_OPUS); 98 | assert_int_equal (app.cfg.media.codec[10], SIPPAK_CODEC_BCG729); 99 | } 100 | 101 | int main(int argc, const char *argv[]) 102 | { 103 | pj_log_set_level(0); // do not print pj debug on init 104 | const struct CMUnitTest tests[] = { 105 | cmocka_unit_test(support_media_codec), 106 | cmocka_unit_test(set_codecs_array_ordered), 107 | cmocka_unit_test(invalid_codec_value_given), 108 | cmocka_unit_test(set_use_all_codecs), 109 | cmocka_unit_test(set_use_all_codecs_from_mix), 110 | cmocka_unit_test(parse_all_codecs_icase), 111 | cmocka_unit_test(fail_on_duplicated_codec), 112 | }; 113 | return cmocka_run_group_tests_name("Media helper", tests, NULL, NULL); 114 | } 115 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/message.auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 104 | 105 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/notify.auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 103 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/refer.auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 105 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/options.auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 103 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/publish.auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 103 | -------------------------------------------------------------------------------- /tests/acceptance/test.register_no_auth.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak NOTIFY command with presence info and check-sync 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/register.no_auth.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak notify basic presence with pidf 27 | $output = `$sippak register sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^REGISTER sip:127\.0\.0\.1:5060 SIP\/2.0$'; 31 | ok ($output =~ m/$regex/m, "Basic REGISTER packet sent."); 32 | 33 | $regex = '^SIP\/2\.0 200 OK Register no auth$'; 34 | ok ($output =~ m/$regex/m, "Basic REGISTER register confirmed."); 35 | 36 | # contact header set 37 | system("$sipp $sippargs -sf $scenario"); 38 | $output = `$sippak register --contact=sip:john\@192.168.12.14:8798 sip:alice\@127.0.0.1:5060`; 39 | 40 | $regex = '^REGISTER sip:127\.0\.0\.1:5060 SIP\/2.0$'; 41 | ok ($output =~ m/$regex/m, "REGISTER with contact packet sent."); 42 | 43 | $regex = '^Contact: '; 44 | ok ($output =~ m/$regex/m, "REGISTER with contact has valid value."); 45 | 46 | # rfc3665 request current contact list 47 | system("$sipp $sippargs -sf $scenario"); 48 | $output = `$sippak register --clist sip:alice\@127.0.0.1:5060`; 49 | 50 | my @lines = split(/\n/, $output); 51 | my $pack = ''; 52 | foreach (@lines) { 53 | if(/^REGISTER/../\.RX/) { 54 | $pack .= "$_\n"; 55 | } 56 | } 57 | $regex = '^REGISTER sip:127\.0\.0\.1:5060 SIP\/2.0$'; 58 | ok ($pack =~ m/$regex/m, "REGISTER packet sent for contact list."); 59 | 60 | ok ($pack !~ m/Contact: /m, "No Contact header in list request"); 61 | 62 | # rfc3665 cancel all registrations 63 | system("$sipp $sippargs -sf $scenario"); 64 | $output = `$sippak register --cancel-all sip:alice\@127.0.0.1:5060`; 65 | 66 | $regex = '^REGISTER sip:127\.0\.0\.1:5060 SIP\/2.0$'; 67 | ok ($output =~ m/$regex/m, "REGISTER cancel all packet sent."); 68 | 69 | $regex = '^Contact: \*$'; 70 | ok ($output =~ m/$regex/m, "Cancel all registartions contact header is asterisk."); 71 | 72 | $regex = '^Expires: 0$'; 73 | ok ($output =~ m/$regex/m, "Cancel all registartions expires header is zero."); 74 | 75 | # cancel particular contact 76 | system("$sipp $sippargs -sf $scenario"); 77 | $output = `$sippak register -c sip:john\@10.12.123.102:5060 --cancel sip:alice\@127.0.0.1:5060`; 78 | 79 | $regex = '^REGISTER sip:127\.0\.0\.1:5060 SIP\/2.0$'; 80 | ok ($output =~ m/$regex/m, "REGISTER with contact registration cancel packet is sent."); 81 | 82 | $regex = '^Contact: '; 83 | ok ($output =~ m/$regex/m, "REGISTER with contact cancellation has valid value."); 84 | 85 | $regex = '^Expires: 0$'; 86 | ok ($output =~ m/$regex/m, "Cancel contact registartions expires header is zero."); 87 | 88 | # Header User-Agent add 89 | system("$sipp $sippargs -sf $scenario"); 90 | $output = `$sippak register -A "sippak UA 1.0" sip:alice\@127.0.0.1:5060`; 91 | 92 | $regex = '^User-Agent: sippak UA 1.0$'; 93 | ok ($output =~ m/$regex/m, "Add User-Agent header."); 94 | 95 | # Custom headers add 96 | system("$sipp $sippargs -sf $scenario"); 97 | $output = `$sippak REGISTER --header="X-PID: 1235:55" sip:alice\@127.0.0.1:5060`; 98 | 99 | $regex = '^X-PID: 1235.55$'; 100 | ok ($output =~ m/$regex/m, "Add custom header."); 101 | 102 | # Proxy set 103 | system("$sipp $sippargs -sf $scenario"); 104 | $output = `$sippak REGISTER --proxy=sip:127.0.0.1:5060 sip:alice\@host.sip.local`; 105 | 106 | $regex = '^REGISTER sip:host.sip.local SIP\/2.0$'; 107 | ok ($output =~ m/$regex/m, "Register send to proxy IP"); 108 | 109 | # Proxy set multi Routes 110 | system("$sipp $sippargs -sf $scenario"); 111 | $output = `$sippak REGISTER -R sip:127.0.0.1:5060 -R sip:foo.bar:8585 sip:alice\@sip.local.pbx`; 112 | 113 | $regex = '^REGISTER sip:sip.local.pbx SIP\/2.0$'; 114 | ok ($output =~ m/$regex/m, "Register send with multi proxies."); 115 | 116 | $regex = '^Route: $'; 117 | ok ($output =~ m/$regex/m, "Register with Route header."); 118 | 119 | done_testing(); 120 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/invite.basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 50 | Content-Length: 0 51 | 52 | ]]> 53 | 54 | 55 | 56 | 65 | Content-Type: application/sdp 66 | Content-Length: [len] 67 | 68 | v=0 69 | o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] 70 | s=- 71 | c=IN IP[media_ip_type] [media_ip] 72 | t=0 0 73 | m=audio [media_port] RTP/AVP 0 74 | a=rtpmap:0 PCMU/8000 75 | 76 | ]]> 77 | 78 | 79 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 98 | Content-Length: 0 99 | 100 | ]]> 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/invite.proxy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 50 | Content-Length: 0 51 | 52 | ]]> 53 | 54 | 55 | 56 | 65 | Content-Type: application/sdp 66 | Content-Length: [len] 67 | 68 | v=0 69 | o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] 70 | s=- 71 | c=IN IP[media_ip_type] [media_ip] 72 | t=0 0 73 | m=audio [media_port] RTP/AVP 0 74 | a=rtpmap:0 PCMU/8000 75 | 76 | ]]> 77 | 78 | 79 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 98 | Content-Length: 0 99 | 100 | ]]> 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * sippak -- SIP command line utility. 3 | * Copyright (C) 2018, Stas Kobzar 4 | * 5 | * This file is part of sippak. 6 | * 7 | * sippak is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * sippak is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with sippak. If not, see . 19 | */ 20 | 21 | /** 22 | * @file main.c 23 | * @brief SIP command line utility main file. 24 | * 25 | * @author Stas Kobzar 26 | */ 27 | 28 | #include "sippak.h" 29 | 30 | static pj_bool_t sippak_loop_stop = PJ_FALSE; 31 | struct sippak_app app; 32 | 33 | static void sippak_main_loop() 34 | { 35 | while (sippak_loop_stop == PJ_FALSE) { 36 | pj_time_val timeout = {0, 500}; 37 | pjsip_endpt_handle_events(app.endpt, &timeout); 38 | } 39 | } 40 | 41 | PJ_DEF(void) sippak_loop_cancel() 42 | { 43 | sippak_loop_stop = PJ_TRUE; 44 | } 45 | 46 | int main(int argc, char *argv[]) 47 | { 48 | pj_status_t status; 49 | pj_caching_pool cp; 50 | 51 | 52 | pj_log_set_level(MIN_LOG_LEVEL); 53 | status = pj_init(); 54 | SIPPAK_ASSERT_SUCC(status, "Failed to init PJSIP."); 55 | 56 | status = sippak_init(&app); 57 | SIPPAK_ASSERT_SUCC(status, "Failed to init SIPPAK."); 58 | 59 | pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); 60 | app.cp = &cp; 61 | 62 | status = pjsip_endpt_create(&cp.factory, PROJECT_NAME, &app.endpt); 63 | SIPPAK_ASSERT_SUCC(status, "Failed to create end point."); 64 | 65 | app.pool = pjsip_endpt_create_pool(app.endpt, PROJECT_NAME, POOL_INIT, POOL_INCR); 66 | 67 | status = sippak_getopts(argc, argv, &app); 68 | SIPPAK_ASSERT_SUCC(status, "Failed to process parameters."); 69 | 70 | status = sippak_mod_logger_register(&app); 71 | SIPPAK_ASSERT_SUCC(status, "Failed to register logger module."); 72 | 73 | status = sippak_set_resolver_ns (&app); 74 | SIPPAK_ASSERT_SUCC(status, "Failed to set DNS resolvers."); 75 | 76 | status = sippak_mod_sip_mangler_register (&app); 77 | SIPPAK_ASSERT_SUCC(status, "Failed to register SIP mangler module."); 78 | 79 | // run 80 | switch (app.cfg.cmd) 81 | { 82 | case CMD_VERSION: 83 | version (); 84 | goto done; 85 | break; 86 | case CMD_HELP: 87 | usage (); 88 | goto done; 89 | break; 90 | case CMD_PING: 91 | status = sippak_cmd_ping(&app); 92 | SIPPAK_ASSERT_SUCC(status, "Failed PING command."); 93 | break; 94 | case CMD_PUBLISH: 95 | status = sippak_cmd_publish(&app); 96 | SIPPAK_ASSERT_SUCC(status, "Failed PUBLISH command."); 97 | break; 98 | case CMD_SUBSCRIBE: 99 | status = sippak_cmd_subscribe(&app); 100 | SIPPAK_ASSERT_SUCC(status, "Failed SUBSCRIBE command."); 101 | break; 102 | case CMD_NOTIFY: 103 | status = sippak_cmd_notify(&app); 104 | SIPPAK_ASSERT_SUCC(status, "Failed NOTIFY command."); 105 | break; 106 | case CMD_REGISTER: 107 | status = sippak_cmd_register(&app); 108 | SIPPAK_ASSERT_SUCC(status, "Failed REGISTER command."); 109 | break; 110 | case CMD_REFER: 111 | status = sippak_cmd_refer(&app); 112 | SIPPAK_ASSERT_SUCC(status, "Failed REFER command."); 113 | break; 114 | case CMD_MESSAGE: 115 | status = sippak_cmd_message(&app); 116 | SIPPAK_ASSERT_SUCC(status, "Failed MESSAGE command."); 117 | break; 118 | case CMD_INVITE: 119 | status = sippak_cmd_invite(&app); 120 | SIPPAK_ASSERT_SUCC(status, "Failed INVITE command."); 121 | break; 122 | 123 | // fail 124 | case CMD_UNKNOWN: 125 | default: 126 | PJ_LOG(1,(PROJECT_NAME, "Invalid or missing parameters. Use --help/-h for usage message.")); 127 | status = PJ_EINVAL; 128 | goto done; 129 | break; 130 | } 131 | // main loop 132 | sippak_main_loop(); 133 | 134 | done: 135 | pj_caching_pool_destroy(&cp); 136 | 137 | return status; 138 | } 139 | -------------------------------------------------------------------------------- /tests/acceptance/CMakeSIPPScenarios.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # sippak -- SIP command line utility. 3 | # Copyright (C) 2018, Stas Kobzar 4 | # 5 | # This file is part of sippak. 6 | # 7 | # sippack is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # sippack is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with sippack. If not, see . 19 | # 20 | 21 | set (SIPP_SCENARIO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/sipp_scenarios) 22 | 23 | add_test ( Ping_With_OPTIONS 24 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.ping_options.pl 25 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 26 | 27 | add_test ( Ping_over_TCP 28 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.ping_options_tcp.pl 29 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 30 | 31 | option (TEST_TIMEOUT "Test timeout SIP. Takes around 30 sec." OFF) 32 | if (TEST_TIMEOUT) 33 | add_test ( Ping_Timeoute_Retrans 34 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.ping_retrans.pl 35 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 36 | endif(TEST_TIMEOUT) 37 | 38 | add_test ( Ping_Source_Port_Username 39 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.ping_src_port_host.pl 40 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 41 | 42 | add_test ( Ping_Proxy_Auth 43 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.ping_auth.pl 44 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 45 | 46 | add_test ( PUBLISH_method 47 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.publish_basic.pl 48 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 49 | 50 | add_test ( PUBLISH_auth 51 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.publish_auth.pl 52 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 53 | 54 | add_test ( SUBSCRIBE_method 55 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.subscribe_basic.pl 56 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 57 | 58 | add_test ( SUBSCRIBE_terminate_subscription 59 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.subscribe_terminate.pl 60 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 61 | 62 | add_test ( SUBSCRIBE_auth_request 63 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.subscribe_auth.pl 64 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 65 | 66 | add_test ( SUBSCRIBE_MWI_request 67 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.subscribe_mwi.pl 68 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 69 | 70 | add_test ( NOTIFY_method_send 71 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.notify_basic.pl 72 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 73 | 74 | add_test ( NOTIFY_auth_request 75 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.notify_auth.pl 76 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 77 | 78 | add_test ( REGISTER_method_send 79 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.register_no_auth.pl 80 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 81 | 82 | add_test ( REGISTER_method_auth 83 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.register_auth.pl 84 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 85 | 86 | add_test ( REFER_method_send 87 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.refer_basic.pl 88 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 89 | 90 | add_test ( REFER_method_auth 91 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.refer_auth.pl 92 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 93 | 94 | add_test ( MESSAGE_method_send 95 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.message_basic.pl 96 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 97 | 98 | add_test ( MESSAGE_method_auth 99 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.message_auth.pl 100 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 101 | 102 | add_test ( INVITE_method_send 103 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.invite_basic.pl 104 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 105 | 106 | add_test ( INVITE_method_auth 107 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.invite_auth.pl 108 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 109 | 110 | add_test ( INVITE_method_cancel 111 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.invite_cancel.pl 112 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 113 | 114 | add_test ( INVITE_method_with_proxy 115 | perl ${CMAKE_CURRENT_SOURCE_DIR}/test.invite_proxy.pl 116 | ${EXECMD} ${SIPP} ${SIPP_SCENARIO_PATH}) 117 | 118 | -------------------------------------------------------------------------------- /tests/acceptance/test.invite_basic.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak basic invite SIP flow 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/invite.basic.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak publish basic 27 | $output = `$sippak INVITE sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^INVITE sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 31 | ok ($output =~ m/$regex/m, "Basic INVITE packet sent."); 32 | 33 | $regex = '^SIP\/2.0 180 Ringing$'; 34 | ok ($output =~ m/$regex/m, "Ringing 180 early response."); 35 | 36 | $regex = '^SIP\/2.0 200 OK Invite$'; 37 | ok ($output =~ m/$regex/m, "Invite session is confirmed."); 38 | 39 | $regex = '^ACK sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 40 | ok ($output =~ m/$regex/m, "Invite 200 in acknowlaged."); 41 | 42 | $regex = '^BYE sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 43 | ok ($output =~ m/$regex/m, "Session is terminated with BYE."); 44 | 45 | $regex = '^SIP\/2.0 200 OK bye$'; 46 | ok ($output =~ m/$regex/m, "BYE method is confirmed."); 47 | 48 | # default codecs are ulaw and alaw 49 | $regex = '^a=rtpmap:0 PCMU\/8000'; 50 | ok ($output =~ m/$regex/m, "Default codec mu-law code used."); 51 | 52 | $regex = '^a=rtpmap:8 PCMA\/8000'; 53 | ok ($output =~ m/$regex/m, "Default codec a-law code used."); 54 | 55 | $regex = '^m=audio \d+ RTP\/AVP 0 8'; 56 | ok ($output =~ m/$regex/m, "Default codec order is 0, 8 (pcmu,pcma)."); 57 | 58 | # speex codec set 59 | system("$sipp $sippargs -sf $scenario"); 60 | $output = `$sippak INVITE --codec=speex sip:alice\@127.0.0.1:5060`; 61 | 62 | $regex = 'a=rtpmap:99 speex\/32000'; 63 | ok ($output =~ m/$regex/m, "Speex codec ultra-wideband is set."); 64 | 65 | $regex = 'a=rtpmap:98 speex\/16000'; 66 | ok ($output =~ m/$regex/m, "Speex codec wideband is set."); 67 | 68 | $regex = 'a=rtpmap:97 speex\/8000'; 69 | ok ($output =~ m/$regex/m, "Speex codec narrowband is set."); 70 | 71 | $regex = '^a=rtpmap:0 PCMU\/8000$'; 72 | ok ($output !~ m/$regex/m, "When only speex is set, no mu-law is set."); 73 | 74 | $regex = '^m=audio \d+ RTP\/AVP 99 98 97'; 75 | ok ($output =~ m/$regex/m, "Default codec order is 0, 8 (pcmu,pcma)."); 76 | 77 | $regex = '^m=audio 4000 RTP\/AVP 99 98 97'; 78 | ok ($output =~ m/$regex/m, "Default port is 4000."); 79 | 80 | # test set RTP port 81 | system("$sipp $sippargs -sf $scenario"); 82 | $output = `$sippak INVITE --rtp-port=10008 sip:alice\@127.0.0.1:5060`; 83 | 84 | $regex = '^m=audio 10008 RTP\/AVP '; 85 | ok ($output =~ m/$regex/m, "Set RTP port to 10008."); 86 | 87 | # test when invalid port set (foo, 0, < 1, > 2^16) 88 | $output = `$sippak INVITE --rtp-port=foo sip:alice\@127.0.0.1:5060`; 89 | 90 | $regex = 'Invalid port: foo'; 91 | ok ($output =~ m/$regex/m, "Test invalid port."); 92 | 93 | $output = `$sippak INVITE --rtp-port=156456 sip:alice\@127.0.0.1:5060`; 94 | 95 | $regex = 'Invalid port: 156456. Expected value must be between 1 and 65535'; 96 | ok ($output =~ m/$regex/m, "Test invalid port value is too big."); 97 | 98 | # test when codec not supported 99 | $output = `$sippak INVITE --codec=foo sip:alice\@127.0.0.1:5060`; 100 | 101 | $regex = 'Codec "foo" is not supported.$'; 102 | ok ($output =~ m/$regex/m, "Fails when codec does not exist."); 103 | 104 | # test set all available (key word all) 105 | system("$sipp $sippargs -sf $scenario"); 106 | $output = `$sippak INVITE --codec=all sip:alice\@127.0.0.1:5060`; 107 | 108 | $regex = 'm=audio 4000 RTP\/AVP 99 98 97 104 3 0 [0-9 ]+ 96'; 109 | ok ($output =~ m/$regex/m, "Use all codecs in SDP."); 110 | 111 | # test multiple codecs order 112 | system("$sipp $sippargs -sf $scenario"); 113 | $output = `$sippak INVITE --codec=ilbc,g711,gsm sip:alice\@127.0.0.1:5060`; 114 | 115 | $regex = 'm=audio 4000 RTP/AVP 104 0 8 3 96'; 116 | ok ($output =~ m/$regex/m, "Use ordered list of codecs in SDP."); 117 | 118 | # Header User-Agent add 119 | system("$sipp $sippargs -sf $scenario"); 120 | $output = `$sippak invite -A "SIP bar" sip:alice\@127.0.0.1:5060`; 121 | 122 | $regex = '^User-Agent: SIP bar$'; 123 | ok ($output =~ m/$regex/m, "Add User-Agent header."); 124 | 125 | # Custom headers add 126 | system("$sipp $sippargs -sf $scenario"); 127 | $output = `$sippak invite -H "X-Custom: Foo Bar" sip:alice\@127.0.0.1:5060`; 128 | 129 | $regex = '^X-Custom: Foo Bar$'; 130 | ok ($output =~ m/$regex/m, "Add custom header."); 131 | 132 | done_testing(); 133 | -------------------------------------------------------------------------------- /tests/unit/test_dns_helper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sippak.h" 7 | 8 | static void argument_ns_with_single_server_no_port (void **state) 9 | { 10 | (void) *state; 11 | pj_str_t ns[MAX_NS_COUNT]; 12 | pj_uint16_t ports[MAX_NS_COUNT]; 13 | int servers_num = 0; 14 | struct sippak_app app; 15 | char *argv[] = { "./sippak", "--ns=2.2.3.4" }; 16 | int argc = sizeof(argv) / sizeof(char*); 17 | sippak_init(&app); 18 | sippak_getopts (argc, argv, &app); 19 | servers_num = sippak_get_ns_list (&app, ns, ports); 20 | assert_int_equal (1, servers_num); 21 | assert_int_equal (53, ports[0]); 22 | assert_int_equal(0, pj_strcmp2(&ns[0], "2.2.3.4")); 23 | } 24 | 25 | static void argument_ns_with_single_server_with_port (void **state) 26 | { 27 | (void) *state; 28 | pj_str_t ns[MAX_NS_COUNT]; 29 | pj_uint16_t ports[MAX_NS_COUNT]; 30 | int servers_num = 0; 31 | struct sippak_app app; 32 | char *argv[] = { "./sippak", "--ns=2.2.3.4:1553" }; 33 | int argc = sizeof(argv) / sizeof(char*); 34 | sippak_init(&app); 35 | sippak_getopts (argc, argv, &app); 36 | servers_num = sippak_get_ns_list (&app, ns, ports); 37 | assert_int_equal (1, servers_num); 38 | assert_int_equal (1553, ports[0]); 39 | assert_int_equal(0, pj_strcmp2(&ns[0], "2.2.3.4")); 40 | } 41 | 42 | static void argument_ns_with_multi_servers_no_port (void **state) 43 | { 44 | (void) *state; 45 | pj_str_t ns[MAX_NS_COUNT]; 46 | pj_uint16_t ports[MAX_NS_COUNT]; 47 | int servers_num = 0; 48 | struct sippak_app app; 49 | char *argv[] = { "./sippak", "--ns=2.2.3.4,127.0.0.1" }; 50 | int argc = sizeof(argv) / sizeof(char*); 51 | sippak_init(&app); 52 | sippak_getopts (argc, argv, &app); 53 | servers_num = sippak_get_ns_list (&app, ns, ports); 54 | assert_int_equal (2, servers_num); 55 | assert_int_equal (53, ports[0]); 56 | assert_int_equal (53, ports[1]); 57 | assert_int_equal(0, pj_strcmp2(&ns[0], "2.2.3.4")); 58 | assert_int_equal(0, pj_strcmp2(&ns[1], "127.0.0.1")); 59 | } 60 | 61 | static void argument_ns_with_multi_servers_with_ports (void **state) 62 | { 63 | (void) *state; 64 | pj_str_t ns[MAX_NS_COUNT]; 65 | pj_uint16_t ports[MAX_NS_COUNT]; 66 | int servers_num = 0; 67 | struct sippak_app app; 68 | char *argv[] = { "./sippak", "--ns=2.2.3.4:553,22.10.10.8,199.8.8.50:89" }; 69 | int argc = sizeof(argv) / sizeof(char*); 70 | sippak_init(&app); 71 | sippak_getopts (argc, argv, &app); 72 | servers_num = sippak_get_ns_list (&app, ns, ports); 73 | assert_int_equal (3, servers_num); 74 | assert_int_equal (553, ports[0]); 75 | assert_int_equal (53, ports[1]); 76 | assert_int_equal (89, ports[2]); 77 | assert_int_equal(0, pj_strcmp2(&ns[0], "2.2.3.4")); 78 | assert_int_equal(0, pj_strcmp2(&ns[1], "22.10.10.8")); 79 | assert_int_equal(0, pj_strcmp2(&ns[2], "199.8.8.50")); 80 | } 81 | 82 | static void argument_ns_respect_servers_num_limit (void **state) 83 | { 84 | (void) *state; 85 | pj_str_t ns[MAX_NS_COUNT]; 86 | pj_uint16_t ports[MAX_NS_COUNT]; 87 | int servers_num = 0; 88 | struct sippak_app app; 89 | char *argv[] = { "./sippak", "--ns=2.2.3.4,2.10.10.8,19.8.8.5,127.0.0.1,127.0.0.2,111.0.0.1" }; 90 | int argc = sizeof(argv) / sizeof(char*); 91 | sippak_init(&app); 92 | sippak_getopts (argc, argv, &app); 93 | servers_num = sippak_get_ns_list (&app, ns, ports); 94 | assert_int_equal (3, servers_num); 95 | assert_int_equal(0, pj_strcmp2(&ns[0], "2.2.3.4")); 96 | assert_int_equal(0, pj_strcmp2(&ns[1], "2.10.10.8")); 97 | assert_int_equal(0, pj_strcmp2(&ns[2], "19.8.8.5")); 98 | } 99 | 100 | static void argument_ns_server_with_invalid_port (void **state) 101 | { 102 | (void) *state; 103 | pj_str_t ns[MAX_NS_COUNT]; 104 | pj_uint16_t ports[MAX_NS_COUNT]; 105 | int servers_num = 0; 106 | struct sippak_app app; 107 | char *argv[] = { "./sippak", "--ns=2.2.3.4:abcd" }; 108 | int argc = sizeof(argv) / sizeof(char*); 109 | sippak_init(&app); 110 | sippak_getopts (argc, argv, &app); 111 | servers_num = sippak_get_ns_list (&app, ns, ports); 112 | assert_int_equal (1, servers_num); 113 | assert_int_equal (53, ports[0]); 114 | assert_int_equal(0, pj_strcmp2(&ns[0], "2.2.3.4")); 115 | } 116 | 117 | int main(int argc, const char *argv[]) 118 | { 119 | pj_log_set_level(0); // do not print pj debug on init 120 | const struct CMUnitTest tests[] = { 121 | cmocka_unit_test(argument_ns_with_single_server_no_port), 122 | cmocka_unit_test(argument_ns_with_single_server_with_port), 123 | cmocka_unit_test(argument_ns_with_multi_servers_no_port), 124 | cmocka_unit_test(argument_ns_with_multi_servers_with_ports), 125 | cmocka_unit_test(argument_ns_respect_servers_num_limit), 126 | cmocka_unit_test(argument_ns_server_with_invalid_port), 127 | }; 128 | return cmocka_run_group_tests_name("DNS helper", tests, NULL, NULL); 129 | } 130 | -------------------------------------------------------------------------------- /src/app/dns.c: -------------------------------------------------------------------------------- 1 | /** 2 | * sippak -- SIP command line utility. 3 | * Copyright (C) 2018, Stas Kobzar 4 | * 5 | * This file is part of sippak. 6 | * 7 | * sippak is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * sippak is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with sippak. If not, see . 19 | */ 20 | 21 | /** 22 | * @file dns.c 23 | * @brief sippak DNS helper 24 | * 25 | * @author Stas Kobzar 26 | */ 27 | #include 28 | #include "sippak.h" 29 | 30 | #define NAME "dns_helper" 31 | 32 | /* get port as int from server string. token ":". */ 33 | static int get_port (pj_str_t server) 34 | { 35 | pj_ssize_t found_idx = 0; 36 | pj_str_t token; 37 | int port = 53; // default DNS port is 53 38 | pj_str_t delim = pj_str(":"); 39 | 40 | found_idx = pj_strtok (&server, &delim, &token, 0); 41 | found_idx = pj_strtok (&server, &delim, &token, found_idx + token.slen); 42 | 43 | if (found_idx == server.slen) { 44 | // port in server string is not found 45 | return port; // return default port 46 | } 47 | 48 | port = pj_strtol (&token); 49 | port = port == 0 ? 53 : port; 50 | 51 | return port; 52 | } 53 | 54 | /* get server ip from pj_str without port if exists. */ 55 | static pj_str_t get_server_ip (pj_str_t token) 56 | { 57 | pj_str_t ip_str; 58 | int idx = 0; 59 | char *ptr = token.ptr; 60 | 61 | for (; idx < token.slen && ptr[idx] != ':'; idx++); 62 | 63 | ip_str.ptr = token.ptr; 64 | ip_str.slen = idx; 65 | return ip_str; 66 | } 67 | 68 | static int parse_nameservers_string (char *nameservers, 69 | pj_str_t *ns, 70 | pj_uint16_t *ports) 71 | { 72 | pj_ssize_t found_idx = 0; 73 | pj_str_t token; 74 | int ns_idx = 0; 75 | pj_str_t ns_str= pj_str(nameservers); 76 | pj_str_t delim = pj_str(","); 77 | 78 | for ( 79 | found_idx = pj_strtok (&ns_str, &delim, &token, 0); 80 | found_idx != ns_str.slen; 81 | found_idx = pj_strtok (&ns_str, &delim, &token, found_idx + token.slen) 82 | ) 83 | { 84 | if (ns_idx >= MAX_NS_COUNT) { 85 | PJ_LOG(4, (NAME, "More NS servers then allowed. Skip server %.*s", token.slen, token.ptr)); 86 | continue; 87 | } 88 | ns[ns_idx] = get_server_ip(token); 89 | ports[ns_idx] = get_port(token); 90 | ns_idx++; 91 | } 92 | 93 | return ns_idx; 94 | } 95 | 96 | /* when nameservers are not given, try to get from system resolv.conf */ 97 | static int system_resolv_ns ( pj_pool_t *pool, 98 | pj_str_t *ns, 99 | pj_uint16_t *ports) 100 | { 101 | int ns_idx = 0; 102 | int loop_ns = 0; 103 | char addr_str[PJ_INET_ADDRSTRLEN]; 104 | 105 | if (res_init() == -1) { 106 | PJ_LOG(1, (NAME, "Failed to init resolv lib. DNS is disabled.")); 107 | return 0; 108 | } 109 | 110 | PJ_LOG(4, (NAME, "Found %d name servers configured on system. Will use first IPv4 one.", 111 | _res.nscount, MAX_NS_COUNT)); 112 | 113 | loop_ns = _res.nscount > MAX_NS_COUNT ? MAX_NS_COUNT : _res.nscount; 114 | 115 | for (unsigned i = 0; ns_idx < loop_ns && i < _res.nscount ; i++ ) { 116 | if (_res.nsaddr_list[i].sin_family != pj_AF_INET()) { 117 | PJ_LOG(4, (NAME, "Skip non IPv4 name server")); 118 | continue; 119 | } 120 | 121 | pj_inet_ntop (pj_AF_INET(), &_res.nsaddr_list[i].sin_addr, addr_str, sizeof(addr_str)); 122 | 123 | ns[ns_idx] = pj_strdup3(pool, addr_str); 124 | ports[ns_idx] = pj_ntohs(_res.nsaddr_list[i].sin_port); 125 | 126 | ns_idx++; 127 | } 128 | 129 | return ns_idx; 130 | } 131 | 132 | PJ_DEF(int) sippak_get_ns_list (struct sippak_app *app, 133 | pj_str_t *ns, 134 | pj_uint16_t *ports) 135 | { 136 | if (app->cfg.nameservers == NULL) { 137 | 138 | return system_resolv_ns (app->pool, ns, ports); 139 | 140 | } else { 141 | 142 | return parse_nameservers_string (app->cfg.nameservers, ns, ports); 143 | 144 | } 145 | } 146 | 147 | PJ_DEF(pj_status_t) sippak_set_resolver_ns(struct sippak_app *app) 148 | { 149 | pj_status_t status; 150 | pj_dns_resolver *resv; 151 | pj_str_t nameservers[MAX_NS_COUNT]; 152 | pj_uint16_t ports[MAX_NS_COUNT]; 153 | unsigned serv_num = 0; 154 | 155 | serv_num = sippak_get_ns_list (app, nameservers, ports); 156 | 157 | status = pjsip_endpt_create_resolver(app->endpt, &resv); 158 | SIPPAK_ASSERT_SUCC(status, "Failed to create end point resolver."); 159 | 160 | status = pj_dns_resolver_set_ns(resv, serv_num, nameservers, ports); 161 | SIPPAK_ASSERT_SUCC(status, "Failed to set DNS name servers."); 162 | 163 | return pjsip_endpt_set_resolver(app->endpt, resv); 164 | } 165 | -------------------------------------------------------------------------------- /tests/acceptance/test.notify_basic.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak NOTIFY command with presence info and check-sync 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/notify.basic.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak notify basic presence with pidf 27 | $output = `$sippak notify sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^NOTIFY sip:alice\@127\.0\.0\.1:5060 SIP\/2.0$'; 31 | ok ($output =~ m/$regex/m, "Basic NOTIFY packet sent."); 32 | 33 | $regex = '^SIP\/2\.0 200 OK Basic NOTIFY Test$'; 34 | ok ($output =~ m/$regex/m, "Basic NOTIFY response displayed."); 35 | 36 | $regex = '^Event: keep-alive$'; 37 | ok ($output =~ m/$regex/m, "Default event is 'keep-alive'"); 38 | 39 | # special events 40 | system("$sipp $sippargs -sf $scenario"); 41 | $output = `$sippak notify -E foo sip:alice\@127.0.0.1:5060`; 42 | 43 | $regex = '^Event: foo$'; 44 | ok ($output =~ m/$regex/m, "Arbitrary event header 'foo'."); 45 | 46 | system("$sipp $sippargs -sf $scenario"); 47 | $output = `$sippak notify --event=check-sync sip:alice\@127.0.0.1:5060`; 48 | 49 | $regex = '^Event: check-sync$'; 50 | ok ($output =~ m/$regex/m, "Event header 'check-sync'."); 51 | 52 | system("$sipp $sippargs -sf $scenario"); 53 | $output = `$sippak notify --event=mwi sip:alice\@127.0.0.1:5060`; 54 | 55 | $regex = '^Event: message-summary$'; 56 | ok ($output =~ m/$regex/m, "Event header 'message-summary' for alias 'mwi'."); 57 | 58 | # custom content type 59 | system("$sipp $sippargs -sf $scenario"); 60 | $output = `$sippak notify --event=foo --content-type=bar sip:alice\@127.0.0.1:5060`; 61 | 62 | $regex = '^Event: foo$'; 63 | ok ($output =~ m/$regex/m, "Custom notify with content type and event 'foo'"); 64 | 65 | $regex = '^Content-Type: application/bar$'; 66 | ok ($output =~ m/$regex/m, "Custom notify with content type 'bar' and event 'foo'"); 67 | 68 | # custom compound content type 69 | system("$sipp $sippargs -sf $scenario"); 70 | $output = `$sippak notify --content-type=foo/bar sip:alice\@127.0.0.1:5060`; 71 | 72 | $regex = '^Content-Type: foo/bar$'; 73 | ok ($output =~ m/$regex/m, "Custom notify with compound content type 'foo/bar'"); 74 | 75 | # message-summary notify 76 | system("$sipp $sippargs -sf $scenario"); 77 | $output = `$sippak notify --mwi=0 sip:alice\@127.0.0.1:5060`; 78 | 79 | $regex = '^Messages-Waiting: no'; 80 | ok ($output =~ m/$regex/m, "MWI basic no message waiting."); 81 | 82 | $regex = '^Message-Account: sip:alice@127.0.0.1:5060'; 83 | ok ($output =~ m/$regex/m, "MWI basic message account use RURI."); 84 | 85 | $regex = '^Voice-Message: 0/0 \(0/0\)'; 86 | ok ($output =~ m/$regex/m, "MWI basic voice message is 0/0 (0/0)."); 87 | 88 | # message-summary notify with account name 89 | system("$sipp $sippargs -sf $scenario"); 90 | $output = `$sippak notify -M 4,0,1,2 --mwi-acc=sip:VM\@sip.com sip:alice\@127.0.0.1:5060`; 91 | 92 | $regex = '^Messages-Waiting: yes'; 93 | ok ($output =~ m/$regex/m, "MWI basic with messages waiting."); 94 | 95 | $regex = '^Message-Account: sip:VM@sip.com'; 96 | ok ($output =~ m/$regex/m, "MWI basic message account set mwi-acc."); 97 | 98 | $regex = '^Voice-Message: 4/0 \(1/2\)'; 99 | ok ($output =~ m/$regex/m, "MWI basic voice message is 0/0 (0/0)."); 100 | 101 | # presence pidf basic 102 | system("$sipp $sippargs -sf $scenario"); 103 | $output = `$sippak notify -C pidf sip:alice\@127.0.0.1:5060`; 104 | 105 | $regex = '^Event: presence$'; 106 | ok ($output =~ m/$regex/m, "NOTIFY PIDF event header is presence"); 107 | 108 | $regex = 'open'; 109 | ok ($output =~ m/$regex/m, "NOTIFY default PIDF status is open"); 110 | 111 | $regex = '^Content-Type: application/pidf\+xml$'; 112 | ok ($output =~ m/$regex/m, "NOTIFY PIDF content type is application/pidf+xml"); 113 | 114 | # presence pidf with status closed 115 | system("$sipp $sippargs -sf $scenario"); 116 | $output = `$sippak notify -C pidf --pres-status=closed sip:alice\@127.0.0.1:5060`; 117 | 118 | $regex = 'closed'; 119 | ok ($output =~ m/$regex/m, "NOTIFY PIDF with status set as closed"); 120 | 121 | # presence pidf with note 122 | system("$sipp $sippargs -sf $scenario"); 123 | $output = `$sippak notify -C pidf --pres-note="Out for lunch" sip:alice\@127.0.0.1:5060`; 124 | 125 | $regex = 'open'; 126 | ok ($output =~ m/$regex/m, "NOTIFY PIDF with note and default status is open"); 127 | 128 | $regex = 'Out for lunch'; 129 | ok ($output =~ m/$regex/m, "NOTIFY PIDF doc note is set"); 130 | 131 | # presence XPIDF 132 | system("$sipp $sippargs -sf $scenario"); 133 | $output = `$sippak notify -C xpidf sip:alice\@127.0.0.1:5060`; 134 | 135 | $regex = '^Event: presence$'; 136 | ok ($output =~ m/$regex/m, "NOTIFY XPIDF event header is presence"); 137 | 138 | $regex = '^Content-Type: application/xpidf\+xml$'; 139 | ok ($output =~ m/$regex/m, "NOTIFY XPIDF document type"); 140 | 141 | # Header User-Agent add 142 | system("$sipp $sippargs -sf $scenario"); 143 | $output = `$sippak notify -A "SIP UA 1.x.x" sip:alice\@127.0.0.1:5060`; 144 | 145 | $regex = '^User-Agent: SIP UA 1.x.x$'; 146 | ok ($output =~ m/$regex/m, "Add User-Agent header."); 147 | 148 | done_testing(); 149 | -------------------------------------------------------------------------------- /src/mod/ping.c: -------------------------------------------------------------------------------- 1 | /** 2 | * sippak -- SIP command line utility. 3 | * Copyright (C) 2018, Stas Kobzar 4 | * 5 | * This file is part of sippak. 6 | * 7 | * sippak is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * sippak is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with sippak. If not, see . 19 | */ 20 | 21 | /** 22 | * @file ping.c 23 | * @brief sippak ping with OPTIONS remote host. Stateless. 24 | * 25 | * @author Stas Kobzar 26 | */ 27 | #include 28 | #include 29 | #include "sippak.h" 30 | 31 | #define NAME "mod_ping" 32 | 33 | static pjsip_auth_clt_sess auth_sess; 34 | static int auth_tries = 0; 35 | 36 | static pj_bool_t on_rx_response (pjsip_rx_data *rdata); 37 | 38 | static pjsip_module mod_ping = 39 | { 40 | NULL, NULL, /* prev, next. */ 41 | { "mod-ping", 9 }, /* Name. */ 42 | -1, /* Id */ 43 | PJSIP_MOD_PRIORITY_TRANSPORT_LAYER, /* Priority */ 44 | NULL, /* load() */ 45 | NULL, /* start() */ 46 | NULL, /* stop() */ 47 | NULL, /* unload() */ 48 | NULL, /* on_rx_request() */ 49 | &on_rx_response, /* on_rx_response() */ 50 | NULL, /* on_tx_request. */ 51 | NULL, /* on_tx_response() */ 52 | NULL, /* on_tsx_state() */ 53 | }; 54 | 55 | /* On response module callback */ 56 | static pj_bool_t on_rx_response (pjsip_rx_data *rdata) 57 | { 58 | pjsip_msg *msg = rdata->msg_info.msg; 59 | pjsip_via_hdr *via = rdata->msg_info.via; 60 | 61 | PJ_LOG(3, (NAME, "Response received: %d %.*s", 62 | msg->line.status.code, 63 | msg->line.status.reason.slen, 64 | msg->line.status.reason.ptr)); 65 | if (via) { 66 | PJ_LOG(3, (NAME, "Via rport: %d, received: %.*s", via->rport_param, 67 | via->recvd_param.slen, via->recvd_param.ptr)); 68 | } 69 | 70 | int code = rdata->msg_info.msg->line.status.code; 71 | 72 | if (code == 401 || code == 407) { 73 | return PJ_FALSE; // processed with callback 74 | } 75 | 76 | sippak_loop_cancel(); 77 | 78 | return PJ_FALSE; // continue with othe modules 79 | } 80 | 81 | static void send_cb(void *token, pjsip_event *e) 82 | { 83 | pj_status_t status; 84 | pjsip_tx_data *tdata; 85 | pjsip_cred_info cred[1]; 86 | pjsip_transaction *tsx = e->body.tsx_state.tsx; 87 | pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; 88 | struct sippak_app *app = token; 89 | if (tsx->status_code == 401 || tsx->status_code == 407) { 90 | auth_tries++; 91 | if (auth_tries > 1) { 92 | PJ_LOG(1, (NAME, "Authentication failed. Check your username and password")); 93 | sippak_loop_cancel(); 94 | return; 95 | } 96 | sippak_set_cred(app, cred); 97 | 98 | status = pjsip_auth_clt_init (&auth_sess, app->endpt, rdata->tp_info.pool, 0); 99 | if (status != PJ_SUCCESS) { 100 | PJ_LOG(1, (NAME, "Failed init authentication credentials.")); 101 | return; 102 | } 103 | 104 | pjsip_auth_clt_set_credentials(&auth_sess, 1, cred); 105 | 106 | status = pjsip_auth_clt_reinit_req(&auth_sess, rdata, 107 | tsx->last_tx, &tdata); 108 | if (status != PJ_SUCCESS) { 109 | PJ_LOG(1, (NAME, "Failed to re-init client authentication session.")); 110 | return; 111 | } 112 | 113 | pjsip_endpt_send_request(app->endpt, tdata, -1, app, &send_cb); 114 | } 115 | } 116 | 117 | /* Ping */ 118 | PJ_DEF(pj_status_t) sippak_cmd_ping (struct sippak_app *app) 119 | { 120 | pj_status_t status; 121 | pj_str_t *local_addr; 122 | int local_port; 123 | 124 | pjsip_tx_data *tdata = NULL; 125 | 126 | pj_str_t cnt, from, ruri; 127 | 128 | status = sippak_transport_init(app, &local_addr, &local_port); 129 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transport."); 130 | 131 | cnt = sippak_create_contact_hdr(app, local_addr, local_port); 132 | from = sippak_create_from_hdr(app); 133 | ruri = sippak_create_ruri(app); 134 | 135 | status = pjsip_endpt_create_request(app->endpt, 136 | &pjsip_options_method, // method OPTIONS 137 | &ruri, // request URI 138 | &from, // from header value 139 | &app->cfg.dest, // to header value 140 | &cnt, // Contact header 141 | NULL, // Call-ID 142 | -1, // CSeq 143 | NULL, // body 144 | &tdata); 145 | SIPPAK_ASSERT_SUCC(status, "Failed to create endpoint request."); 146 | 147 | status = pjsip_tsx_layer_init_module(app->endpt); 148 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transaction layer."); 149 | 150 | status = pjsip_endpt_register_module(app->endpt, &mod_ping); 151 | SIPPAK_ASSERT_SUCC(status, "Failed to register module mod_ping."); 152 | 153 | return pjsip_endpt_send_request(app->endpt, tdata, -1, app, &send_cb); 154 | } 155 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/invite.auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 158 | 159 | -------------------------------------------------------------------------------- /tests/acceptance/sipp_scenarios/subscribe.auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 145 | -------------------------------------------------------------------------------- /tests/unit/test_sip_helper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sippak.h" 7 | 8 | pjsip_endpoint *endpt; 9 | pj_pool_t *pool; 10 | 11 | static int setup_app(void **state) 12 | { 13 | struct sippak_app *app = malloc(sizeof(struct sippak_app)); 14 | 15 | sippak_init(app); 16 | 17 | app->pool = pool; 18 | 19 | *state = app; 20 | return 0; 21 | } 22 | 23 | static int teardown_app(void **state) 24 | { 25 | free(*state); 26 | return 0; 27 | } 28 | 29 | static void create_from_header (void **state) 30 | { 31 | pj_status_t status; 32 | struct sippak_app *app = *state; 33 | char *argv[] = { "./sippak", "sip:alice@example.com" }; 34 | int argc = sizeof(argv) / sizeof(char*); 35 | 36 | status = sippak_getopts (argc, argv, app); 37 | 38 | assert_int_equal (status, PJ_SUCCESS); 39 | pj_str_t from = sippak_create_from_hdr(app); 40 | assert_string_equal("sip:alice@example.com", from.ptr); 41 | } 42 | 43 | static void create_from_header_username (void **state) 44 | { 45 | struct sippak_app *app = *state; 46 | 47 | app->cfg.dest = pj_str("sip:alice@sipspot.com"); 48 | app->cfg.username = pj_str("charlie"); 49 | 50 | pj_str_t from = sippak_create_from_hdr(app); 51 | assert_string_equal("sip:charlie@sipspot.com", from.ptr); 52 | } 53 | 54 | static void create_from_header_display_name (void **state) 55 | { 56 | struct sippak_app *app = *state; 57 | 58 | app->cfg.dest = pj_str("sip:bob@sip.com"); 59 | app->cfg.username = pj_str("charlie"); 60 | app->cfg.from_name = pj_str("Charlie & Co."); 61 | 62 | pj_str_t from = sippak_create_from_hdr(app); 63 | assert_string_equal("\"Charlie & Co.\" ", from.ptr); 64 | } 65 | 66 | static void create_ruri (void **state) 67 | { 68 | struct sippak_app *app = *state; 69 | 70 | app->cfg.dest = pj_str("sip:harry@sip.com"); 71 | 72 | pj_str_t ruri = sippak_create_ruri(app); 73 | assert_string_equal("sip:harry@sip.com", ruri.ptr); 74 | } 75 | 76 | static void create_reg_ruri (void **state) 77 | { 78 | struct sippak_app *app = *state; 79 | 80 | app->cfg.dest = pj_str("sip:alice@sipproxy.com"); 81 | pj_str_t ruri = sippak_create_reg_ruri(app); 82 | assert_string_equal("sip:sipproxy.com", ruri.ptr); 83 | } 84 | 85 | static void create_reg_ruri_tcp (void **state) 86 | { 87 | struct sippak_app *app = *state; 88 | 89 | app->cfg.dest = pj_str("sip:john@sipproxy.com"); 90 | app->cfg.proto = PJSIP_TRANSPORT_TCP; 91 | pj_str_t ruri = sippak_create_reg_ruri(app); 92 | assert_string_equal("sip:sipproxy.com;transport=tcp", ruri.ptr); 93 | } 94 | 95 | static void create_ruri_tcp (void **state) 96 | { 97 | struct sippak_app *app = *state; 98 | 99 | app->cfg.dest = pj_str("sip:jane@sip.com"); 100 | app->cfg.proto = PJSIP_TRANSPORT_TCP; 101 | 102 | pj_str_t ruri = sippak_create_ruri(app); 103 | assert_string_equal("sip:jane@sip.com;transport=tcp", ruri.ptr); 104 | } 105 | 106 | static void create_contact_hdr (void **state) 107 | { 108 | pj_status_t status; 109 | struct sippak_app *app = *state; 110 | char *argv[] = { "./sippak", "sip:alice@foo.com" }; 111 | int argc = sizeof(argv) / sizeof(char*); 112 | pj_str_t addr = pj_str("192.168.1.111"); 113 | 114 | status = sippak_getopts (argc, argv, app); 115 | assert_int_equal (status, PJ_SUCCESS); 116 | pj_str_t cnt = sippak_create_contact_hdr(app, &addr, 14255); 117 | assert_string_equal("sip:alice@192.168.1.111:14255", cnt.ptr); 118 | } 119 | 120 | static void create_contact_hdr_user (void **state) 121 | { 122 | struct sippak_app *app = *state; 123 | pj_str_t addr = pj_str("192.168.18.11"); 124 | 125 | app->cfg.username = pj_str("jonny"); 126 | pj_str_t cnt = sippak_create_contact_hdr(app, &addr, 1255); 127 | assert_string_equal("sip:jonny@192.168.18.11:1255", cnt.ptr); 128 | } 129 | 130 | static void create_contact_hdr_cli_arg (void **state) 131 | { 132 | pj_status_t status; 133 | struct sippak_app *app = *state; 134 | char *argv[] = { "./sippak", "--contact=sip:foo@10.123.123.22:14511", "sip:alice@foo.com" }; 135 | int argc = sizeof(argv) / sizeof(char*); 136 | pj_str_t addr = pj_str("192.168.18.11"); 137 | 138 | status = sippak_getopts (argc, argv, app); 139 | app->cfg.username = pj_str("jonny"); 140 | assert_int_equal (status, PJ_SUCCESS); 141 | pj_str_t cnt = sippak_create_contact_hdr(app, &addr, 1255); 142 | assert_string_equal("sip:foo@10.123.123.22:14511", cnt.ptr); 143 | } 144 | 145 | int main(int argc, const char *argv[]) 146 | { 147 | pj_status_t status; 148 | pj_caching_pool cp; 149 | 150 | pj_log_set_level(0); // do not print pj debug on init 151 | 152 | pj_init(); 153 | 154 | pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); 155 | 156 | status = pjsip_endpt_create(&cp.factory, "TEST_SIP_HELPER", &endpt); 157 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); 158 | 159 | pool = pjsip_endpt_create_pool(endpt, PROJECT_NAME, POOL_INIT, POOL_INCR); 160 | 161 | const struct CMUnitTest tests[] = { 162 | cmocka_unit_test_setup_teardown(create_from_header, setup_app, teardown_app), 163 | cmocka_unit_test_setup_teardown(create_from_header_username, setup_app, teardown_app), 164 | cmocka_unit_test_setup_teardown(create_from_header_display_name, setup_app, teardown_app), 165 | 166 | cmocka_unit_test_setup_teardown(create_ruri, setup_app, teardown_app), 167 | cmocka_unit_test_setup_teardown(create_ruri_tcp, setup_app, teardown_app), 168 | cmocka_unit_test_setup_teardown(create_reg_ruri, setup_app, teardown_app), 169 | cmocka_unit_test_setup_teardown(create_reg_ruri_tcp, setup_app, teardown_app), 170 | 171 | cmocka_unit_test_setup_teardown(create_contact_hdr, setup_app, teardown_app), 172 | cmocka_unit_test_setup_teardown(create_contact_hdr_user, setup_app, teardown_app), 173 | cmocka_unit_test_setup_teardown(create_contact_hdr_cli_arg, setup_app, teardown_app), 174 | }; 175 | status = cmocka_run_group_tests_name("SIP packet helper", tests, NULL, NULL); 176 | 177 | pjsip_endpt_release_pool (endpt, pool); 178 | pjsip_endpt_destroy(endpt); 179 | 180 | return status; 181 | } 182 | -------------------------------------------------------------------------------- /src/mod/register.c: -------------------------------------------------------------------------------- 1 | /** 2 | * sippak -- SIP command line utility. 3 | * Copyright (C) 2018, Stas Kobzar 4 | * 5 | * This file is part of sippak. 6 | * 7 | * sippak is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * sippak is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with sippak. If not, see . 19 | */ 20 | 21 | /** 22 | * @file ping.c 23 | * @brief sippak send REGISTER request 24 | * 25 | * @author Stas Kobzar 26 | */ 27 | #include 28 | #include "sippak.h" 29 | 30 | #define NAME "mod_register" 31 | 32 | static void reg_callback(struct pjsip_regc_cbparam *param); 33 | static void print_reg_success(struct pjsip_regc_cbparam *regp); 34 | 35 | static pjsip_module mod_register = 36 | { 37 | NULL, NULL, /* prev, next. */ 38 | { "mod-register", 12 }, /* Name. */ 39 | -1, /* Id */ 40 | PJSIP_MOD_PRIORITY_APPLICATION - 1, /* Priority */ 41 | NULL, /* load() */ 42 | NULL, /* start() */ 43 | NULL, /* stop() */ 44 | NULL, /* unload() */ 45 | NULL, /* on_rx_request() */ 46 | NULL, /* on_rx_response() */ 47 | NULL, /* on_tx_request. */ 48 | NULL, /* on_tx_response() */ 49 | NULL, /* on_tsx_state() */ 50 | }; 51 | 52 | static void print_reg_success(struct pjsip_regc_cbparam *regp) 53 | { 54 | struct sippak_app *app = regp->token; 55 | if (app->cfg.cancel_all_reg == PJ_TRUE) { 56 | PJ_LOG(3, (NAME, "Cancel all registrations. Response received: %d %.*s", 57 | regp->code, regp->reason.slen, regp->reason.ptr)); 58 | } else if (app->cfg.cancel == PJ_TRUE) { 59 | PJ_LOG(3, (NAME, "Unregister contact. Response received: %d %.*s", 60 | regp->code, regp->reason.slen, regp->reason.ptr)); 61 | } else if (app->cfg.is_clist == PJ_TRUE) { 62 | PJ_LOG(3, (NAME, "Request for current contacts. Response received: %d %.*s", 63 | regp->code, regp->reason.slen, regp->reason.ptr)); 64 | } else { 65 | PJ_LOG(3, (NAME, "Successfully registered. Response received: %d %.*s", 66 | regp->code, regp->reason.slen, regp->reason.ptr)); 67 | } 68 | PJ_LOG(3, (NAME, "Contacts list (%d):", regp->contact_cnt)); 69 | for(int i = 0; i < regp->contact_cnt; i++) { 70 | char buf[PJSIP_MAX_URL_SIZE] = {0}; 71 | pjsip_contact_hdr *hdr = regp->contact[i]; 72 | hdr->vptr->print_on(hdr, buf, PJSIP_MAX_URL_SIZE); 73 | PJ_LOG(3, (NAME, "\t%d - %s", i + 1, buf)); 74 | } 75 | } 76 | 77 | static void reg_callback(struct pjsip_regc_cbparam *regp) 78 | { 79 | if (regp->code == PJSIP_SC_OK) { 80 | print_reg_success(regp); 81 | 82 | } else if (regp->code == PJSIP_SC_UNAUTHORIZED) { 83 | PJ_LOG(1, (NAME, "Authentication failed. Check your username and password")); 84 | } else { 85 | PJ_LOG(1, (NAME, "Registration response: %d %.*s", regp->code, 86 | regp->reason.slen, regp->reason.ptr)); 87 | } 88 | sippak_loop_cancel(); 89 | } 90 | 91 | /* Register */ 92 | PJ_DEF(pj_status_t) sippak_cmd_register (struct sippak_app *app) 93 | { 94 | pj_status_t status; 95 | pj_str_t *local_addr; 96 | int local_port; 97 | pjsip_tx_data *tdata; 98 | pjsip_regc *regc; 99 | pjsip_cred_info cred[1]; 100 | pjsip_route_hdr *route_set; 101 | 102 | pj_str_t srv_url, from_uri, to_uri; 103 | pj_str_t contacts[1]; 104 | 105 | status = sippak_transport_init(app, &local_addr, &local_port); 106 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transport."); 107 | 108 | status = pjsip_tsx_layer_init_module(app->endpt); 109 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transaction layer."); 110 | 111 | status = pjsip_endpt_register_module(app->endpt, &mod_register); 112 | SIPPAK_ASSERT_SUCC(status, "Failed to register module mod_register."); 113 | 114 | status = pjsip_regc_create(app->endpt, 115 | app, // void *token: A data to be associated with the client registration struct. 116 | ®_callback, // Pointer to callback function to receive registration status 117 | ®c); 118 | SIPPAK_ASSERT_SUCC(status, "Failed to create register client."); 119 | 120 | srv_url = sippak_create_reg_ruri(app); 121 | to_uri = sippak_create_ruri(app); 122 | from_uri = sippak_create_from_hdr(app); 123 | contacts[0] = sippak_create_contact_hdr(app, local_addr, local_port); 124 | 125 | status = pjsip_regc_init(regc, &srv_url, &from_uri, &to_uri, 126 | app->cfg.is_clist == PJ_TRUE ? 0: 1, // no contact header, means return contacts list 127 | contacts, app->cfg.expires); 128 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate register client."); 129 | 130 | sippak_set_cred(app, cred); 131 | status = pjsip_regc_set_credentials(regc, 1, cred); 132 | SIPPAK_ASSERT_SUCC(status, "Failed to set auth credentials."); 133 | 134 | if (sippak_set_proxies_list(app, &route_set) == PJ_TRUE) { 135 | pjsip_regc_set_route_set(regc, route_set); 136 | } 137 | 138 | if (app->cfg.cancel_all_reg == PJ_TRUE) { 139 | status = pjsip_regc_unregister_all(regc, &tdata); 140 | SIPPAK_ASSERT_SUCC(status, "Failed to cancel all registarations for %.*s.", 141 | from_uri.slen, from_uri.ptr); 142 | } else if (app->cfg.cancel == PJ_TRUE) { 143 | status = pjsip_regc_unregister(regc, &tdata); 144 | SIPPAK_ASSERT_SUCC(status, "Failed to cancel registaration for %.*s.", 145 | from_uri.slen, from_uri.ptr); 146 | } else { 147 | status = pjsip_regc_register(regc, PJ_FALSE, &tdata); 148 | SIPPAK_ASSERT_SUCC(status, "Failed to register %.*s.", 149 | from_uri.slen, from_uri.ptr); 150 | } 151 | 152 | return pjsip_regc_send(regc, tdata); 153 | } 154 | -------------------------------------------------------------------------------- /tests/acceptance/test.publish_basic.pl: -------------------------------------------------------------------------------- 1 | # 2 | # Acceptence test: 3 | # Run sippak ping command without colors and trailing dot 4 | # 5 | 6 | use strict; 7 | use warnings; 8 | use Cwd qw(abs_path); 9 | use File::Basename; 10 | use Test::More; 11 | 12 | my $sippak = $ARGV[0]; 13 | my $sipp = $ARGV[1]; 14 | my $scenario = $ARGV[2] . "/publish.basic.xml"; 15 | my $sippargs = "-timeout 10s -p 5060 -m 1 -bg"; 16 | my $output = ""; 17 | my $regex = ""; 18 | 19 | # run sipp scenario in background mode 20 | system("$sipp $sippargs -sf $scenario"); 21 | if ($? == -1) { 22 | print "Failed execute sipp\n"; 23 | exit(1); 24 | } 25 | 26 | # run sippak publish basic 27 | $output = `$sippak PUBLISH sip:alice\@127.0.0.1:5060`; 28 | 29 | # test request 30 | $regex = '^PUBLISH sip:alice\@127\.0\.0\.1:5060 SIP\/2\.0$'; 31 | ok ($output =~ m/$regex/m, "Basic PUBLISH packet sent."); 32 | 33 | # test response 34 | $regex = '^SIP\/2\.0 200 OK Basic PUBLISH Test$'; 35 | ok ($output =~ m/$regex/m, "Basic PUBLISH Response 200 OK received."); 36 | 37 | # contenct type is Content-Type: application/pidf+xml 38 | $regex = '^Content-Type: application/pidf\+xml$'; 39 | ok ($output =~ m/$regex/m, "Basic PUBLISH content type is pidf+xml."); 40 | 41 | # default expire is 3600 42 | $regex = '^Expires: 3600$'; 43 | ok ($output =~ m/$regex/m, "Expires default value is 3600"); 44 | 45 | # invalid Expires header value 46 | $output = `$sippak PUBLISH --expires=5foo sip:alice\@127.0.0.1:5060`; 47 | $regex = 'Invalid expires value: 5foo. Must be numeric.'; 48 | ok ($output =~ m/$regex/m, "Invalid Expires value set."); 49 | 50 | # run sippak publish with expire set value 51 | system("$sipp $sippargs -sf $scenario"); 52 | $output = `$sippak PUBLISH -X 84877 sip:alice\@127.0.0.1:5060`; 53 | # test request 54 | $regex = '^Expires: 84877$'; 55 | ok ($output =~ m/$regex/m, "Set expire value 84877."); 56 | 57 | # run sippak publish with expire set to 0 58 | $output = `$sippak PUBLISH --expires=0 sip:alice\@127.0.0.1:5060`; 59 | # test request 60 | $regex = 'Expires header value must be more then 0.'; 61 | ok ($output =~ m/$regex/m, "Set expire value to 0 not allowed."); 62 | 63 | # test basic opens/close 64 | system("$sipp $sippargs -sf $scenario"); 65 | $output = `$sippak PUBLISH sip:alice\@127.0.0.1:5060`; 66 | 67 | $regex = 'open'; 68 | ok ($output =~ m/$regex/m, "Default status is 'open'."); 69 | 70 | system("$sipp $sippargs -sf $scenario"); 71 | $output = `$sippak PUBLISH --pres-status=closed sip:alice\@127.0.0.1:5060`; 72 | 73 | $regex = 'closed'; 74 | ok ($output =~ m/$regex/m, "Set presence status to 'closed'."); 75 | 76 | system("$sipp $sippargs -sf $scenario"); 77 | $output = `$sippak PUBLISH --pres-status=open sip:alice\@127.0.0.1:5060`; 78 | 79 | $regex = 'open'; 80 | ok ($output =~ m/$regex/m, "Set presence status to 'open'."); 81 | 82 | system("$sipp $sippargs -sf $scenario"); 83 | $output = `$sippak PUBLISH --pres-status=foo sip:alice\@127.0.0.1:5060`; 84 | 85 | $regex = 'open'; 86 | ok ($output =~ m/$regex/m, "For unknown presence status, use 'open'"); 87 | 88 | $regex = "Unknown presence status 'foo'. Will use 'open'."; 89 | ok ($output =~ m/$regex/m, "Warning for unknown presence status."); 90 | 91 | # test presence note message 92 | system("$sipp $sippargs -sf $scenario"); 93 | $output = `$sippak PUBLISH --pres-note="Will be back soon!" sip:alice\@127.0.0.1:5060`; 94 | 95 | $regex = 'Will be back soon!'; 96 | ok ($output =~ m/$regex/m, "Set pids note message."); 97 | 98 | system("$sipp $sippargs -sf $scenario"); 99 | my $too_long_msg = 'This memo further defines the Presence Information Data Format as a common presence data format for CPP-compliant presence protocols, allowing presence information to be transferred across CPP-cstas@stas-aspire ~/Dev/sippak'; 100 | $output = `$sippak PUBLISH --pres-note="$too_long_msg" sip:alice\@127.0.0.1:5060`; 101 | 102 | $regex = 'Note message is too long. Max 64 char allowed. Message is stripped.'; 103 | ok ($output =~ m/$regex/m, "Warning, max length of note is limited to 64 chars."); 104 | 105 | $regex = 'This memo further defines the Presence Information Data Format a'; 106 | ok ($output =~ m/$regex/m, "Long note message is stripped to 64 chars."); 107 | 108 | # test XPIDF presence message 109 | system("$sipp $sippargs -sf $scenario"); 110 | $output = `$sippak PUBLISH --content-type=xpidf sip:alice\@127.0.0.1:5060`; 111 | 112 | $regex = '^Content-Type: application/xpidf\+xml$'; 113 | ok ($output =~ m/$regex/m, "Basic PUBLISH content type is XPIDF."); 114 | 115 | # test explicit pidf type 116 | system("$sipp $sippargs -sf $scenario"); 117 | $output = `$sippak PUBLISH -C pidf sip:alice\@127.0.0.1:5060`; 118 | 119 | $regex = '^Content-Type: application/pidf\+xml$'; 120 | ok ($output =~ m/$regex/m, "Basic PUBLISH content type is PIDF explicit set."); 121 | 122 | # test invalid content type 123 | system("$sipp $sippargs -sf $scenario"); 124 | $output = `$sippak PUBLISH -C foo sip:alice\@127.0.0.1:5060`; 125 | 126 | $regex = '^Content-Type: application/pidf\+xml$'; 127 | ok ($output =~ m/$regex/m, "Basic PUBLISH content type is PIDF for invalid types."); 128 | 129 | $regex = 'Content type "foo" can not be used. Fall back to pidf content type.'; 130 | ok ($output =~ m/$regex/m, "Basic PUBLISH content type is PIDF for invalid types warning."); 131 | 132 | # Header User-Agent add 133 | system("$sipp $sippargs -sf $scenario"); 134 | $output = `$sippak PUBLISH --user-agent="SIP Publisher" sip:alice\@127.0.0.1:5060`; 135 | 136 | $regex = '^User-Agent: SIP Publisher$'; 137 | ok ($output =~ m/$regex/m, "Add User-Agent header."); 138 | 139 | # Custom headers add 140 | system("$sipp $sippargs -sf $scenario"); 141 | $output = `$sippak PUBLISH -H SIP-Hdr:Publisher sip:alice\@127.0.0.1:5060`; 142 | 143 | $regex = '^SIP-Hdr: Publisher$'; 144 | ok ($output =~ m/$regex/m, "Add custom header."); 145 | 146 | # Outbound proxy set 147 | system("$sipp $sippargs -sf $scenario"); 148 | $output = `$sippak PUBLISH --proxy=sip:127.0.0.1:5060 sip:alice\@sip.home.pbx`; 149 | 150 | $regex = '^PUBLISH sip:alice\@sip.home.pbx SIP\/2\.0$'; 151 | ok ($output =~ m/$regex/m, "Use outbound proxy."); 152 | 153 | # Route headers with multiple proxies 154 | system("$sipp $sippargs -sf $scenario"); 155 | $output = `$sippak PUBLISH -R sip:127.0.0.1:5060 -R "sips:102.10.10.1:8787;lr" sip:alice\@sip.home.pbx`; 156 | 157 | $regex = '^Route: $'; 158 | ok ($output =~ m/$regex/m, "Route header for second proxy."); 159 | 160 | done_testing(); 161 | -------------------------------------------------------------------------------- /src/mod/publish.c: -------------------------------------------------------------------------------- 1 | /** 2 | * sippak -- SIP command line utility. 3 | * Copyright (C) 2018, Stas Kobzar 4 | * 5 | * This file is part of sippak. 6 | * 7 | * sippak is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * sippak is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with sippak. If not, see . 19 | */ 20 | 21 | /** 22 | * @file publish.c 23 | * @brief sippak PUBLISH message send 24 | * 25 | * @author Stas Kobzar 26 | */ 27 | #include // needed for return value like PJ_CLI_EINVARG 28 | #include 29 | #include 30 | #include "sippak.h" 31 | 32 | #define NAME "mod_publish" 33 | 34 | static pj_bool_t on_rx_response (pjsip_rx_data *rdata); 35 | static short unsigned auth_tries = 0; 36 | static int pj_str_toi(pj_str_t val); 37 | 38 | static pjsip_module mod_publish = 39 | { 40 | NULL, NULL, /* prev, next. */ 41 | { "mod-publish", 11 }, /* Name. */ 42 | -1, /* Id */ 43 | PJSIP_MOD_PRIORITY_TSX_LAYER - 1, /* Priority */ 44 | NULL, /* load() */ 45 | NULL, /* start() */ 46 | NULL, /* stop() */ 47 | NULL, /* unload() */ 48 | NULL, /* on_rx_request() */ 49 | &on_rx_response, /* on_rx_response() */ 50 | NULL, /* on_tx_request. */ 51 | NULL, /* on_tx_response() */ 52 | NULL, /* on_tsx_state() */ 53 | }; 54 | 55 | /* On response module callback */ 56 | static pj_bool_t on_rx_response (pjsip_rx_data *rdata) 57 | { 58 | pjsip_msg *msg = rdata->msg_info.msg; 59 | 60 | auth_tries++; 61 | 62 | if (msg->type != PJSIP_RESPONSE_MSG) { 63 | return PJ_FALSE; 64 | } 65 | 66 | if (msg->line.status.code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED || 67 | msg->line.status.code == PJSIP_SC_UNAUTHORIZED) { 68 | if (auth_tries > 1) { 69 | sippak_loop_cancel(); 70 | PJ_LOG(1, (NAME, "Authentication failed. Check your username and password")); 71 | return PJ_TRUE; 72 | } 73 | } 74 | return PJ_FALSE; // continue with othe modules 75 | } 76 | 77 | static void publish_cb(struct pjsip_publishc_cbparam *param) 78 | { 79 | PJ_LOG(3, (NAME, "Response received: %d %.*s", 80 | param->code, 81 | param->reason.slen, 82 | param->reason.ptr)); 83 | sippak_loop_cancel(); 84 | } 85 | 86 | PJ_DEF(pj_status_t) sippak_cmd_publish (struct sippak_app *app) 87 | { 88 | pj_status_t status; 89 | pj_str_t *local_addr; 90 | int local_port; 91 | pj_str_t target_uri; 92 | pj_str_t from_uri; 93 | pj_str_t pres_id; 94 | 95 | pjsip_publishc *publish_sess = NULL; 96 | pjsip_publishc_opt publish_opt; 97 | pjsip_tx_data *tdata; 98 | pjsip_route_hdr *route_set; 99 | pjsip_pres_status pres_status; 100 | pjsip_cred_info cred[1]; 101 | pj_str_t event; 102 | 103 | if (app->cfg.event.slen == 0) { 104 | event = pj_str("presence"); 105 | } else{ 106 | event = app->cfg.event; 107 | } 108 | 109 | if (app->cfg.expires < 1) { 110 | PJ_LOG(1, (PROJECT_NAME, "Expires header value must be more then 0.")); 111 | exit(PJ_CLI_EINVARG); 112 | } 113 | 114 | status = sippak_transport_init(app, &local_addr, &local_port); 115 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transport."); 116 | 117 | target_uri = sippak_create_ruri(app); // also as To header URI 118 | from_uri = sippak_create_from_hdr(app); // also entity 119 | pres_id = sippak_create_contact_hdr(app, local_addr, local_port); 120 | 121 | // set presence status 122 | pj_bzero(&pres_status, sizeof(pres_status)); 123 | pres_status.info_cnt = 1; 124 | pres_status.info[0].basic_open = app->cfg.pres_status_open; 125 | pres_status.info[0].id = pres_id; 126 | pres_status.info[0].rpid.note = app->cfg.pres_note; 127 | pres_status.info[0].rpid.activity = app->cfg.pres_status_open 128 | ? PJRPID_ACTIVITY_UNKNOWN 129 | : PJRPID_ACTIVITY_BUSY; 130 | 131 | pjsip_publishc_opt_default(&publish_opt); 132 | 133 | status = pjsip_publishc_create(app->endpt, &publish_opt, NULL, &publish_cb, &publish_sess); 134 | SIPPAK_ASSERT_SUCC(status, "Failed to create publish client."); 135 | 136 | status = pjsip_publishc_init(publish_sess, &event, &target_uri, &from_uri, &target_uri, app->cfg.expires); 137 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate publish client."); 138 | 139 | sippak_set_cred(app, cred); 140 | 141 | if (sippak_set_proxies_list(app, &route_set) == PJ_TRUE) { 142 | pjsip_publishc_set_route_set (publish_sess, route_set); 143 | } 144 | 145 | status = pjsip_publishc_set_credentials(publish_sess, 1, cred); 146 | SIPPAK_ASSERT_SUCC(status, "Failed to set auth credentials."); 147 | 148 | status = pjsip_publishc_publish(publish_sess, PJ_TRUE, &tdata); 149 | SIPPAK_ASSERT_SUCC(status, "Failed to create publish client."); 150 | 151 | if (app->cfg.ctype_e == CTYPE_XPIDF) { 152 | status = pjsip_pres_create_xpidf(tdata->pool, &pres_status, &pres_id, &tdata->msg->body); 153 | SIPPAK_ASSERT_SUCC(status, "Failed to create XPIDF publish body."); 154 | } else if (app->cfg.ctype_e == CTYPE_PIDF) { 155 | status = pjsip_pres_create_pidf(tdata->pool, &pres_status, &pres_id, &tdata->msg->body); 156 | SIPPAK_ASSERT_SUCC(status, "Failed to create PIDF publish body."); 157 | } else { 158 | PJ_LOG(2, (PROJECT_NAME, 159 | "Content type \"%.*s\" can not be used. Fall back to pidf content type.", 160 | app->cfg.ctype_media.subtype.slen, app->cfg.ctype_media.subtype.ptr)); 161 | status = pjsip_pres_create_pidf(tdata->pool, &pres_status, &pres_id, &tdata->msg->body); 162 | } 163 | 164 | status = pjsip_tsx_layer_init_module(app->endpt); 165 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transaction layer."); 166 | 167 | status = pjsip_endpt_register_module(app->endpt, &mod_publish); 168 | SIPPAK_ASSERT_SUCC(status, "Failed to register module mod_publish."); 169 | 170 | return pjsip_publishc_send(publish_sess, tdata); 171 | } 172 | -------------------------------------------------------------------------------- /src/mod/refer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * sippak -- SIP command line utility. 3 | * Copyright (C) 2018, Stas Kobzar 4 | * 5 | * This file is part of sippak. 6 | * 7 | * sippak is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * sippak is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with sippak. If not, see . 19 | */ 20 | 21 | /** 22 | * @file ping.c 23 | * @brief sippak send REFER request. Click To Dial scenrio (rfc#5359 section 2.18) 24 | * 25 | * @author Stas Kobzar 26 | */ 27 | #include 28 | #include "sippak.h" 29 | 30 | #define NAME "mod_refer" 31 | 32 | static pjsip_auth_clt_sess auth_sess; 33 | static int auth_tries = 0; 34 | 35 | static pj_bool_t on_rx_response (pjsip_rx_data *rdata); 36 | static void send_cb(void *token, pjsip_event *e); 37 | static void add_referto_hdr(pjsip_tx_data *tdata, struct sippak_app *app); 38 | static void add_refersub_hdr(pjsip_tx_data *tdata, struct sippak_app *app); 39 | 40 | static pjsip_module mod_refer = 41 | { 42 | NULL, NULL, /* prev, next. */ 43 | { "mod-refer", 9 }, /* Name. */ 44 | -1, /* Id */ 45 | PJSIP_MOD_PRIORITY_TSX_LAYER - 1, /* Priority */ 46 | NULL, /* load() */ 47 | NULL, /* start() */ 48 | NULL, /* stop() */ 49 | NULL, /* unload() */ 50 | NULL, /* on_rx_request() */ 51 | &on_rx_response, /* on_rx_response() */ 52 | NULL, /* on_tx_request. */ 53 | NULL, /* on_tx_response() */ 54 | NULL, /* on_tsx_state() */ 55 | }; 56 | 57 | static void add_referto_hdr(pjsip_tx_data *tdata, struct sippak_app *app) 58 | { 59 | pj_str_t hname = pj_str("Refer-To"); 60 | pjsip_generic_string_hdr *hdr; 61 | hdr = pjsip_generic_string_hdr_create(app->pool, &hname, &app->cfg.refer_to); 62 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr); 63 | } 64 | 65 | static void add_refersub_hdr(pjsip_tx_data *tdata, struct sippak_app *app) 66 | { 67 | pj_str_t hname = pj_str("Refer-Sub"); 68 | pj_str_t hvalue = pj_str("false"); 69 | pjsip_generic_string_hdr *hdr = pjsip_generic_string_hdr_create(app->pool, &hname, &hvalue); 70 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr); 71 | } 72 | 73 | /* On response module callback */ 74 | static pj_bool_t on_rx_response (pjsip_rx_data *rdata) 75 | { 76 | pjsip_msg *msg = rdata->msg_info.msg; 77 | pjsip_via_hdr *via = rdata->msg_info.via; 78 | 79 | PJ_LOG(3, (NAME, "Response received: %d %.*s", 80 | msg->line.status.code, 81 | msg->line.status.reason.slen, 82 | msg->line.status.reason.ptr)); 83 | if (via && via->rport_param) { 84 | PJ_LOG(3, (NAME, "Via rport: %d, received: %.*s", via->rport_param, 85 | via->recvd_param.slen, via->recvd_param.ptr)); 86 | } 87 | 88 | int code = rdata->msg_info.msg->line.status.code; 89 | 90 | if (code == 401 || code == 407) { 91 | return PJ_FALSE; // processed with callback 92 | } 93 | 94 | sippak_loop_cancel(); 95 | 96 | return PJ_FALSE; // continue with othe modules 97 | } 98 | 99 | static void send_cb(void *token, pjsip_event *e) 100 | { 101 | pj_status_t status; 102 | pjsip_tx_data *tdata; 103 | pjsip_cred_info cred[1]; 104 | pjsip_transaction *tsx = e->body.tsx_state.tsx; 105 | pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; 106 | struct sippak_app *app = token; 107 | if (tsx->status_code == 401 || tsx->status_code == 407) { 108 | auth_tries++; 109 | if (auth_tries > 1) { 110 | PJ_LOG(1, (NAME, "Authentication failed. Check your username and password")); 111 | sippak_loop_cancel(); 112 | return; 113 | } 114 | sippak_set_cred(app, cred); 115 | 116 | status = pjsip_auth_clt_init (&auth_sess, app->endpt, rdata->tp_info.pool, 0); 117 | if (status != PJ_SUCCESS) { 118 | PJ_LOG(1, (NAME, "Failed init authentication credentials.")); 119 | return; 120 | } 121 | 122 | pjsip_auth_clt_set_credentials(&auth_sess, 1, cred); 123 | 124 | status = pjsip_auth_clt_reinit_req(&auth_sess, rdata, 125 | tsx->last_tx, &tdata); 126 | if (status != PJ_SUCCESS) { 127 | PJ_LOG(1, (NAME, "Failed to re-init client authentication session.")); 128 | return; 129 | } 130 | 131 | pjsip_endpt_send_request(app->endpt, tdata, -1, app, &send_cb); 132 | } 133 | } 134 | 135 | /* Refer */ 136 | PJ_DEF(pj_status_t) sippak_cmd_refer (struct sippak_app *app) 137 | { 138 | pj_status_t status; 139 | pj_str_t *local_addr; 140 | int local_port; 141 | pjsip_tx_data *tdata = NULL; 142 | pj_str_t ruri, from_uri, cnt; 143 | 144 | status = sippak_transport_init(app, &local_addr, &local_port); 145 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transport."); 146 | 147 | status = pjsip_tsx_layer_init_module(app->endpt); 148 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transaction layer."); 149 | 150 | status = pjsip_endpt_register_module(app->endpt, &mod_refer); 151 | SIPPAK_ASSERT_SUCC(status, "Failed to register module mod_refer."); 152 | 153 | ruri = sippak_create_ruri(app); 154 | from_uri = sippak_create_from_hdr(app); 155 | cnt = sippak_create_contact_hdr(app, local_addr, local_port); 156 | 157 | status = pjsip_endpt_create_request(app->endpt, 158 | pjsip_get_refer_method(), // method REFER 159 | &ruri, // request URI 160 | &from_uri, // from header value 161 | &app->cfg.dest, // to header value 162 | &cnt, // Contact header 163 | NULL, // Call-ID 164 | -1, // CSeq 165 | NULL, // body 166 | &tdata); 167 | SIPPAK_ASSERT_SUCC(status, "Failed to create endpoint request."); 168 | 169 | add_referto_hdr(tdata, app); 170 | add_refersub_hdr(tdata, app); 171 | 172 | return pjsip_endpt_send_request(app->endpt, tdata, -1, app, &send_cb); 173 | } 174 | -------------------------------------------------------------------------------- /src/mod/message.c: -------------------------------------------------------------------------------- 1 | /** 2 | * sippak -- SIP command line utility. 3 | * Copyright (C) 2018, Stas Kobzar 4 | * 5 | * This file is part of sippak. 6 | * 7 | * sippak is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * sippak is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with sippak. If not, see . 19 | */ 20 | 21 | /** 22 | * @file ping.c 23 | * @brief sippak send stateless MESSAGE message 24 | * 25 | * @author Stas Kobzar 26 | */ 27 | #include "sippak.h" 28 | 29 | #define NAME "mod_message" 30 | 31 | static pjsip_auth_clt_sess auth_sess; 32 | static int auth_tries = 0; 33 | 34 | static pj_bool_t on_rx_response (pjsip_rx_data *rdata); 35 | static void add_accept_hdr (pjsip_tx_data *tdata); 36 | static void add_message_body(pjsip_tx_data *tdata, struct sippak_app *app); 37 | 38 | enum { PJSIP_MESSAGE_METHOD = PJSIP_OTHER_METHOD }; 39 | const pjsip_method pjsip_message_method = 40 | { 41 | (pjsip_method_e) PJSIP_MESSAGE_METHOD, 42 | { "MESSAGE", 7 } 43 | }; 44 | 45 | static pjsip_module mod_message = 46 | { 47 | NULL, NULL, /* prev, next. */ 48 | { "mod-message", 11 }, /* Name. */ 49 | -1, /* Id */ 50 | PJSIP_MOD_PRIORITY_TRANSPORT_LAYER, /* Priority */ 51 | NULL, /* load() */ 52 | NULL, /* start() */ 53 | NULL, /* stop() */ 54 | NULL, /* unload() */ 55 | NULL, /* on_rx_request() */ 56 | &on_rx_response, /* on_rx_response() */ 57 | NULL, /* on_tx_request. */ 58 | NULL, /* on_tx_response() */ 59 | NULL, /* on_tsx_state() */ 60 | }; 61 | 62 | /* On response module callback */ 63 | static pj_bool_t on_rx_response (pjsip_rx_data *rdata) 64 | { 65 | pjsip_msg *msg = rdata->msg_info.msg; 66 | pjsip_via_hdr *via = rdata->msg_info.via; 67 | 68 | PJ_LOG(3, (NAME, "Response received: %d %.*s", 69 | msg->line.status.code, 70 | msg->line.status.reason.slen, 71 | msg->line.status.reason.ptr)); 72 | if (via) { 73 | PJ_LOG(3, (NAME, "Via rport: %d, received: %.*s", via->rport_param, 74 | via->recvd_param.slen, via->recvd_param.ptr)); 75 | } 76 | 77 | int code = rdata->msg_info.msg->line.status.code; 78 | 79 | if (code == 401 || code == 407) { 80 | return PJ_FALSE; // processed with callback 81 | } 82 | 83 | sippak_loop_cancel(); 84 | 85 | return PJ_FALSE; // continue with othe modules 86 | } 87 | 88 | /* Add accept header. */ 89 | static void add_accept_hdr (pjsip_tx_data *tdata) 90 | { 91 | pjsip_accept_hdr *accept; 92 | accept = pjsip_accept_hdr_create(tdata->pool); 93 | accept->values[0] = pj_str("text/plain"); 94 | accept->count = 1; 95 | 96 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) accept); 97 | } 98 | 99 | /* add message body */ 100 | static void add_message_body(pjsip_tx_data *tdata, struct sippak_app *app) 101 | { 102 | pj_str_t mtype = pj_str("text"); 103 | pj_str_t msubtype = pj_str("plain"); 104 | 105 | if (app->cfg.body.slen > MSG_BODY_LEN) { 106 | PJ_LOG(1, (NAME, 107 | "Message body is too long. Max allowed characters is %d\n", 108 | MSG_BODY_LEN)); 109 | app->cfg.body.slen = MSG_BODY_LEN; 110 | } 111 | 112 | tdata->msg->body = pjsip_msg_body_create( tdata->pool, &mtype, 113 | &msubtype, &app->cfg.body); 114 | } 115 | 116 | /* request callback */ 117 | static void send_cb(void *token, pjsip_event *e) 118 | { 119 | pj_status_t status; 120 | pjsip_tx_data *tdata; 121 | pjsip_cred_info cred[1]; 122 | pjsip_transaction *tsx = e->body.tsx_state.tsx; 123 | pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; 124 | struct sippak_app *app = token; 125 | if (tsx->status_code == 401 || tsx->status_code == 407) { 126 | auth_tries++; 127 | if (auth_tries > 1) { 128 | PJ_LOG(1, (NAME, "Authentication failed. Check your username and password")); 129 | sippak_loop_cancel(); 130 | return; 131 | } 132 | sippak_set_cred(app, cred); 133 | 134 | status = pjsip_auth_clt_init (&auth_sess, app->endpt, rdata->tp_info.pool, 0); 135 | if (status != PJ_SUCCESS) { 136 | PJ_LOG(1, (NAME, "Failed init authentication credentials.")); 137 | return; 138 | } 139 | 140 | pjsip_auth_clt_set_credentials(&auth_sess, 1, cred); 141 | 142 | status = pjsip_auth_clt_reinit_req(&auth_sess, rdata, 143 | tsx->last_tx, &tdata); 144 | if (status != PJ_SUCCESS) { 145 | PJ_LOG(1, (NAME, "Failed to re-init client authentication session.")); 146 | return; 147 | } 148 | 149 | pjsip_endpt_send_request(app->endpt, tdata, -1, app, &send_cb); 150 | } 151 | } 152 | 153 | /* Message */ 154 | PJ_DEF(pj_status_t) sippak_cmd_message (struct sippak_app *app) 155 | { 156 | pj_status_t status; 157 | pj_str_t *local_addr; 158 | int local_port; 159 | 160 | pjsip_tx_data *tdata = NULL; 161 | 162 | pj_str_t from = sippak_create_from_hdr(app); 163 | pj_str_t ruri = sippak_create_ruri(app); 164 | 165 | status = sippak_transport_init(app, &local_addr, &local_port); 166 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transport."); 167 | 168 | status = pjsip_endpt_create_request(app->endpt, 169 | &pjsip_message_method, // method MESSAGE 170 | &ruri, // request URI 171 | &from, // from header value 172 | &app->cfg.dest, // to header value 173 | NULL, // Contact header. User Agents MUST NOT insert 174 | // Contact header fields into MESSAGE requests [RFC 3428] 175 | NULL, // Call-ID 176 | -1, // CSeq 177 | NULL, // body 178 | &tdata); 179 | SIPPAK_ASSERT_SUCC(status, "Failed to create endpoint request."); 180 | 181 | add_accept_hdr (tdata); 182 | 183 | add_message_body (tdata, app); 184 | 185 | status = pjsip_tsx_layer_init_module(app->endpt); 186 | SIPPAK_ASSERT_SUCC(status, "Failed to initiate transaction layer."); 187 | 188 | status = pjsip_endpt_register_module(app->endpt, &mod_message); 189 | SIPPAK_ASSERT_SUCC(status, "Failed to register module mod_message."); 190 | 191 | return pjsip_endpt_send_request(app->endpt, tdata, -1, app, &send_cb); 192 | } 193 | --------------------------------------------------------------------------------