├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin └── oversip ├── create-deb.sh ├── debian ├── changelog ├── compat ├── control ├── copyright ├── oversip.default ├── oversip.init ├── postrm ├── preinst └── rules ├── etc ├── oversip.conf ├── proxies.conf ├── server.rb └── tls │ ├── ca │ └── cacert.pem │ ├── demo-tls.oversip.net.crt │ ├── demo-tls.oversip.net.key │ ├── upgrade-cacert.sh │ └── utils │ ├── create-cert.rb │ └── get-sip-identities.rb ├── ext ├── common │ ├── c_util.h │ └── ruby_c_util.h ├── sip_parser │ ├── common_headers.h │ ├── compile_ragel_files.sh │ ├── compile_ragel_sip_message_parser.sh │ ├── compile_ragel_sip_uri_parser.sh │ ├── ext_help.h │ ├── extconf.rb │ ├── grammar_absolute_uri.rl │ ├── grammar_name_addr.rl │ ├── grammar_sip_core.rl │ ├── grammar_sip_headers.rl │ ├── grammar_sip_message.rl │ ├── grammar_sip_uri.rl │ ├── grammar_tel_uri.rl │ ├── sip_message_parser.c │ ├── sip_message_parser.rl │ ├── sip_parser.h │ ├── sip_parser_ruby.c │ ├── sip_uri_parser.c │ └── sip_uri_parser.rl ├── stud │ └── extconf.rb ├── stun │ ├── ext_help.h │ ├── extconf.rb │ └── stun_ruby.c ├── utils │ ├── compile_ragel_files.sh │ ├── ext_help.h │ ├── extconf.rb │ ├── grammar_ip.rl │ ├── haproxy_protocol.c │ ├── haproxy_protocol.h │ ├── haproxy_protocol.rl │ ├── ip_utils.c │ ├── ip_utils.h │ ├── ip_utils.rl │ ├── outbound_utils.c │ ├── outbound_utils.h │ ├── outbound_utils.rl │ ├── utils_ruby.c │ └── utils_ruby.h ├── websocket_framing_utils │ ├── ext_help.h │ ├── extconf.rb │ ├── ws_framing_utils.h │ └── ws_framing_utils_ruby.c └── websocket_http_parser │ ├── compile_ragel_files.sh │ ├── ext_help.h │ ├── extconf.rb │ ├── grammar_ws_http_core.rl │ ├── grammar_ws_http_headers.rl │ ├── grammar_ws_http_request.rl │ ├── ws_http_parser.c │ ├── ws_http_parser.h │ ├── ws_http_parser.rl │ └── ws_http_parser_ruby.c ├── lib ├── oversip.rb └── oversip │ ├── config.rb │ ├── config_validators.rb │ ├── default_server.rb │ ├── errors.rb │ ├── fiber_pool.rb │ ├── launcher.rb │ ├── logger.rb │ ├── modules │ ├── outbound_mangling.rb │ └── user_assertion.rb │ ├── proxies_config.rb │ ├── ruby_ext │ └── eventmachine.rb │ ├── sip │ ├── client.rb │ ├── client_transaction.rb │ ├── constants.rb │ ├── core.rb │ ├── launcher.rb │ ├── listeners.rb │ ├── listeners │ │ ├── connection.rb │ │ ├── ipv4_tcp_client.rb │ │ ├── ipv4_tcp_server.rb │ │ ├── ipv4_tls_client.rb │ │ ├── ipv4_tls_server.rb │ │ ├── ipv4_tls_tunnel_server.rb │ │ ├── ipv4_udp_server.rb │ │ ├── ipv6_tcp_client.rb │ │ ├── ipv6_tcp_server.rb │ │ ├── ipv6_tls_client.rb │ │ ├── ipv6_tls_server.rb │ │ ├── ipv6_tls_tunnel_server.rb │ │ ├── ipv6_udp_server.rb │ │ ├── tcp_client.rb │ │ ├── tcp_connection.rb │ │ ├── tcp_server.rb │ │ ├── tls_client.rb │ │ ├── tls_server.rb │ │ ├── tls_tunnel_connection.rb │ │ ├── tls_tunnel_server.rb │ │ └── udp_connection.rb │ ├── message.rb │ ├── message_processor.rb │ ├── name_addr.rb │ ├── proxy.rb │ ├── request.rb │ ├── response.rb │ ├── rfc3263.rb │ ├── server_transaction.rb │ ├── sip.rb │ ├── tags.rb │ ├── timers.rb │ ├── transport_manager.rb │ ├── uac.rb │ ├── uac_request.rb │ └── uri.rb │ ├── syslog.rb │ ├── system_callbacks.rb │ ├── tls.rb │ ├── utils.rb │ ├── version.rb │ └── websocket │ ├── constants.rb │ ├── http_request.rb │ ├── launcher.rb │ ├── listeners.rb │ ├── listeners │ ├── connection.rb │ ├── ipv4_ws_server.rb │ ├── ipv4_wss_server.rb │ ├── ipv4_wss_tunnel_server.rb │ ├── ipv6_ws_server.rb │ ├── ipv6_wss_server.rb │ ├── ipv6_wss_tunnel_server.rb │ ├── ws_server.rb │ ├── wss_server.rb │ └── wss_tunnel_server.rb │ ├── websocket.rb │ ├── ws_framing.rb │ └── ws_sip_app.rb ├── oversip.gemspec ├── test ├── oversip_test_helper.rb ├── test_http_parser.rb ├── test_name_addr.rb ├── test_name_addr_parser.rb ├── test_sip_message_parser.rb ├── test_sip_uri_parser.rb └── test_uri.rb └── thirdparty └── stud ├── NOTES └── stud.tar.gz /.gitignore: -------------------------------------------------------------------------------- 1 | /*.gem 2 | /.bundle 3 | /.config 4 | /test/tmp 5 | /tmp 6 | /Gemfile.lock 7 | *.so 8 | *.o 9 | *.bundle 10 | .DS_Store 11 | Makefile 12 | mkmf.log 13 | /bin/oversip_stud 14 | /thirdparty/stud/stud/ 15 | /OLD 16 | /TODO 17 | /NO_GIT 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | - 2.0.0 5 | - 2.1 6 | - 2.2 7 | - 2.3 8 | - 2.4.0-preview2 9 | 10 | before_install: 11 | - gem install bundler -v 1.12.6 12 | - sudo apt-get install libev-dev libssl-dev 13 | 14 | after_script: 15 | - rake test 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | MAIN AUTHOR 2 | =========== 3 | 4 | - Iñaki Baz Castillo (Github @ibc) 5 | 6 | 7 | 8 | CONTRIBUTORS 9 | ============ 10 | 11 | - Jon Bonilla (Github @manwe) 12 | 13 | Lots of help with packaging for Debian based distributions. 14 | The first deployment of OverSIP in production for thousands of clients. 15 | 16 | - Saúl Ibarra Corretgé (Github @saghul) 17 | 18 | Testing, ideas, proposals, too many proposals. 19 | 20 | - José Luis Millán (Github @jmillan) 21 | 22 | Intensive testing with JsSIP library acting as a SIP Outbound UA with OverSIP. 23 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | 5 | group :test do 6 | gem "rake", "~> 10.3.2" 7 | end 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Name: OverSIP 2 | Maintainer: Iñaki Baz Castillo 3 | Copyright (c) 2012-2016 Iñaki Baz Castillo 4 | 5 | 6 | License: The MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | [![Build Status](https://secure.travis-ci.org/versatica/OverSIP.png?branch=master)](http://travis-ci.org/versatica/OverSIP) 4 | 5 | ## Notice 6 | 7 | This project is no longer maintained. If you wish to maintain it, contact the authors (see below). 8 | 9 | 10 | ## Website 11 | 12 | * [www.oversip.versatica.com](http://www.oversip.versatica.com) 13 | 14 | 15 | ## Overview 16 | 17 | OverSIP is a powerful and flexible SIP proxy & server by the authors of [RFC 7118](http://tools.ietf.org/html/rfc7118) (*The WebSocket Protocol as a Transport for SIP*): 18 | 19 | * Works on Linux/BSD/OSX 20 | * Fully asynchronous event-based design, never block! 21 | * Enjoy coding your SIP logic in Ruby language, feel free to code whatever you need! 22 | * Fast: core and message parsers written in C language 23 | * SIP over UDP, TCP, TLS and WebSocket (use true SIP in your web apps) 24 | * Full support for IPv4, IPv6 and DNS resolution (NAPTR, SRV, A, AAAA) 25 | * The perfect Outbound Edge Proxy 26 | * Written by the authors of [RFC 7118 "The WebSocket Protocol as a Transport for SIP"](http://tools.ietf.org/html/rfc7118) and [JsSIP](http://jssip.net) 27 | 28 | 29 | ## Documentation 30 | 31 | * [www.oversip.versatica.com/documentation](http://www.oversip.versatica.com/documentation/) 32 | 33 | 34 | ## Authors 35 | 36 | ### Main Author 37 | 38 | * Iñaki Baz Castillo ( | [github](https://github.com/ibc) | [twitter](https://twitter.com/ibc_tw)) 39 | 40 | ### Contributors 41 | 42 | * José Luis Millán ( | [github](https://github.com/jmillan) | [twitter](https://twitter.com/jomivi)) 43 | * Saúl Ibarra Corretgé ( | [github](https://github.com/saghul) | [twitter](https://twitter.com/saghul)) 44 | * Jon Bonilla ( | [github](https://github.com/manwe) | [twitter](https://twitter.com/jbmanwe)) 45 | 46 | ## License 47 | 48 | OverSIP is released under the [MIT license](http://www.oversip.versatica.com/license). 49 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/testtask" 2 | require "rake/clean" 3 | 4 | 5 | OVERSIP_EXTENSIONS = [ 6 | { :dir => "ext/sip_parser", :lib => "sip_parser.#{RbConfig::CONFIG["DLEXT"]}", :dest => "lib/oversip/sip" }, 7 | { :dir => "ext/stun", :lib => "stun.#{RbConfig::CONFIG["DLEXT"]}", :dest => "lib/oversip" }, 8 | { :dir => "ext/utils", :lib => "utils.#{RbConfig::CONFIG["DLEXT"]}", :dest => "lib/oversip" }, 9 | { :dir => "ext/websocket_framing_utils", :lib => "ws_framing_utils.#{RbConfig::CONFIG["DLEXT"]}", :dest => "lib/oversip/websocket" }, 10 | { :dir => "ext/websocket_http_parser", :lib => "ws_http_parser.#{RbConfig::CONFIG["DLEXT"]}", :dest => "lib/oversip/websocket" }, 11 | ] 12 | 13 | OVERSIP_EXTENSIONS.each do |ext| 14 | file ext[:lib] => Dir.glob(["#{ext[:dir]}/*{.c,.h}"]) do 15 | Dir.chdir(ext[:dir]) do 16 | ruby "extconf.rb" 17 | sh "make" 18 | end 19 | cp "#{ext[:dir]}/#{ext[:lib]}", "#{ext[:dest]}/" 20 | end 21 | 22 | CLEAN.include("#{ext[:dir]}/*{.o,.log,.so,.a,.bundle}") 23 | CLEAN.include("#{ext[:dir]}/Makefile") 24 | CLEAN.include("#{ext[:dest]}/#{ext[:lib]}") 25 | end 26 | 27 | # Stud stuff. 28 | directory "tmp" 29 | file "bin/oversip_stud" => "tmp" do 30 | Dir.chdir("ext/stud") do 31 | ruby "extconf.rb" 32 | end 33 | FileUtils.remove_dir "tmp" 34 | end 35 | CLEAN.include("ext/stud/Makefile") 36 | CLEAN.include("thirdparty/stud/mkmf.log") 37 | CLEAN.include("bin/oversip_stud") 38 | 39 | 40 | OVERSIP_COMPILE_ITEMS = OVERSIP_EXTENSIONS.map {|e| e[:lib]} << "bin/oversip_stud" 41 | 42 | task :default => :compile 43 | 44 | desc "Compile" 45 | task :compile => OVERSIP_COMPILE_ITEMS 46 | 47 | Rake::TestTask.new do |t| 48 | t.libs << "test" 49 | end 50 | 51 | # Make the :test task depend on the shared object, so it will be built automatically 52 | # before running the tests. 53 | desc "Run tests" 54 | task :test => OVERSIP_COMPILE_ITEMS 55 | -------------------------------------------------------------------------------- /create-deb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Using this the generated stuf is clean after package is built. 4 | # Options -us and -uc prevent the package from being signed. 5 | dpkg-buildpackage -tc -us -uc 6 | 7 | # Similar option. The second command cleans the generated stuf. 8 | #debuild -us -uc 9 | #./debian/rules clean 10 | 11 | # Clean debian/files and the log file. 12 | rm -f debian/files 13 | rm -f debian/oversip.debhelper.log 14 | 15 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | oversip (2.0.4) stable; urgency=high 2 | * Package for OverSIP Ruby Gem 2.0.4. 3 | -- Iñaki Baz Castillo Fri, 19 Aug 2016 13:11:00 +0100 4 | 5 | oversip (2.0.3) stable; urgency=low 6 | * Package for OverSIP Ruby Gem 2.0.3. 7 | -- Iñaki Baz Castillo Fri, 29 Jan 2016 18:15:00 +0100 8 | 9 | oversip (2.0.2) stable; urgency=low 10 | * Package for OverSIP Ruby Gem 2.0.2. 11 | -- Iñaki Baz Castillo Fri, 29 Jan 2016 18:00:00 +0100 12 | 13 | oversip (2.0.1) stable; urgency=low 14 | * Package for OverSIP Ruby Gem 2.0.1. 15 | -- Iñaki Baz Castillo Tue, 24 Feb 2015 20:00:00 +0100 16 | 17 | oversip (2.0.0) stable; urgency=low 18 | * Package for OverSIP Ruby Gem 2.0.0. 19 | -- Iñaki Baz Castillo Wed, 24 Sep 2014 23:00:00 +0100 20 | 21 | oversip (1.4.1) stable; urgency=low 22 | * Package for OverSIP Ruby Gem 1.4.1. 23 | -- Iñaki Baz Castillo Mon, 16 Sep 2013 16:35:00 +0100 24 | 25 | oversip (1.4.0) stable; urgency=low 26 | * Package for OverSIP Ruby Gem 1.4.0. 27 | -- Iñaki Baz Castillo Sun, 15 Sep 2013 16:10:00 +0100 28 | 29 | oversip (1.3.8) stable; urgency=low 30 | * Package for OverSIP Ruby Gem 1.3.8. 31 | -- Iñaki Baz Castillo Thu, 16 May 2013 13:10:00 +0100 32 | 33 | oversip (1.3.7) stable; urgency=low 34 | * Package for OverSIP Ruby Gem 1.3.7. 35 | -- Iñaki Baz Castillo Mon, 28 Jan 2013 22:00:00 +0100 36 | 37 | oversip (1.3.6) stable; urgency=low 38 | * Package for OverSIP Ruby Gem 1.3.6. 39 | -- Iñaki Baz Castillo Thu, 03 Jan 2013 13:00:00 +0100 40 | 41 | oversip (1.3.5) stable; urgency=low 42 | * Package for OverSIP Ruby Gem 1.3.5. 43 | -- Iñaki Baz Castillo Mon, 17 Dec 2012 23:20:00 +0100 44 | 45 | oversip (1.3.3) stable; urgency=low 46 | * Package for OverSIP Ruby Gem 1.3.3. 47 | -- Iñaki Baz Castillo Thu, 15 Nov 2012 23:20:00 +0100 48 | 49 | oversip (1.3.2) stable; urgency=low 50 | * Package for OverSIP Ruby Gem 1.3.2. 51 | -- Iñaki Baz Castillo Sat, 03 Nov 2012 19:06:00 +0100 52 | 53 | oversip (1.3.1) stable; urgency=high 54 | * Package for OverSIP Ruby Gem 1.3.1. 55 | -- Iñaki Baz Castillo Thu, 04 Oct 2012 23:30:00 +0100 56 | 57 | oversip (1.3.0) stable; urgency=low 58 | * Package for OverSIP Ruby Gem 1.3.0. 59 | -- Iñaki Baz Castillo Thu, 04 Oct 2012 17:25:00 +0100 60 | 61 | oversip (1.2.1) stable; urgency=high 62 | * Package for OverSIP Ruby Gem 1.2.1. 63 | -- Iñaki Baz Castillo Thu, 06 Aug 2012 02:10:00 +0100 64 | 65 | oversip (1.2.0) stable; urgency=low 66 | * Package for OverSIP Ruby Gem 1.2.X. 67 | -- Iñaki Baz Castillo Tue, 04 Aug 2012 12:00:00 +0100 68 | 69 | oversip (1.1.0) stable; urgency=low 70 | * Package for OverSIP Ruby Gem 1.1.X. 71 | -- Iñaki Baz Castillo Fri, 03 Aug 2012 17:53:00 +0100 72 | 73 | oversip (1.0.0) stable; urgency=low 74 | * Initial release. 75 | -- Iñaki Baz Castillo Mon, 09 Jul 2012 21:21:00 +0100 76 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: oversip 2 | Section: comm 3 | Priority: optional 4 | Maintainer: Iñaki Baz Castillo 5 | Homepage: http://www.oversip.net 6 | Build-Depends: debhelper (>= 7) 7 | Standards-Version: 3.9.3 8 | 9 | Package: oversip 10 | Architecture: all 11 | Pre-Depends: ${misc:Depends}, ruby (>= 1.9.3), ruby-dev (>= 1.9.3), make, g++, libssl-dev, libev-dev 12 | Suggests: unbound 13 | Description: OverSIP (the SIP framework you dreamed about) 14 | OverSIP is an async SIP proxy/server programmable in Ruby language. 15 | Some features of OverSIP are: 16 | - SIP transports: UDP, TCP, TLS and WebSocket. 17 | - Full IPv4 and IPv6 support. 18 | - RFC 3263: SIP DNS mechanism (NAPTR, SRV, A, AAAA) for failover and load 19 | balancing based on DNS. 20 | - RFC 5626: OverSIP is a perfect Outbound Edge Proxy, including an integrated 21 | STUN server. 22 | - Fully programmable in Ruby language (make SIP easy). 23 | - Fast and efficient: OverSIP core is coded in C language. 24 | OverSIP is build on top of EventMachine async library which follows the Reactor 25 | Design Pattern, allowing thousands of concurrent connections and requests in a 26 | never-blocking fashion. 27 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Name: OverSIP 2 | Maintainer: Iñaki Baz Castillo 3 | Copyright (c) 2012-2016 Iñaki Baz Castillo 4 | 5 | 6 | License: The MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /debian/oversip.default: -------------------------------------------------------------------------------- 1 | # 2 | # oversip startup options 3 | # 4 | 5 | # Set to 'yes' when configured. 6 | RUN=no 7 | 8 | # User to run as. 9 | USER=oversip 10 | 11 | # Group to run as. 12 | GROUP=oversip 13 | 14 | # Directory with the configuration files. 15 | # By default '/etc/oversip/'. 16 | #CONFIG_DIR=/etc/oversip/ 17 | 18 | # Main configuration file name (within the configuration directory). 19 | # By default 'oversip.conf'. 20 | #CONFIG_FILE=oversip.conf 21 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Doc: http://wiki.debian.org/MaintainerScripts 4 | 5 | #DEBHELPER# 6 | 7 | case "$1" in 8 | 9 | purge) 10 | # Remove the Debian system user/group. 11 | deluser --quiet --remove-home oversip &>/dev/null || true 12 | 13 | # Remove the Ruby gem. 14 | echo "uninstalling 'oversip' Ruby Gem(s)..." 15 | gem uninstall oversip -a -x -I 16 | ;; 17 | 18 | esac 19 | 20 | exit 0 21 | 22 | #DEBHELPER# 23 | -------------------------------------------------------------------------------- /debian/preinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Doc: http://wiki.debian.org/MaintainerScripts 4 | 5 | #DEBHELPER# 6 | 7 | set -e 8 | 9 | 10 | OVERSIP_GEM_VERSION="2.0.4" 11 | 12 | 13 | install_gem() { 14 | echo "installing 'oversip' Ruby Gem version $OVERSIP_GEM_VERSION..." 15 | gem install oversip --no-rdoc --no-ri -v $OVERSIP_GEM_VERSION 16 | } 17 | 18 | case "$1" in 19 | 20 | install) 21 | # Add a Debian system user/group called "oversip". 22 | adduser --quiet --system --group --disabled-password \ 23 | --shell /bin/false --gecos "OverSIP" \ 24 | --home /var/run/oversip oversip || true 25 | 26 | # Install the Ruby gem. 27 | install_gem 28 | ;; 29 | 30 | upgrade) 31 | # Install the Ruby gem. 32 | install_gem 33 | ;; 34 | 35 | esac 36 | 37 | exit 0 38 | 39 | #DEBHELPER# 40 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | 7 | build: build-stamp 8 | 9 | build-stamp: 10 | dh_testdir 11 | touch $@ 12 | 13 | clean: 14 | dh_testdir 15 | dh_testroot 16 | rm -rf build-stamp oversip 17 | dh_prep 18 | 19 | install: build 20 | dh_testdir 21 | dh_testroot 22 | dh_prep -k 23 | dh_installdirs /etc 24 | 25 | mkdir -p $(CURDIR)/debian/oversip/etc/oversip/ 26 | mkdir -p $(CURDIR)/debian/oversip/etc/oversip/tls/ 27 | mkdir -p $(CURDIR)/debian/oversip/etc/oversip/tls/ca/ 28 | mkdir -p $(CURDIR)/debian/oversip/etc/oversip/tls/utils/ 29 | 30 | install -m 644 etc/oversip.conf debian/oversip/etc/oversip/ 31 | install -m 644 etc/proxies.conf debian/oversip/etc/oversip/ 32 | install -m 644 etc/server.rb debian/oversip/etc/oversip/ 33 | install -m 755 etc/tls/upgrade-cacert.sh debian/oversip/etc/oversip/tls/ 34 | install -m 644 etc/tls/demo-tls.oversip.net.crt debian/oversip/etc/oversip/tls/ 35 | install -m 600 etc/tls/demo-tls.oversip.net.key debian/oversip/etc/oversip/tls/ 36 | install -m 644 etc/tls/ca/* debian/oversip/etc/oversip/tls/ca/ 37 | install -m 755 etc/tls/utils/* debian/oversip/etc/oversip/tls/utils/ 38 | 39 | # Build architecture-dependent files here. 40 | binary-arch: install 41 | # We have nothing to do by default. 42 | 43 | # Build architecture-independent files here. 44 | binary-indep: install 45 | dh_testdir 46 | dh_testroot 47 | dh_installchangelogs 48 | dh_installdocs 49 | dh_installexamples 50 | dh_installman 51 | dh_installinit --restart-after-upgrade -- defaults 20 52 | dh_link 53 | dh_strip 54 | dh_compress 55 | dh_fixperms 56 | dh_installcron 57 | # dh_makeshlibs 58 | dh_installdeb 59 | dh_shlibdeps 60 | dh_gencontrol 61 | dh_md5sums 62 | dh_builddeb 63 | 64 | binary: binary-indep binary-arch 65 | .PHONY: build clean binary-indep binary-arch binary install 66 | -------------------------------------------------------------------------------- /etc/proxies.conf: -------------------------------------------------------------------------------- 1 | # 2 | # OverSIP - Proxies Configuration. 3 | # 4 | # 5 | # IMPORTANT: 6 | # This is a YAML [1] format configuration file. DON'T USE tab for indentation 7 | # as it's not allowed and would raise unexpected errors. Instead, respect 8 | # the existing indentation spaces. 9 | # [1] http://en.wikipedia.org/wiki/YAML 10 | 11 | 12 | # Default proxy configuration. 13 | # 14 | default_proxy: 15 | 16 | # For initial INVITE, SUBSCRIBE and REFER requests and in-dialog NOTIFY the proxy adds Record-Route header(s). 17 | # For REGISTER requests the proxy adds Path header(s). 18 | # By default _yes_. 19 | # 20 | do_record_routing: yes 21 | 22 | # Enable DNS cache. By default _yes_. 23 | # 24 | use_dns_cache: yes 25 | 26 | # DNS cache time (in seconds). A DNS result is removed from the cache after the given time. 27 | # Minimum value is 300. Default value is 300. 28 | # 29 | dns_cache_time: 300 30 | 31 | # Enable destination blacklist. When a destination (target) fails due to timeout, connection error 32 | # or TLS validation error, the target is added to a temporal blacklist and requests to same 33 | # targets are not tryed again until the entry in the blacklist expires. By default _yes_. 34 | # 35 | use_blacklist: yes 36 | 37 | # Blacklist expiration time (in seconds). The time of live of failed targets within the blacklist. 38 | # 39 | blacklist_time: 400 40 | 41 | # Use DNS NAPTR. If set, NAPTR query is performed when URI host is a domain, has no port nor 42 | # ;transport param. 43 | # Default value is _yes_. 44 | # 45 | use_naptr: yes 46 | 47 | # Use DNS SRV. If set, SRV query is performed when URI host is a domain and has no port. 48 | # If this is set to _no_ then _use_naptr_ is also set to _no_. 49 | # Default value is _yes_. 50 | # 51 | use_srv: yes 52 | 53 | # Transport preference. The list of supported transports in order of preference. 54 | # When there is NAPTR record, its SRV records are tryed in this order just in the case 55 | # _force_transport_preference_ is _yes_. 56 | # If there is not NAPTR record, SRV records are then tryed in this order. 57 | # Valid transports are "udp", "tcp" and "tls". 58 | # Default value is ["tls", "tcp", "udp"] (first try "tls"). 59 | # 60 | transport_preference: ["tls", "tcp", "udp"] 61 | 62 | # Force transport preference. If _no_, transport preference is taken from NAPTR records 63 | # (when present). If _yes_, transport preferences are taken from transport_preference 64 | # parameter even for NAPTR records. 65 | # Default value is _no_. 66 | # 67 | force_transport_preference: no 68 | 69 | # IP type preference. When both IPv4 and IPv6 are available, this parameter determines 70 | # whether to try first DNS A or AAAA queries. It also determines the IP type this proxy 71 | # is allowed to use for routing requests. 72 | # Valid IP types are "ipv4" and "ipv6". 73 | # Default value is ["ipv4", "ipv6"] (first try "ipv4"). 74 | # 75 | ip_type_preference: ["ipv4", "ipv6"] 76 | 77 | # DNS failover on received 503. 78 | # If a DNS query retrieves more than a single destinations and the first attempt 79 | # receives a 503 response, then OverSIP tries the next destination (when this parameter 80 | # is set) or replies a 500 error upstream (when not set). 81 | # Default value is _yes_. 82 | # 83 | dns_failover_on_503: yes 84 | 85 | # INVITE transaction timeout (in seconds). 86 | # Time waiting for a provisional or final response. 87 | # Minimum value is 2, maximum value is 64. 88 | # Default value is 32. 89 | # 90 | timer_B: 32 91 | 92 | # Proxy INVITE transaction timeout (in seconds). 93 | # Time waiting for a final response. 94 | # Minimum value is 8, maximum value is 180. 95 | # Default value is 120. 96 | # 97 | timer_C: 120 98 | 99 | # Non-INVITE transaction timeout (in seconds). 100 | # Time waiting for a final response. 101 | # Minimum value is 2, maximum value is 64. 102 | # Default value is 32. 103 | # 104 | timer_F: 32 105 | 106 | # Call the OverSIP::SIP.on_server_tls_handshake() callback when 107 | # establishing an outbound SIP TLS connection with a remote SIP peer. 108 | # By default _yes_. 109 | # 110 | callback_on_server_tls_handshake: yes 111 | 112 | 113 | # Proxy configuration for routing in-dialog requests. 114 | # 115 | proxy_in_dialog: 116 | 117 | use_dns: yes 118 | use_dns_cache: yes 119 | dns_cache_time: 300 120 | use_naptr: no 121 | use_srv: no 122 | timer_B: 32 123 | timer_C: 60 124 | timer_F: 32 125 | 126 | 127 | # Proxy configuration for routing initial requests to clients. 128 | proxy_to_users: 129 | 130 | use_dns: no 131 | dns_failover_on_503: no 132 | timer_B: 32 133 | timer_F: 32 134 | 135 | 136 | # Proxy configuration for routing initial requests to the external world. 137 | proxy_out: 138 | 139 | dns_failover_on_503: yes 140 | timer_B: 6 141 | timer_C: 60 142 | timer_F: 6 143 | 144 | 145 | # Add your own proxy configurations here and/or replace the above ones. 146 | -------------------------------------------------------------------------------- /etc/tls/demo-tls.oversip.net.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICrzCCAhigAwIBAgIET/1hdzANBgkqhkiG9w0BAQUFADBxMR0wGwYDVQQDDBRk 3 | ZW1vLXRscy5vdmVyc2lwLm5ldDELMAkGA1UEBhMCRVMxEjAQBgNVBAoMCVZlcnNh 4 | dGljYTEQMA4GA1UECwwHT3ZlclNJUDEdMBsGCgmSJomT8ixkAQMMDWliY0BhbGlh 5 | eC5uZXQwHhcNMTIwNzEwMTEyMDMyWhcNMTcwNzEwMTEyMDMyWjBxMR0wGwYDVQQD 6 | DBRkZW1vLXRscy5vdmVyc2lwLm5ldDELMAkGA1UEBhMCRVMxEjAQBgNVBAoMCVZl 7 | cnNhdGljYTEQMA4GA1UECwwHT3ZlclNJUDEdMBsGCgmSJomT8ixkAQMMDWliY0Bh 8 | bGlheC5uZXQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALXVloxokaERx4xL 9 | 0pH4rEe5liijlScKLGFJtpESUiG1pMTtWCxNzNTZ4J6mgdE07umS7567tHAEpRbr 10 | C+yJ+VzoLNEpOf+x9zm83NTs3xg55SbhfVEL1vQqlnsfr5YG0iTy2znUPM3r3LVS 11 | rTXz9UsIpnJO9ICvi28wz2a+HgStAgMBAAGjVDBSMAwGA1UdEwQFMAMBAf8wHQYD 12 | VR0OBBYEFLMUKsF6Xm3uI2UnBTXZihnyOv90MCMGA1UdEQQcMBqGGHNpcDpkZW1v 13 | LXRscy5vdmVyc2lwLm5ldDANBgkqhkiG9w0BAQUFAAOBgQAPj8XD5/snSPgjJocn 14 | TpWqbOIbsQBMn11+sRpftf2SsC82wQ4iZy3E1nEDVItO+YzGkxtt2VV+uFoWYNKp 15 | kzlTJDjvuE2lHwpiWgHIK4qcuC3NBYRKqopfmEtNj2vojQ3DPqyOA5v1YZyGw+SI 16 | AhSZ8W6FTvfiHCO5ut/d7036VA== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /etc/tls/demo-tls.oversip.net.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQC11ZaMaJGhEceMS9KR+KxHuZYoo5UnCixhSbaRElIhtaTE7Vgs 3 | TczU2eCepoHRNO7pku+eu7RwBKUW6wvsiflc6CzRKTn/sfc5vNzU7N8YOeUm4X1R 4 | C9b0KpZ7H6+WBtIk8ts51DzN69y1Uq018/VLCKZyTvSAr4tvMM9mvh4ErQIDAQAB 5 | AoGAVFzCOmaRmk8ra9YJ3hunoqdiGXy7yJ8ZtBGFGI2NeYJS7eLIU9XMwLxNUI4k 6 | ELIkXk4Dynt/3bDp/1YR9C6XeFEZkmLcA3jbaX74/mcx6GgeCdAUg1bvrzpSFqyk 7 | UYUw2ioFTKyfI1Z3VQmtWDxtz9BkHQ7uakOyu/HA8N8m0EECQQDe1velUoiQUTTn 8 | DlsdrMvwFhBvJHstXzZMA6Rwp7HKwh6kmaqycr4Lv5UZX/6nzpqiM7EO7eCD6l9n 9 | x7zIoOD5AkEA0OSHbCzJr0Wlxuq8joQe+ZXRz5BS+A3XWLG2ahnw/FdtLuxzNsWi 10 | PdDiUOO4xdPoVj/9l2xwkPTfDLsxmDRiVQJAWfZ7QBkT3P+L1gQrsM1EAAdIVzZp 11 | LCYWK5YE2x44Xt0Dtfv7t9Mu+ls7/GSO0HxOXVF1F8vdKiSCo8k1Y+HfMQJAcrLY 12 | zQP2ph+2+/cOG67eFys1biQP+pYXBWNnBvFBij0y/U3loVB5Wjnk2od/gFhvvVQb 13 | mVZ4pI9gHex3OdyhlQJAXNLuufGOEjnUC8lsmupiAQApMXscYPP0ahNES+hfX5uS 14 | ylXyHsIJp7h6tBbK/bv/BW4HYFsWUpLbjl71EC2ymg== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /etc/tls/upgrade-cacert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script downloads the Root CAs list from Mozilla and stores 4 | # it under ca/ directory for TLS validation. 5 | # cacert.pem is downloaded from http://curl.haxx.se/docs/caextract.html 6 | # (the exact link is http://curl.haxx.se/ca/cacert.pem). 7 | # cacert.pem is just downloaded in case the server version is newer than 8 | # the local version of the file. 9 | 10 | cd ca/ 11 | wget -N http://curl.haxx.se/ca/cacert.pem 12 | 13 | -------------------------------------------------------------------------------- /etc/tls/utils/get-sip-identities.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Runs as follows: 4 | # 5 | # ~$ ruby get-sip-identities.rb PEM_FILE 6 | 7 | 8 | require "openssl" 9 | 10 | 11 | module TLS 12 | 13 | # Extracts the SIP identities in a public certificate following 14 | # the mechanism in http://tools.ietf.org/html/rfc5922#section-7.1 15 | # and returns an array containing them. 16 | # 17 | # Arguments: 18 | # - _cert_: must be a public X.509 certificate in PEM format. 19 | # 20 | def self.get_sip_identities cert 21 | puts "DEBUG: following rules in RFC 5922 \"Domain Certificates in SIP\" section 7.1 \"Finding SIP Identities in a Certificate\"" 22 | verify_subjectAltName_DNS = true 23 | verify_CN = true 24 | subjectAltName_URI_sip_entries = [] 25 | subjectAltName_DNS_entries = [] 26 | sip_identities = {} 27 | 28 | cert.extensions.each do |ext| 29 | next if ext.oid != "subjectAltName" 30 | verify_CN = false 31 | 32 | ext.value.split(/,\s+/).each do |name| 33 | if /^URI:sip:([^@]*)/i =~ name 34 | verify_subjectAltName_DNS = false 35 | subjectAltName_URI_sip_entries << $1.downcase 36 | elsif verify_subjectAltName_DNS && /^DNS:(.*)/i =~ name 37 | subjectAltName_DNS_entries << $1.downcase 38 | end 39 | end 40 | end 41 | 42 | unless verify_CN 43 | puts "DEBUG: certificate contains 'subjectAltName' extensions, 'CommonName' ignored" 44 | unless verify_subjectAltName_DNS 45 | subjectAltName_URI_sip_entries.each {|domain| sip_identities[domain] = true} 46 | puts "DEBUG: 'subjectAltName' entries of type \"URI:sip:\" found, 'subjectAltName' entries of type \"DNS\" ignored" 47 | else 48 | subjectAltName_DNS_entries.each {|domain| sip_identities[domain] = true} 49 | puts "DEBUG: 'subjectAltName' entries of type \"URI:sip:\" not found, using 'subjectAltName' entries of type \"DNS\"" 50 | end 51 | 52 | else 53 | puts "DEBUG: no 'subjectAltName' extension found, using 'CommonName' value" 54 | cert.subject.to_a.each do |oid, value| 55 | if oid == "CN" 56 | sip_identities[value.downcase] = true 57 | break 58 | end 59 | end 60 | end 61 | 62 | return sip_identities 63 | end 64 | 65 | end 66 | 67 | 68 | unless (file = ARGV[0]) 69 | $stderr.puts "ERROR: no file given as argument" 70 | exit false 71 | end 72 | 73 | unless ::File.file?(file) and ::File.readable?(file) 74 | $stderr.puts "ERROR: given file is not a readable file" 75 | exit false 76 | end 77 | 78 | begin 79 | cert = ::OpenSSL::X509::Certificate.new(::File.read(file)) 80 | rescue => e 81 | $stderr.puts "ERROR: cannot get a PEM certificate in the given file: #{e.message} (#{e.class})" 82 | exit false 83 | end 84 | 85 | sip_identities = TLS.get_sip_identities cert 86 | 87 | puts 88 | if sip_identities.any? 89 | puts "SIP identities found in the certificate:" 90 | puts 91 | sip_identities.each_key {|name| puts " - #{name}"} 92 | else 93 | puts "No SIP identities found in the certificate" 94 | end 95 | puts 96 | -------------------------------------------------------------------------------- /ext/common/c_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generic C functions and macros go here, there are no dependencies 3 | * on OverSIP internal structures or the Ruby C API in here. 4 | */ 5 | 6 | #ifndef c_util_h 7 | #define c_util_h 8 | 9 | 10 | #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) 11 | 12 | 13 | /* 14 | * str_to_int: Given a pointer to char and length returns an int (but just possitive). 15 | */ 16 | static int str_to_int(const char* str, size_t len) 17 | { 18 | TRACE(); 19 | int number = 0; 20 | const char *s = str; 21 | 22 | while (len--) { 23 | /* Ignore zeroes at the beginning. */ 24 | if (number || *s != '0') 25 | number = number*10 + (*s)-'0'; 26 | s++; 27 | } 28 | return number; 29 | } 30 | 31 | 32 | /* 33 | * strnchr: Find the first character in a length limited string. 34 | * @s: The string to be searched 35 | * @len: The number of characters to be searched 36 | * @c: The character to search for 37 | */ 38 | static char *strnchr(const char *s, size_t len, size_t c) 39 | { 40 | TRACE(); 41 | for (; len--; ++s) 42 | if (*s == (char)c) 43 | return (char *)s; 44 | return NULL; 45 | } 46 | 47 | 48 | /* 49 | * str_find_upcase: Returns non zero if the string (*str, len) contains at least 50 | * an upcase letter. 51 | */ 52 | static char *str_find_upcase(const char *s, size_t len) 53 | { 54 | TRACE(); 55 | for (; len--; ++s) 56 | if (*s >= 'A' && *s <= 'Z') 57 | return (char *)s; 58 | return NULL; 59 | } 60 | 61 | 62 | /* 63 | * capitalizes all lower-case ASCII characters. 64 | */ 65 | static void downcase_char(char *c) 66 | { 67 | TRACE(); 68 | if (*c >= 'A' && *c <= 'Z') 69 | *c += 32; 70 | } 71 | 72 | 73 | #endif 74 | 75 | -------------------------------------------------------------------------------- /ext/common/ruby_c_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generic Ruby C functions and macros go here. 3 | */ 4 | 5 | #ifndef ruby_c_util_h 6 | #define ruby_c_util_h 7 | 8 | 9 | #include 10 | #include /* Required: http://redmine.ruby-lang.org/issues/show/4272 */ 11 | #include "c_util.h" 12 | 13 | 14 | #define RB_STR_UTF8_NEW(s, len) (rb_enc_str_new(s, len, rb_utf8_encoding())) 15 | 16 | 17 | /* 18 | * my_rb_str_hex_unescape: Unescapes hexadecimal encoded symbols (%NN). 19 | */ 20 | static VALUE my_rb_str_hex_unescape(const char *str, size_t len) 21 | { 22 | TRACE(); 23 | /* Check if hexadecimal unescaping is required. */ 24 | if (strnchr(str, len, '%')) { 25 | char *new_str; 26 | VALUE str_unescaped; 27 | 28 | new_str = ALLOC_N(char, len); 29 | memcpy(new_str, str, len); 30 | 31 | char *s, *t; 32 | char hex[3] = {0, 0, 0}; 33 | int i; 34 | 35 | for (s = t = new_str, i = 0 ; i < len ; s++, i++) { 36 | if (*s != '%' || !(*(s+1)) || !(*(s+2))) 37 | *t++ = *s; 38 | else { 39 | hex[0] = *(s+1); 40 | hex[1] = *(s+2); 41 | *t++ = (strtol(hex, NULL, 16) & 0xFF); 42 | s += 2; 43 | len -= 2; 44 | } 45 | } 46 | 47 | str_unescaped = RB_STR_UTF8_NEW(new_str, len); 48 | xfree(new_str); 49 | return(str_unescaped); 50 | } 51 | /* If unescaping is not required, then create a Ruby string with original pointer and length. */ 52 | else 53 | return(RB_STR_UTF8_NEW(str, len)); 54 | } 55 | 56 | /* 57 | * my_rb_str_downcase: Downcases a string formed by simple symbols (ASCII). 58 | */ 59 | static VALUE my_rb_str_downcase(const char *str, size_t len) 60 | { 61 | TRACE(); 62 | /* Check if there is at least an upcase char. */ 63 | if (str_find_upcase(str, len)) { 64 | char *new_str; 65 | VALUE str_downcased; 66 | 67 | new_str = ALLOC_N(char, len); 68 | memcpy(new_str, str, len); 69 | 70 | char *s; 71 | int i; 72 | 73 | for (s = new_str, i = 0 ; i < len ; s++, i++) 74 | if (*s >= 'A' && *s <= 'Z') 75 | *s += 32; 76 | 77 | str_downcased = RB_STR_UTF8_NEW(new_str, len); 78 | xfree(new_str); 79 | return(str_downcased); 80 | } 81 | /* If not, then create a Ruby string with original pointer and length. */ 82 | else 83 | return(RB_STR_UTF8_NEW(str, len)); 84 | } 85 | 86 | 87 | #endif 88 | 89 | -------------------------------------------------------------------------------- /ext/sip_parser/common_headers.h: -------------------------------------------------------------------------------- 1 | #ifndef common_headers_h 2 | #define common_headers_h 3 | 4 | #include "../common/c_util.h" 5 | #include "ruby.h" 6 | #include // toupper() 7 | 8 | 9 | /* There are 20 headers with sort representation. */ 10 | #define NUM_SHORT_HEADERS 20 11 | 12 | 13 | struct common_header_name { 14 | const signed long len; 15 | const char *name; 16 | VALUE value; 17 | const char short_name; 18 | }; 19 | 20 | 21 | struct short_header { 22 | char abbr; 23 | VALUE value; 24 | }; 25 | 26 | 27 | /* 28 | * A list of common SIP headers we expect to receive. 29 | * This allows us to avoid repeatedly creating identical string 30 | * objects to be used with rb_hash_aset(). 31 | */ 32 | static struct common_header_name common_headers[] = { 33 | #define f(N, S) { (sizeof(N) - 1), N, Qnil, S } 34 | f("Accept", ' '), 35 | f("Accept-Contact", 'A'), 36 | f("Accept-Encoding", ' '), 37 | f("Accept-Language", ' '), 38 | f("Alert-Info", ' '), 39 | f("Allow", ' '), 40 | f("Allow-Events", 'U'), 41 | f("Authentication-Info", ' '), 42 | f("Authorization", ' '), 43 | f("Call-ID", 'I'), 44 | f("Call-Info", ' '), 45 | f("Contact", 'M'), 46 | f("Content-Disposition", ' '), 47 | f("Content-Encoding", 'E'), 48 | f("Content-Language", ' '), 49 | f("Content-Length", 'L'), 50 | f("Content-Type", 'C'), 51 | f("CSeq", ' '), 52 | f("Date", ' '), 53 | f("Event", 'O'), 54 | f("Error-Info", ' '), 55 | f("Expires", ' '), 56 | f("From", 'F'), 57 | f("Identity", 'Y'), 58 | f("Identity-Info", 'N'), 59 | f("In-Reply-To", ' '), 60 | f("Max-Forwards", ' '), 61 | f("Min-Expires", ' '), 62 | f("MIME-Version", ' '), 63 | f("Organization", ' '), 64 | f("Priority", ' '), 65 | f("Proxy-Authenticate", ' '), 66 | f("Proxy-Authorization", ' '), 67 | f("Proxy-Require", ' '), 68 | f("Record-Route", ' '), 69 | f("Refer-To", 'R'), 70 | f("Referred-By", 'B'), 71 | f("Reject-Contact", 'J'), 72 | f("Reply-To", ' '), 73 | f("Request-Disposition", 'D'), 74 | f("Require", ' '), 75 | f("Retry-After", ' '), 76 | f("Route", ' '), 77 | f("Server", ' '), 78 | f("Session-Expires", 'X'), 79 | f("Subject", 'S'), 80 | f("Supported", 'K'), 81 | f("Timestamp", ' '), 82 | f("To", 'T'), 83 | f("Unsupported", ' '), 84 | f("User-Agent", ' '), 85 | f("Via", 'V'), 86 | f("Warning", ' '), 87 | f("WWW-Authenticate", ' ') 88 | # undef f 89 | }; 90 | 91 | 92 | /* 93 | * The list of short headers. This list is filled by the funcion 94 | * init_short_header_names. 95 | */ 96 | static struct short_header short_headers[NUM_SHORT_HEADERS]; 97 | 98 | 99 | /* this function is not performance-critical, called only at load time */ 100 | static void init_common_headers(void) 101 | { 102 | TRACE(); 103 | int i; 104 | struct common_header_name *cf = common_headers; 105 | 106 | for(i = ARRAY_SIZE(common_headers); --i >= 0; cf++) { 107 | cf->value = rb_str_new(cf->name, cf->len); 108 | cf->value = rb_obj_freeze(cf->value); 109 | /* This tell Ruby not to GC global variables which refer to Ruby's objects, 110 | but are not exported to the Ruby world. */ 111 | rb_global_variable(&cf->value); 112 | } 113 | } 114 | 115 | /* this funcion fills the list of short headers taken the data from 116 | * common_headers array. 117 | */ 118 | static void init_short_headers(void) 119 | { 120 | TRACE(); 121 | int i, j; 122 | struct common_header_name *cf = common_headers; 123 | 124 | for(i = ARRAY_SIZE(common_headers), j=0; --i >= 0; cf++) { 125 | if (cf->short_name != ' ') { 126 | short_headers[j].abbr = cf->short_name; 127 | short_headers[j].value = cf->value; 128 | j++; 129 | } 130 | } 131 | } 132 | 133 | /* this function is called for every header set */ 134 | static VALUE find_common_header_name(const char *name, size_t len) 135 | { 136 | TRACE(); 137 | int i; 138 | struct common_header_name *cf = common_headers; 139 | 140 | for(i = ARRAY_SIZE(common_headers); --i >= 0; cf++) { 141 | if (cf->len == (long)len && !strncasecmp(cf->name, name, len)) 142 | return cf->value; 143 | } 144 | return Qnil; 145 | } 146 | 147 | /* This function is called for every short header found */ 148 | static VALUE find_short_header_name(char abbr) 149 | { 150 | TRACE(); 151 | int i; 152 | struct short_header *sh = short_headers; 153 | 154 | for(i = ARRAY_SIZE(short_headers); --i >= 0; sh++) { 155 | if (sh->abbr == toupper(abbr)) 156 | return sh->value; 157 | } 158 | return Qnil; 159 | } 160 | 161 | 162 | /* Tries to lookup the header name in a list of well-known headers. If so, 163 | * returns the retrieved VALUE. It also works for short headers. 164 | * In case the header is unknown, it normalizes it (by capitalizing the 165 | * first letter and each letter under a "-" or "_" symbol). 166 | */ 167 | static VALUE headerize(const char* hname, size_t hname_len) 168 | { 169 | TRACE(); 170 | VALUE headerized; 171 | char* str; 172 | int i; 173 | 174 | /* Header short name. */ 175 | if (hname_len == 1) { 176 | headerized = find_short_header_name(hname[0]); 177 | if (NIL_P(headerized)) { 178 | headerized = rb_str_new(hname, hname_len); 179 | /* Downcase the header name. */ 180 | downcase_char(RSTRING_PTR(headerized)); 181 | } 182 | } 183 | 184 | /* Header long name. */ 185 | else { 186 | headerized = find_common_header_name(hname, hname_len); 187 | if (NIL_P(headerized)) { 188 | headerized = rb_str_new(hname, hname_len); 189 | str = RSTRING_PTR(headerized); 190 | if (*str >= 'a' && *str <= 'z') 191 | *str &= ~0x20; 192 | 193 | for(i = 1; i < hname_len; i++) { 194 | if (str[i-1] == '-' || str[i-1] == '_') { 195 | if (str[i] >= 'a' && str[i] <= 'z') 196 | str[i] &= ~0x20; 197 | } 198 | else { 199 | if (str[i] >= 'A' && str[i] <= 'Z') 200 | str[i] += 32; 201 | } 202 | } 203 | } 204 | } 205 | 206 | return(headerized); 207 | } 208 | 209 | 210 | #endif 211 | -------------------------------------------------------------------------------- /ext/sip_parser/compile_ragel_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | which ragel >/dev/null 5 | if [ $? -ne 0 ] ; then 6 | echo "ERROR: ragel binary not found, cannot compile the Ragel grammar." >&2 7 | exit 1 8 | else 9 | ragel -v 10 | echo 11 | fi 12 | 13 | 14 | set -e 15 | 16 | RAGEL_FILE=sip_message_parser 17 | echo "DEBUG: compiling Ragel grammar $RAGEL_FILE.rl ..." 18 | ragel -T0 -C $RAGEL_FILE.rl 19 | echo 20 | echo "DEBUG: $RAGEL_FILE.c generated" 21 | echo 22 | 23 | RAGEL_FILE=sip_uri_parser 24 | echo "DEBUG: compiling Ragel grammar $RAGEL_FILE.rl ..." 25 | ragel -T0 -C $RAGEL_FILE.rl 26 | echo 27 | echo "DEBUG: $RAGEL_FILE.c generated" 28 | echo 29 | 30 | -------------------------------------------------------------------------------- /ext/sip_parser/compile_ragel_sip_message_parser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | which ragel >/dev/null 5 | if [ $? -ne 0 ] ; then 6 | echo "ERROR: ragel binary not found, cannot compile the Ragel grammar." >&2 7 | exit 1 8 | else 9 | ragel -v 10 | echo 11 | fi 12 | 13 | 14 | set -e 15 | 16 | RAGEL_FILE=sip_message_parser 17 | echo "DEBUG: compiling Ragel grammar $RAGEL_FILE.rl ..." 18 | ragel -T0 -C $RAGEL_FILE.rl 19 | echo 20 | echo "DEBUG: $RAGEL_FILE.c generated" 21 | echo 22 | -------------------------------------------------------------------------------- /ext/sip_parser/compile_ragel_sip_uri_parser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | which ragel >/dev/null 5 | if [ $? -ne 0 ] ; then 6 | echo "ERROR: ragel binary not found, cannot compile the Ragel grammar." >&2 7 | exit 1 8 | else 9 | ragel -v 10 | echo 11 | fi 12 | 13 | 14 | set -e 15 | 16 | RAGEL_FILE=sip_uri_parser 17 | echo "DEBUG: compiling Ragel grammar $RAGEL_FILE.rl ..." 18 | ragel -T0 -C $RAGEL_FILE.rl 19 | echo 20 | echo "DEBUG: $RAGEL_FILE.c generated" 21 | echo 22 | 23 | -------------------------------------------------------------------------------- /ext/sip_parser/ext_help.h: -------------------------------------------------------------------------------- 1 | #ifndef ext_help_h 2 | #define ext_help_h 3 | 4 | #define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be."); 5 | #define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name); 6 | #define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T); 7 | 8 | 9 | /* Uncomment for enabling TRACE() function. */ 10 | /*#define DEBUG */ 11 | 12 | #ifdef DEBUG 13 | #define TRACE() fprintf(stderr, "TRACE: %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__) 14 | #else 15 | #define TRACE() 16 | #endif 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /ext/sip_parser/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | 3 | create_makefile("oversip/sip/sip_parser") 4 | -------------------------------------------------------------------------------- /ext/sip_parser/grammar_absolute_uri.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_absolute_uri; 3 | 4 | abs_uri_reg_name = ( unreserved | escaped | "$" | "," | ";" | ":" | "@" | "&" | "=" | "+" )+; 5 | abs_uri_srvr = ( ( userinfo "@" )? hostport )?; 6 | abs_uri_authority = abs_uri_srvr | abs_uri_reg_name; 7 | abs_uri_scheme = ALPHA ( ALPHA | DIGIT | "+" | "-" | "." )* - ( "sip"i | "sips"i | "tel"i ); 8 | abs_uri_pchar = unreserved | escaped | ":" | "@" | "&" | "=" | "+" | "$" | ","; 9 | abs_uri_param = ( abs_uri_pchar )*; 10 | abs_uri_segment = ( abs_uri_pchar )* ( ";" abs_uri_param )*; 11 | abs_uri_path_segments = abs_uri_segment ( "/" abs_uri_segment )*; 12 | abs_uri_uric = reserved | unreserved | escaped; 13 | abs_uri_query = ( abs_uri_uric )*; 14 | abs_uri_uric_no_slash = abs_uri_uric - "/"; 15 | abs_uri_opaque_part = abs_uri_uric_no_slash ( abs_uri_uric )*; 16 | abs_uri_abs_path = "/" abs_uri_path_segments; 17 | abs_uri_net_path = "//" abs_uri_authority ( abs_uri_abs_path )?; 18 | # NOTE: Original BNF in RFC 3261 for absoluteURI doesn't allow "mailto:qwe@[::1]" (due to a bug in RFC 3986 URI). Fix it: 19 | # http://crazygreek.co.uk/blogger/2009/03/sip-uri-syntax-is-broken-with-ipv6.html 20 | # http://www.ietf.org/mail-archive/web/sip/current/msg26338.html 21 | #abs_uri_hier_part = ( abs_uri_net_path | abs_uri_abs_path ) ( "?" abs_uri_query )?; 22 | abs_uri_hier_part = ( abs_uri_net_path | abs_uri_abs_path | abs_uri_authority ) ( "?" abs_uri_query )?; 23 | 24 | absoluteURI = ( 25 | abs_uri_scheme %uri_is_unknown >start_uri >mark %uri_scheme 26 | ":" ( abs_uri_hier_part | abs_uri_opaque_part ) 27 | ) %write_uri; 28 | }%% -------------------------------------------------------------------------------- /ext/sip_parser/grammar_name_addr.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_name_addr; 3 | 4 | uri_display_name = ( quoted_string >uri_display_name_quoted | ( token ( LWS token )* ) ); 5 | addr_spec = SIP_URI | TEL_URI | absoluteURI; 6 | name_addr = ( uri_display_name >mark %uri_display_name )? LAQUOT addr_spec RAQUOT; 7 | 8 | # It solves a problem when multiples name_addr are allowed separated by COMMA (i.e. Contact header). 9 | name_addr_anti_COMMA = ( uri_display_name >mark %uri_display_name )? LAQUOT addr_spec ">"; 10 | 11 | # In Route header just SIP/SIPS schemes are allowed. It also allows comma separated values, so apply 12 | # same as in name_addr_anti_COMMA. 13 | name_addr_sip = ( uri_display_name >mark %uri_display_name )? LAQUOT SIP_URI ">"; 14 | }%% -------------------------------------------------------------------------------- /ext/sip_parser/grammar_sip_core.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_sip_core; 3 | 4 | CRLF = "\r\n"; 5 | DIGIT = "0".."9"; 6 | ALPHA = "a".."z" | "A".."Z"; 7 | HEXDIG = DIGIT | "A"i | "B"i | "C"i | "D"i | "E"i | "F"i; 8 | DQUOTE = "\""; 9 | SP = " "; 10 | HTAB = "\t"; 11 | WSP = SP | HTAB; 12 | LWS = ( WSP* CRLF )? WSP+; 13 | SWS = LWS?; 14 | OCTET = 0x00..0xff; 15 | VCHAR = 0x21..0x7e; 16 | HCOLON = ( SP | HTAB )* ":" SWS; 17 | SEMI = SWS ";" SWS; 18 | EQUAL = SWS "=" SWS; 19 | SLASH = SWS "/" SWS; 20 | COLON = SWS ":" SWS; 21 | COMMA = SWS "," SWS; 22 | RAQUOT = ">" SWS; 23 | LAQUOT = SWS "<"; 24 | UTF8_CONT = 0x80..0xbf; 25 | UTF8_NONASCII = ( 0xc0..0xdf UTF8_CONT ) | ( 0xe0..0xef UTF8_CONT{2} ) | ( 0xf0..0xf7 UTF8_CONT{3} ) | 26 | ( 0xf8..0xfb UTF8_CONT{4} ) | ( 0xfc..0xfd UTF8_CONT{5} ); 27 | # NOTE: Workaround to relax grammar: 28 | # https://lists.cs.columbia.edu/pipermail/sip-implementors/2010-December/026127.html 29 | # NOTE: This allows non UTF-8 symbols in headers! 30 | #UTF8_NONASCII = 0x80..0xff; 31 | # NOTE: Added by me (doesn't include space neither tabulator). 32 | PRINTABLE_ASCII = 0x21..0x7e; 33 | TEXT_UTF8char = PRINTABLE_ASCII | UTF8_NONASCII; 34 | 35 | alphanum = ALPHA | DIGIT; 36 | reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","; 37 | mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"; 38 | unreserved = alphanum | mark; 39 | escaped = "%" HEXDIG HEXDIG; 40 | 41 | token = ( alphanum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`" | "'" | "~" )+; 42 | word = ( alphanum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`" | "'" | "~" | "(" | ")" | 43 | "<" | ">" | ":" | "\\" | DQUOTE | "/" | "[" | "]" | "?" | "{" | "}" )+; 44 | ctext = 0x21..0x27 | 0x2a..0x5b | 0x5d..0x7e | UTF8_NONASCII | LWS; 45 | quoted_pair = "\\" ( 0x00..0x09 | 0x0b..0x0c | 0x0e..0x7f ); 46 | comment = SWS "(" ( ctext | quoted_pair | "(" | ")" )* ")" SWS ; 47 | qdtext = LWS | "!" | 0x23..0x5b | 0x5d..0x7e | UTF8_NONASCII; 48 | quoted_string = DQUOTE ( qdtext | quoted_pair )* DQUOTE; 49 | 50 | domainlabel = alphanum | ( alphanum ( alphanum | "-" | "_" )* alphanum ); 51 | toplabel = ALPHA | ( ALPHA ( alphanum | "-" | "_" )* alphanum ); 52 | hostname = ( domainlabel "." )* toplabel "."?; 53 | dec_octet = DIGIT | ( 0x31..0x39 DIGIT ) | ( "1" DIGIT{2} ) | 54 | ( "2" 0x30..0x34 DIGIT ) | ( "25" 0x30..0x35 ); 55 | IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet; 56 | h16 = HEXDIG{1,4}; 57 | ls32 = ( h16 ":" h16 ) | IPv4address; 58 | IPv6address = ( ( h16 ":" ){6} ls32 ) | 59 | ( "::" ( h16 ":" ){5} ls32 ) | 60 | ( h16? "::" ( h16 ":" ){4} ls32 ) | 61 | ( ( ( h16 ":" )? h16 )? "::" ( h16 ":" ){3} ls32 ) | 62 | ( ( ( h16 ":" ){,2} h16 )? "::" ( h16 ":" ){2} ls32 ) | 63 | ( ( ( h16 ":" ){,3} h16 )? "::" h16 ":" ls32 ) | 64 | ( ( ( h16 ":" ){,4} h16 )? "::" ls32 ) | 65 | ( ( ( h16 ":" ){,5} h16 )? "::" h16 ) | 66 | ( ( ( h16 ":" ){,6} h16 )? "::" ); 67 | IPv6reference = "[" IPv6address "]"; 68 | host = hostname | 69 | IPv4address | 70 | IPv6reference; 71 | #port = DIGIT{1,5}; 72 | # Valid values: 0 - 65535. 73 | port = ( DIGIT{1,4} | 74 | "1".."5" DIGIT{4} | 75 | "6" "0".."4" DIGIT{3} | 76 | "6" "5" "0".."4" DIGIT{2} | 77 | "6" "5" "5" "0".."2" DIGIT | 78 | "6" "5" "5" "3" "0".."5" 79 | ) - ( "0" | "00" | "000" | "0000" ); 80 | hostport = host ( ":" port )?; 81 | 82 | user_unreserved = "&" | "=" | "+" | "$" | "," | ";" | "?" | "/"; 83 | # NOTE: '#' allowed even if it's incorrect. 84 | user = ( user_unreserved | unreserved | escaped | "#" )+; 85 | password = ( unreserved | escaped | "&" | "=" | "+" | "$" | "," )*; 86 | userinfo = user ( ":" password )?; 87 | }%% -------------------------------------------------------------------------------- /ext/sip_parser/grammar_sip_message.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_sip_message; 3 | 4 | include grammar_sip_core "grammar_sip_core.rl"; 5 | 6 | Method = ( "INVITE" %msg_method_INVITE | 7 | "ACK" %msg_method_ACK | 8 | "CANCEL" %msg_method_CANCEL | 9 | "PRACK" %msg_method_PRACK | 10 | "BYE" %msg_method_BYE | 11 | "REFER" %msg_method_REFER | 12 | "INFO" %msg_method_INFO | 13 | "UPDATE" %msg_method_UPDATE | 14 | "OPTIONS" %msg_method_OPTIONS | 15 | "REGISTER" %msg_method_REGISTER | 16 | "MESSAGE" %msg_method_MESSAGE | 17 | "SUBSCRIBE" %msg_method_SUBSCRIBE | 18 | "NOTIFY" %msg_method_NOTIFY | 19 | "PUBLISH" %msg_method_PUBLISH | 20 | "PULL" %msg_method_PULL | 21 | "PUSH" %msg_method_PUSH | 22 | "STORE" %msg_method_STORE | 23 | token ) >mark %msg_method_unknown; 24 | 25 | include grammar_sip_uri "grammar_sip_uri.rl"; 26 | include grammar_tel_uri "grammar_tel_uri.rl"; 27 | include grammar_absolute_uri "grammar_absolute_uri.rl"; 28 | include grammar_name_addr "grammar_name_addr.rl"; 29 | include grammar_sip_headers "grammar_sip_headers.rl"; 30 | 31 | ### TODO: Quitar el HTTP 32 | SIP_Version = ( "SIP"i | "HTTP"i ) "/" DIGIT{1,2} "." DIGIT{1,2}; 33 | 34 | # In request. 35 | Request_Line = Method %msg_request %msg_method SP >init_ruri 36 | ( SIP_URI | TEL_URI | absoluteURI ) >do_request_uri SP 37 | SIP_Version >mark %msg_sip_version; 38 | 39 | # In response. 40 | Status_Code = ( "1".."6" DIGIT{2} ) >mark %msg_status_code; 41 | Reason_Phrase = ( ( any )* -- CRLF ) >mark %msg_reason_phrase; 42 | Status_Line = SIP_Version %msg_response >mark %msg_sip_version SP 43 | Status_Code SP Reason_Phrase; 44 | 45 | SIP_Message = ( Request_Line :> CRLF | Status_Line :> CRLF ) 46 | ( Header CRLF )* 47 | CRLF >write_hdr_value @done; 48 | 49 | Outbound_keepalive = ( CRLF CRLF ) @outbound_keepalive @done; 50 | 51 | main := ( CRLF? SIP_Message ) | Outbound_keepalive; 52 | }%% 53 | -------------------------------------------------------------------------------- /ext/sip_parser/grammar_sip_uri.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_sip_uri; 3 | 4 | sip_uri_param_unreserved = "[" | "]" | "/" | ":" | "&" | "+" | "$"; 5 | sip_uri_paramchar = sip_uri_param_unreserved | unreserved | escaped; 6 | #sip_uri_pname = sip_uri_paramchar+; 7 | # Let's allow ugly devices that add parameters with "=" but no value! 8 | sip_uri_pname = sip_uri_paramchar+ "="?; 9 | sip_uri_pvalue = sip_uri_paramchar+; 10 | sip_uri_lr = "lr"i %sip_uri_has_lr ( "=" token )?; 11 | sip_uri_ob = "ob"i %sip_uri_has_ob ( "=" token )?; 12 | # Custom URI param for Route header inspection. 13 | # as it's not discarded in 'sip_uri_param'. 14 | sip_uri_ovid = "ovid"i ( "=" token >mark %sip_uri_ovid )?; 15 | sip_uri_transport = "transport="i 16 | ( "udp"i %sip_uri_transport_udp | 17 | "tcp"i %sip_uri_transport_tcp | 18 | "tls"i %sip_uri_transport_tls | 19 | "sctp"i %sip_uri_transport_sctp | 20 | "ws"i %sip_uri_transport_ws | 21 | "wss"i %sip_uri_transport_wss | 22 | ( token - ( "udp"i | "tcp"i | "tls"i | "sctp"i | "ws"i | "wss"i ) ) >mark %sip_uri_transport_unknown ); 23 | 24 | sip_uri_param = ( sip_uri_pname >start_uri_param_key %uri_param_key_len 25 | ( "=" sip_uri_pvalue >start_uri_param_value %uri_param_value_len )? %write_uri_param ) | 26 | sip_uri_transport | sip_uri_lr | sip_uri_ob | sip_uri_ovid; 27 | 28 | sip_uri_params = ( ";" sip_uri_param )*; 29 | 30 | sip_uri_hnv_unreserved = "[" | "]" | "/" | "?" | ":" | "+" | "$"; 31 | sip_uri_hname = ( sip_uri_hnv_unreserved | unreserved | escaped )+; 32 | sip_uri_hvalue = ( sip_uri_hnv_unreserved | unreserved | escaped )*; 33 | sip_uri_header = sip_uri_hname "=" sip_uri_hvalue; 34 | sip_uri_headers = "?" sip_uri_header ( "&" sip_uri_header )*; 35 | 36 | SIP_URI = ( 37 | ( "sip"i %uri_is_sip | "sips"i %uri_is_sips ) >start_uri >mark %uri_scheme ":" 38 | ( userinfo >mark %uri_user "@" )? 39 | ( hostname %uri_host_domain | 40 | IPv4address %uri_host_ipv4 | 41 | IPv6reference %uri_host_ipv6 ) >mark 42 | ( ":" port >mark %uri_port )? 43 | sip_uri_params 44 | ( sip_uri_headers >mark %uri_headers )? 45 | ) %write_uri; 46 | 47 | }%% -------------------------------------------------------------------------------- /ext/sip_parser/grammar_tel_uri.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_tel_uri; 3 | 4 | tel_visual_separator = "-" | "." | "(" | ")"; 5 | tel_phonedigit = DIGIT | tel_visual_separator; 6 | tel_global_number_digits = "+" tel_phonedigit* DIGIT tel_phonedigit*; 7 | tel_phonedigit_hex = HEXDIG | "*" | "#" | tel_visual_separator; 8 | tel_local_number_digits = tel_phonedigit_hex* ( HEXDIG | "*" | "#" ) tel_phonedigit_hex*; 9 | 10 | tel_descriptor = hostname | tel_global_number_digits; 11 | tel_context = "phone-context="i tel_descriptor >mark %uri_tel_phone_context; 12 | 13 | tel_param_unreserved = "[" | "]" | "/" | ":" | "&" | "+" | "$"; 14 | tel_pct_encoded = "%" HEXDIG HEXDIG; 15 | tel_paramchar = tel_param_unreserved | unreserved | tel_pct_encoded; 16 | tel_uri_pname = ( alphanum | "-" )+; 17 | tel_uri_pvalue = tel_paramchar+; 18 | 19 | tel_uri_param = ( tel_uri_pname >start_uri_param_key %uri_param_key_len 20 | ( "=" tel_uri_pvalue >start_uri_param_value %uri_param_value_len )? %write_uri_param ) | 21 | tel_context; 22 | 23 | tel_uri_params = ( ";" tel_uri_param )*; 24 | 25 | tel_global_number = tel_global_number_digits >mark %uri_user tel_uri_params; 26 | tel_local_number = tel_local_number_digits >mark %uri_user tel_uri_params; 27 | 28 | TEL_URI = ( 29 | "tel:"i %uri_is_tel >start_uri >mark %uri_scheme 30 | ( tel_global_number | tel_local_number ) 31 | ) %write_uri; 32 | }%% -------------------------------------------------------------------------------- /ext/stud/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | require "fileutils" 3 | require "rbconfig" 4 | 5 | 6 | def log(message) 7 | puts "[ext/stud/extconf.rb] #{message}" 8 | end 9 | 10 | 11 | def sys(cmd) 12 | log "executing system command: #{cmd}" 13 | unless ret = xsystem(cmd) 14 | raise "[ext/stud/extconf.rb] system command `#{cmd}' failed" 15 | end 16 | ret 17 | end 18 | 19 | 20 | here = File.expand_path(File.dirname(__FILE__)) 21 | stud_dir = "#{here}/../../thirdparty/stud/" 22 | stud_tarball = "stud.tar.gz" 23 | 24 | Dir.chdir(stud_dir) do 25 | sys("tar -zxf #{stud_tarball}") 26 | 27 | Dir.chdir("stud") do 28 | host_os = RbConfig::CONFIG["host_os"] 29 | log "RbConfig::CONFIG['host_os'] returns #{host_os.inspect}" 30 | case host_os 31 | when /bsd/i 32 | log "BSD detected, using `gmake' instead of `make'" 33 | sys("gmake") 34 | else 35 | sys("make") 36 | end 37 | FileUtils.mv "stud", "../../../bin/oversip_stud" 38 | end 39 | 40 | FileUtils.remove_dir("stud", force = true) 41 | end 42 | 43 | create_makefile("stud") 44 | -------------------------------------------------------------------------------- /ext/stun/ext_help.h: -------------------------------------------------------------------------------- 1 | #ifndef ext_help_h 2 | #define ext_help_h 3 | 4 | /* Uncomment for enabling TRACE() function. */ 5 | /*#define DEBUG*/ 6 | 7 | #ifdef DEBUG 8 | #define TRACE() fprintf(stderr, "TRACE: %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__) 9 | #define LOG(string) fprintf(stderr, "LOG: %s:%d:%s: %s\n", __FILE__, __LINE__, __FUNCTION__, string) 10 | #else 11 | #define TRACE() 12 | #define LOG(string) 13 | #endif 14 | 15 | #endif 16 | 17 | -------------------------------------------------------------------------------- /ext/stun/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | 3 | create_makefile("oversip/stun") 4 | -------------------------------------------------------------------------------- /ext/utils/compile_ragel_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | which ragel >/dev/null 5 | if [ $? -ne 0 ] ; then 6 | echo "ERROR: ragel binary not found, cannot compile the Ragel grammar." >&2 7 | exit 1 8 | else 9 | ragel -v 10 | echo 11 | fi 12 | 13 | 14 | set -e 15 | 16 | RAGEL_FILE=ip_utils 17 | echo "DEBUG: compiling Ragel grammar $RAGEL_FILE.rl ..." 18 | ragel -G2 -C $RAGEL_FILE.rl 19 | echo 20 | echo "DEBUG: $RAGEL_FILE.c generated" 21 | echo 22 | 23 | RAGEL_FILE=outbound_utils 24 | echo "DEBUG: compiling Ragel grammar $RAGEL_FILE.rl ..." 25 | ragel -G2 -C $RAGEL_FILE.rl 26 | echo 27 | echo "DEBUG: $RAGEL_FILE.c generated" 28 | echo 29 | 30 | RAGEL_FILE=haproxy_protocol 31 | echo "DEBUG: compiling Ragel grammar $RAGEL_FILE.rl ..." 32 | ragel -G2 -C $RAGEL_FILE.rl 33 | echo 34 | echo "DEBUG: $RAGEL_FILE.c generated" 35 | echo 36 | 37 | 38 | -------------------------------------------------------------------------------- /ext/utils/ext_help.h: -------------------------------------------------------------------------------- 1 | #ifndef ext_help_h 2 | #define ext_help_h 3 | 4 | /* Uncomment for enabling TRACE() function. */ 5 | /*#define DEBUG*/ 6 | 7 | #ifdef DEBUG 8 | #define TRACE() fprintf(stderr, "TRACE: %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__) 9 | #else 10 | #define TRACE() 11 | #endif 12 | 13 | #endif 14 | 15 | -------------------------------------------------------------------------------- /ext/utils/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | 3 | create_makefile("oversip/utils") 4 | -------------------------------------------------------------------------------- /ext/utils/grammar_ip.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_ip; 3 | 4 | DIGIT = "0".."9"; 5 | HEXDIG = DIGIT | "A"i | "B"i | "C"i | "D"i | "E"i | "F"i; 6 | dec_octet = DIGIT | ( 0x31..0x39 DIGIT ) | ( "1" DIGIT{2} ) | 7 | ( "2" 0x30..0x34 DIGIT ) | ( "25" 0x30..0x35 ); 8 | IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet; 9 | h16 = HEXDIG{1,4}; 10 | ls32 = ( h16 ":" h16 ) | IPv4address; 11 | IPv6address = ( ( h16 ":" ){6} ls32 ) | 12 | ( "::" ( h16 ":" ){5} ls32 ) | 13 | ( h16? "::" ( h16 ":" ){4} ls32 ) | 14 | ( ( ( h16 ":" )? h16 )? "::" ( h16 ":" ){3} ls32 ) | 15 | ( ( ( h16 ":" ){,2} h16 )? "::" ( h16 ":" ){2} ls32 ) | 16 | ( ( ( h16 ":" ){,3} h16 )? "::" h16 ":" ls32 ) | 17 | ( ( ( h16 ":" ){,4} h16 )? "::" ls32 ) | 18 | ( ( ( h16 ":" ){,5} h16 )? "::" h16 ) | 19 | ( ( ( h16 ":" ){,6} h16 )? "::" ); 20 | 21 | port = ( DIGIT{1,4} | 22 | "1".."5" DIGIT{4} | 23 | "6" "0".."4" DIGIT{3} | 24 | "6" "5" "0".."4" DIGIT{2} | 25 | "6" "5" "5" "0".."2" DIGIT | 26 | "6" "5" "5" "3" "0".."5" 27 | ) - ( "0" | "00" | "000" | "0000" ); 28 | }%% -------------------------------------------------------------------------------- /ext/utils/haproxy_protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef haproxy_protocol_h 2 | #define haproxy_protocol_h 3 | 4 | 5 | #include 6 | 7 | 8 | enum enum_haproxy_protocol_ip_type { 9 | haproxy_protocol_ip_type_ipv4 = 1, 10 | haproxy_protocol_ip_type_ipv6 11 | }; 12 | 13 | 14 | typedef struct struct_haproxy_protocol { 15 | unsigned short int valid; 16 | unsigned short int total_len; 17 | enum enum_haproxy_protocol_ip_type ip_type; 18 | size_t ip_s; 19 | size_t ip_len; 20 | size_t port_s; 21 | size_t port_len; 22 | } struct_haproxy_protocol; 23 | 24 | 25 | struct_haproxy_protocol struct_haproxy_protocol_parser_execute(const char *str, size_t len); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /ext/utils/haproxy_protocol.rl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "haproxy_protocol.h" 4 | 5 | 6 | /** machine **/ 7 | %%{ 8 | machine utils_haproxy_protocol_parser; 9 | 10 | 11 | action is_ipv4 { 12 | haproxy_protocol.ip_type = haproxy_protocol_ip_type_ipv4; 13 | } 14 | 15 | action is_ipv6 { 16 | haproxy_protocol.ip_type = haproxy_protocol_ip_type_ipv6; 17 | } 18 | 19 | action start_ip { 20 | haproxy_protocol.ip_s = (size_t)fpc; 21 | } 22 | 23 | action end_ip { 24 | haproxy_protocol.ip_len = (size_t)fpc - haproxy_protocol.ip_s; 25 | } 26 | 27 | action start_port { 28 | haproxy_protocol.port_s = (size_t)fpc; 29 | } 30 | 31 | action end_port { 32 | haproxy_protocol.port_len = (size_t)fpc - haproxy_protocol.port_s + 1; 33 | } 34 | 35 | action done { 36 | finished = 1; 37 | } 38 | 39 | include grammar_ip "grammar_ip.rl"; 40 | 41 | main := "PROXY TCP" ( "4" | "6" ) " " 42 | ( IPv4address %is_ipv4 | IPv6address %is_ipv6 ) >start_ip %end_ip " " 43 | ( IPv4address | IPv6address ) " " 44 | port >start_port @end_port " " 45 | port "\r\n" 46 | @done; 47 | }%% 48 | 49 | /** Data **/ 50 | %% write data; 51 | 52 | 53 | /** exec **/ 54 | /* 55 | * Expects a string like "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n". 56 | */ 57 | struct_haproxy_protocol struct_haproxy_protocol_parser_execute(const char *str, size_t len) 58 | { 59 | int cs = 0; 60 | const char *p, *pe; 61 | size_t mark; 62 | int finished = 0; 63 | struct_haproxy_protocol haproxy_protocol; 64 | 65 | p = str; 66 | pe = str+len; 67 | 68 | haproxy_protocol.valid = 0; 69 | haproxy_protocol.total_len = 0; 70 | haproxy_protocol.ip_s = 0; 71 | haproxy_protocol.ip_len = 0; 72 | haproxy_protocol.port_s = 0; 73 | haproxy_protocol.port_len = 0; 74 | 75 | %% write init; 76 | %% write exec; 77 | 78 | if(finished) 79 | haproxy_protocol.valid = 1; 80 | 81 | /* Write the number of read bytes so the HAProxy Protocol line can be removed. */ 82 | haproxy_protocol.total_len = (int)(p - str); 83 | 84 | return haproxy_protocol; 85 | } 86 | 87 | -------------------------------------------------------------------------------- /ext/utils/ip_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef ip_utils_h 2 | #define ip_utils_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include // inet_pton() 9 | 10 | 11 | enum enum_ip_type { 12 | ip_type_ipv4 = 1, 13 | ip_type_ipv6, 14 | ip_type_ipv6_reference, 15 | ip_type_error 16 | }; 17 | 18 | 19 | enum enum_ip_type utils_ip_parser_execute(const char *str, size_t len); 20 | 21 | 22 | /*! \brief Return 1 if both pure IP's are equal, 0 otherwise. */ 23 | static int utils_compare_pure_ips(char *ip1, size_t len1, enum enum_ip_type ip1_type, char *ip2, size_t len2, enum enum_ip_type ip2_type) 24 | { 25 | struct in_addr in_addr1, in_addr2; 26 | struct in6_addr in6_addr1, in6_addr2; 27 | char _ip1[INET6_ADDRSTRLEN+1], _ip2[INET6_ADDRSTRLEN+1]; 28 | 29 | /* Not same IP type, return false. */ 30 | if (ip1_type != ip2_type) 31 | return 0; 32 | 33 | memcpy(_ip1, ip1, len1); 34 | _ip1[len1] = '\0'; 35 | memcpy(_ip2, ip2, len2); 36 | _ip2[len2] = '\0'; 37 | 38 | switch(ip1_type) { 39 | /* Comparing IPv4 with IPv4. */ 40 | case(ip_type_ipv4): 41 | if (inet_pton(AF_INET, _ip1, &in_addr1) == 0) return 0; 42 | if (inet_pton(AF_INET, _ip2, &in_addr2) == 0) return 0; 43 | if (in_addr1.s_addr == in_addr2.s_addr) 44 | return 1; 45 | else 46 | return 0; 47 | break; 48 | /* Comparing IPv6 with IPv6. */ 49 | case(ip_type_ipv6): 50 | if (inet_pton(AF_INET6, _ip1, &in6_addr1) != 1) return 0; 51 | if (inet_pton(AF_INET6, _ip2, &in6_addr2) != 1) return 0; 52 | if (memcmp(in6_addr1.s6_addr, in6_addr2.s6_addr, sizeof(in6_addr1.s6_addr)) == 0) 53 | return 1; 54 | else 55 | return 0; 56 | break; 57 | default: 58 | return 0; 59 | break; 60 | } 61 | } 62 | 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /ext/utils/ip_utils.rl: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ip_utils.h" 3 | 4 | 5 | /** machine **/ 6 | %%{ 7 | machine utils_ip_parser; 8 | 9 | 10 | action is_ipv4 { 11 | ip_type = ip_type_ipv4; 12 | } 13 | 14 | action is_ipv6 { 15 | ip_type = ip_type_ipv6; 16 | } 17 | 18 | action is_ipv6_reference { 19 | ip_type = ip_type_ipv6_reference; 20 | } 21 | 22 | include grammar_ip "grammar_ip.rl"; 23 | 24 | IPv6reference = "[" IPv6address "]"; 25 | 26 | main := IPv4address @is_ipv4 | 27 | IPv6address @is_ipv6 | 28 | IPv6reference @is_ipv6_reference; 29 | }%% 30 | 31 | /** Data **/ 32 | %% write data; 33 | 34 | 35 | /** exec **/ 36 | enum enum_ip_type utils_ip_parser_execute(const char *str, size_t len) 37 | { 38 | int cs = 0; 39 | const char *p, *pe; 40 | enum enum_ip_type ip_type = ip_type_error; 41 | 42 | p = str; 43 | pe = str+len; 44 | 45 | %% write init; 46 | %% write exec; 47 | 48 | if(len != p-str) 49 | return ip_type_error; 50 | else 51 | return ip_type; 52 | } 53 | 54 | -------------------------------------------------------------------------------- /ext/utils/outbound_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef outbound_utils_h 2 | #define outbound_utils_h 3 | 4 | 5 | #include 6 | 7 | 8 | enum enum_outbound_udp_flow_token_ip_type { 9 | outbound_udp_flow_token_ip_type_ipv4 = 1, 10 | outbound_udp_flow_token_ip_type_ipv6 11 | }; 12 | 13 | 14 | typedef struct struct_outbound_udp_flow_token { 15 | unsigned short int valid; 16 | enum enum_outbound_udp_flow_token_ip_type ip_type; 17 | size_t ip_s; 18 | size_t ip_len; 19 | size_t port_s; 20 | size_t port_len; 21 | } struct_outbound_udp_flow_token; 22 | 23 | 24 | struct_outbound_udp_flow_token outbound_udp_flow_token_parser_execute(const char *str, size_t len); 25 | 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /ext/utils/outbound_utils.rl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "outbound_utils.h" 4 | 5 | 6 | /** machine **/ 7 | %%{ 8 | machine utils_outbound_udp_flow_token_parser; 9 | 10 | 11 | action is_ipv4 { 12 | outbound_udp_flow_token.ip_type = outbound_udp_flow_token_ip_type_ipv4; 13 | } 14 | 15 | action is_ipv6 { 16 | outbound_udp_flow_token.ip_type = outbound_udp_flow_token_ip_type_ipv6; 17 | } 18 | 19 | action start_ip { 20 | outbound_udp_flow_token.ip_s = (size_t)fpc; 21 | } 22 | 23 | action end_ip { 24 | outbound_udp_flow_token.ip_len = (size_t)fpc - outbound_udp_flow_token.ip_s; 25 | } 26 | 27 | action start_port { 28 | outbound_udp_flow_token.port_s = (size_t)fpc; 29 | } 30 | 31 | action end_port { 32 | outbound_udp_flow_token.port_len = (size_t)fpc - outbound_udp_flow_token.port_s + 1; 33 | finished = 1; 34 | } 35 | 36 | include grammar_ip "grammar_ip.rl"; 37 | 38 | main := ( IPv4address %is_ipv4 | IPv6address %is_ipv6 ) >start_ip %end_ip "_" port >start_port @end_port; 39 | }%% 40 | 41 | /** Data **/ 42 | %% write data; 43 | 44 | 45 | /** exec **/ 46 | /* 47 | * Expects a string like "1.2.3.4_5060" or "1af:43::ab_9090" (no "_" at the beginning). 48 | */ 49 | struct_outbound_udp_flow_token outbound_udp_flow_token_parser_execute(const char *str, size_t len) 50 | { 51 | int cs = 0; 52 | const char *p, *pe; 53 | size_t mark; 54 | int finished = 0; 55 | struct_outbound_udp_flow_token outbound_udp_flow_token; 56 | 57 | p = str; 58 | pe = str+len; 59 | 60 | outbound_udp_flow_token.valid = 0; 61 | outbound_udp_flow_token.ip_s = 0; 62 | outbound_udp_flow_token.ip_len = 0; 63 | outbound_udp_flow_token.port_s = 0; 64 | outbound_udp_flow_token.port_len = 0; 65 | 66 | %% write init; 67 | %% write exec; 68 | 69 | if(finished && len == p-str) 70 | outbound_udp_flow_token.valid = 1; 71 | 72 | return outbound_udp_flow_token; 73 | } 74 | 75 | -------------------------------------------------------------------------------- /ext/utils/utils_ruby.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is not used by Ruby OverSIP::Utils module itself, its aim is to 3 | * be included by other OverSIP Ruby C extensions. 4 | */ 5 | 6 | #ifndef utils_ruby_h 7 | #define utils_ruby_h 8 | 9 | 10 | #include "ip_utils.h" 11 | #include "outbound_utils.h" 12 | #include "haproxy_protocol.h" 13 | #include // inet_ntop() 14 | 15 | 16 | /* Export the Ruby C functions so other C libraries within OverSIP can use them. */ 17 | VALUE Utils_is_ip(VALUE self, VALUE string); 18 | VALUE Utils_is_pure_ip(VALUE self, VALUE string); 19 | VALUE Utils_ip_type(VALUE self, VALUE string); 20 | VALUE Utils_compare_ips(VALUE self, VALUE string1, VALUE string2); 21 | VALUE Utils_compare_pure_ips(VALUE self, VALUE string1, VALUE string2); 22 | VALUE Utils_normalize_ipv6(int argc, VALUE *argv, VALUE self); 23 | VALUE Utils_normalize_host(int argc, VALUE *argv, VALUE self); 24 | VALUE Utils_to_pure_ip(VALUE self, VALUE string); 25 | VALUE Utils_parser_outbound_udp_flow_token(VALUE self, VALUE string); 26 | VALUE Utils_parser_haproxy_protocol(VALUE self, VALUE string); 27 | 28 | 29 | VALUE utils_normalize_ipv6(VALUE string, int force_pure_ipv6) 30 | { 31 | struct in6_addr addr; 32 | char normalized_ipv6[INET6_ADDRSTRLEN + 1]; 33 | char normalized_ipv6_reference[INET6_ADDRSTRLEN + 3]; 34 | char *str, str2[INET6_ADDRSTRLEN + 3], *str_pointer; 35 | int is_ipv6_reference = 0; 36 | 37 | str = StringValueCStr(string); 38 | if (str[0] != '[') { 39 | str_pointer = str; 40 | } 41 | else { 42 | is_ipv6_reference = 1; 43 | memcpy(str2, str + 1, strlen(str) - 2); 44 | str2[strlen(str) - 2] = '\0'; 45 | str_pointer = str2; 46 | } 47 | 48 | switch(inet_pton(AF_INET6, str_pointer, &addr)) { 49 | /* Not a valid IPv6. */ 50 | case 0: 51 | return Qfalse; 52 | break; 53 | /* Some error ocurred. */ 54 | case -1: 55 | return Qnil; 56 | break; 57 | default: 58 | break; 59 | } 60 | 61 | if (inet_ntop(AF_INET6, &addr, normalized_ipv6, INET6_ADDRSTRLEN)) 62 | if (is_ipv6_reference && !force_pure_ipv6) { 63 | memcpy(normalized_ipv6_reference, "[", 1); 64 | memcpy(normalized_ipv6_reference + 1, normalized_ipv6, strlen(normalized_ipv6)); 65 | memcpy(normalized_ipv6_reference + strlen(normalized_ipv6) + 1, "]\0", 2); 66 | return rb_str_new_cstr(normalized_ipv6_reference); 67 | } 68 | else 69 | return rb_str_new_cstr(normalized_ipv6); 70 | /* Some error ocurred. */ 71 | else 72 | return Qnil; 73 | } 74 | 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /ext/websocket_framing_utils/ext_help.h: -------------------------------------------------------------------------------- 1 | #ifndef ext_help_h 2 | #define ext_help_h 3 | 4 | #define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be."); 5 | #define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name); 6 | #define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T); 7 | 8 | 9 | /* Uncomment for enabling TRACE() function. */ 10 | /*#define DEBUG*/ 11 | 12 | #ifdef DEBUG 13 | #define TRACE() fprintf(stderr, "TRACE: %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__) 14 | #else 15 | #define TRACE() 16 | #endif 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /ext/websocket_framing_utils/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | 3 | create_makefile("oversip/websocket/ws_framing_utils") 4 | -------------------------------------------------------------------------------- /ext/websocket_framing_utils/ws_framing_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef ws_framing_utils_h 2 | #define ws_framing_utils_h 3 | 4 | 5 | #include 6 | 7 | 8 | /* Extracted from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ 9 | 10 | #define UTF8_ACCEPT 0 11 | #define UTF8_REJECT 1 12 | #define UTF8D_SIZE 400 13 | 14 | static const uint8_t utf8d[] = { 15 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 00..1f */ 16 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 20..3f */ 17 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 40..5f */ 18 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 60..7f */ 19 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, /* 80..9f */ 20 | 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* a0..bf */ 21 | 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* c0..df */ 22 | 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, /* e0..ef */ 23 | 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, /* f0..ff */ 24 | 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, /* s0..s0 */ 25 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, /* s1..s2 */ 26 | 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, /* s3..s4 */ 27 | 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, /* s5..s6 */ 28 | 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1 /* s7..s8 */ 29 | }; 30 | 31 | static inline 32 | uint32_t utf8_decode(uint32_t* state, uint32_t* codep, uint32_t byte) { 33 | uint32_t type = utf8d[byte]; 34 | 35 | *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte); 36 | *state = utf8d[256 + *state*16 + type]; 37 | return *state; 38 | } 39 | 40 | 41 | typedef struct utf8_validator { 42 | uint32_t codepoint; 43 | uint32_t state; 44 | } utf8_validator; 45 | 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /ext/websocket_framing_utils/ws_framing_utils_ruby.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ws_framing_utils.h" 3 | #include "ext_help.h" 4 | 5 | 6 | static VALUE mOverSIP; 7 | static VALUE mWebSocket; 8 | static VALUE mFramingUtils; 9 | static VALUE cUtf8Validator; 10 | 11 | 12 | 13 | /* 14 | * Ruby functions. 15 | */ 16 | 17 | VALUE WsFramingUtils_unmask(VALUE self, VALUE payload, VALUE mask) 18 | { 19 | char *payload_str, *mask_str; 20 | long payload_len; /* mask length is always 4 bytes. */ 21 | char *unmasked_payload_str; 22 | VALUE rb_unmasked_payload; 23 | int i; 24 | 25 | if (TYPE(payload) != T_STRING) 26 | rb_raise(rb_eTypeError, "Argument must be a String"); 27 | 28 | if (TYPE(mask) != T_STRING) 29 | rb_raise(rb_eTypeError, "Argument must be a String"); 30 | 31 | if (RSTRING_LEN(mask) != 4) 32 | rb_raise(rb_eTypeError, "mask size must be 4 bytes"); 33 | 34 | payload_str = RSTRING_PTR(payload); 35 | payload_len = RSTRING_LEN(payload); 36 | mask_str = RSTRING_PTR(mask); 37 | 38 | /* NOTE: In Ruby C extensions always use: 39 | * pointer = ALLOC_N(type, n) 40 | * which means: pointer = (type*)xmalloc(sizeof(type)*(n)) 41 | * and: 42 | * xfree() 43 | */ 44 | 45 | unmasked_payload_str = ALLOC_N(char, payload_len); 46 | 47 | for(i=0; i < payload_len; i++) 48 | unmasked_payload_str[i] = payload_str[i] ^ mask_str[i % 4]; 49 | 50 | rb_unmasked_payload = rb_str_new(unmasked_payload_str, payload_len); 51 | xfree(unmasked_payload_str); 52 | return(rb_unmasked_payload); 53 | } 54 | 55 | 56 | static void Utf8Validator_free(void *validator) 57 | { 58 | TRACE(); 59 | if(validator) { 60 | xfree(validator); 61 | } 62 | } 63 | 64 | 65 | VALUE Utf8Validator_alloc(VALUE klass) 66 | { 67 | TRACE(); 68 | VALUE obj; 69 | utf8_validator *validator = ALLOC(utf8_validator); 70 | 71 | validator->state = UTF8_ACCEPT; 72 | 73 | obj = Data_Wrap_Struct(klass, NULL, Utf8Validator_free, validator); 74 | return obj; 75 | } 76 | 77 | 78 | VALUE Utf8Validator_reset(VALUE self) 79 | { 80 | TRACE(); 81 | utf8_validator *validator = NULL; 82 | DATA_GET(self, utf8_validator, validator); 83 | 84 | validator->state = UTF8_ACCEPT; 85 | 86 | return Qnil; 87 | } 88 | 89 | 90 | /* 91 | * Returns: 92 | * - true: Valid UTF-8 string. 93 | * - nil: Valid but not terminated UTF-8 string. 94 | * - false: Invalid UTF-8 string. 95 | */ 96 | VALUE Utf8Validator_validate(VALUE self, VALUE string) 97 | { 98 | TRACE(); 99 | utf8_validator *validator = NULL; 100 | uint8_t *str = NULL; 101 | int i; 102 | 103 | REQUIRE_TYPE(string, T_STRING); 104 | str = (uint8_t *)RSTRING_PTR(string); 105 | 106 | DATA_GET(self, utf8_validator, validator); 107 | 108 | for(i=0; i < RSTRING_LEN(string); i++) 109 | if (utf8_decode(&validator->state, &validator->codepoint, str[i]) == UTF8_REJECT) 110 | return Qfalse; 111 | 112 | switch(validator->state) { 113 | case UTF8_ACCEPT: 114 | return Qtrue; 115 | break; 116 | default: 117 | return Qnil; 118 | break; 119 | } 120 | } 121 | 122 | 123 | void Init_ws_framing_utils() 124 | { 125 | mOverSIP = rb_define_module("OverSIP"); 126 | mWebSocket = rb_define_module_under(mOverSIP, "WebSocket"); 127 | mFramingUtils = rb_define_module_under(mWebSocket, "FramingUtils"); 128 | cUtf8Validator = rb_define_class_under(mFramingUtils, "Utf8Validator", rb_cObject); 129 | 130 | rb_define_module_function(mFramingUtils, "unmask", WsFramingUtils_unmask,2); 131 | 132 | rb_define_alloc_func(cUtf8Validator, Utf8Validator_alloc); 133 | rb_define_method(cUtf8Validator, "reset", Utf8Validator_reset,0); 134 | rb_define_method(cUtf8Validator, "validate", Utf8Validator_validate,1); 135 | } 136 | -------------------------------------------------------------------------------- /ext/websocket_http_parser/compile_ragel_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | which ragel >/dev/null 5 | if [ $? -ne 0 ] ; then 6 | echo "ERROR: ragel binary not found, cannot compile the Ragel grammar." >&2 7 | exit 1 8 | else 9 | ragel -v 10 | echo 11 | fi 12 | 13 | 14 | set -e 15 | 16 | RAGEL_FILE=ws_http_parser 17 | echo "DEBUG: compiling Ragel grammar $RAGEL_FILE.rl ..." 18 | ragel -T0 -C $RAGEL_FILE.rl 19 | echo 20 | echo "DEBUG: $RAGEL_FILE.c generated" 21 | echo 22 | -------------------------------------------------------------------------------- /ext/websocket_http_parser/ext_help.h: -------------------------------------------------------------------------------- 1 | #ifndef ext_help_h 2 | #define ext_help_h 3 | 4 | #define RAISE_NOT_NULL(T) if(T == NULL) rb_raise(rb_eArgError, "NULL found for " # T " when shouldn't be."); 5 | #define DATA_GET(from,type,name) Data_Get_Struct(from,type,name); RAISE_NOT_NULL(name); 6 | #define REQUIRE_TYPE(V, T) if(TYPE(V) != T) rb_raise(rb_eTypeError, "Wrong argument type for " # V " required " # T); 7 | 8 | 9 | /* Uncomment for enabling TRACE() function. */ 10 | /*#define DEBUG*/ 11 | 12 | #ifdef DEBUG 13 | #define TRACE() fprintf(stderr, "TRACE: %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__) 14 | #else 15 | #define TRACE() 16 | #endif 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /ext/websocket_http_parser/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | 3 | create_makefile("oversip/websocket/ws_http_parser") 4 | -------------------------------------------------------------------------------- /ext/websocket_http_parser/grammar_ws_http_core.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_ws_http_core; 3 | 4 | CRLF = "\r\n"; 5 | CTL = (cntrl | 127); 6 | DIGIT = "0".."9"; 7 | ALPHA = "a".."z" | "A".."Z"; 8 | HEXDIG = DIGIT | "A"i | "B"i | "C"i | "D"i | "E"i | "F"i; 9 | DQUOTE = "\""; 10 | SP = " "; 11 | HTAB = "\t"; 12 | WSP = SP | HTAB; 13 | LWS = ( WSP* CRLF )? WSP+; 14 | SWS = LWS?; 15 | OCTET = 0x00..0xff; 16 | VCHAR = 0x21..0x7e; 17 | HCOLON = ( SP | HTAB )* ":" SWS; 18 | SEMI = SWS ";" SWS; 19 | EQUAL = SWS "=" SWS; 20 | SLASH = SWS "/" SWS; 21 | COLON = SWS ":" SWS; 22 | COMMA = SWS "," SWS; 23 | RAQUOT = ">" SWS; 24 | LAQUOT = SWS "<"; 25 | UTF8_CONT = 0x80..0xbf; 26 | #UTF8_NONASCII = ( 0xc0..0xdf UTF8_CONT ) | ( 0xe0..0xef UTF8_CONT{2} ) | ( 0xf0..0xf7 UTF8_CONT{3} ) | 27 | # ( 0xf8..0xfb UTF8_CONT{4} ) | ( 0xfc..0xfd UTF8_CONT{5} ); 28 | # NOTE: Workaround to relax grammar: 29 | # https://lists.cs.columbia.edu/pipermail/sip-implementors/2010-December/026127.html 30 | UTF8_NONASCII = 0x80..0xff; 31 | # NOTE: Added by me (doesn't include space neither tabulator). 32 | PRINTABLE_ASCII = 0x21..0x7e; 33 | TEXT_UTF8char = PRINTABLE_ASCII | UTF8_NONASCII; 34 | 35 | alphanum = ALPHA | DIGIT; 36 | safe = ("$" | "-" | "_" | "."); 37 | extra = ("!" | "*" | "'" | "(" | ")" | ","); 38 | reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"); 39 | sorta_safe = ("\"" | "<" | ">"); 40 | unsafe = (CTL | " " | "#" | "%" | sorta_safe); 41 | national = any -- (alpha | digit | reserved | extra | safe | unsafe); 42 | unreserved = (alpha | digit | safe | extra | national); 43 | escape = ("%" xdigit xdigit); 44 | uchar = (unreserved | escape | sorta_safe); 45 | pchar = (uchar | ":" | "@" | "&" | "=" | "+"); 46 | tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t"); 47 | 48 | token = ( alphanum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`" | "'" | "~" )+; 49 | word = ( alphanum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`" | "'" | "~" | "(" | ")" | 50 | "<" | ">" | ":" | "\\" | DQUOTE | "/" | "[" | "]" | "?" | "{" | "}" )+; 51 | ctext = 0x21..0x27 | 0x2a..0x5b | 0x5d..0x7e | UTF8_NONASCII | LWS; 52 | quoted_pair = "\\" ( 0x00..0x09 | 0x0b..0x0c | 0x0e..0x7f ); 53 | qdtext = LWS | "!" | 0x23..0x5b | 0x5d..0x7e | UTF8_NONASCII; 54 | quoted_string = DQUOTE ( qdtext | quoted_pair )* DQUOTE; 55 | 56 | domainlabel = alphanum | ( alphanum ( alphanum | "-" | "_" )* alphanum ); 57 | toplabel = ALPHA | ( ALPHA ( alphanum | "-" | "_" )* alphanum ); 58 | hostname = ( domainlabel "." )* toplabel "."?; 59 | dec_octet = DIGIT | ( 0x31..0x39 DIGIT ) | ( "1" DIGIT{2} ) | 60 | ( "2" 0x30..0x34 DIGIT ) | ( "25" 0x30..0x35 ); 61 | IPv4address = dec_octet "." dec_octet "." dec_octet "." dec_octet; 62 | h16 = HEXDIG{1,4}; 63 | ls32 = ( h16 ":" h16 ) | IPv4address; 64 | IPv6address = ( ( h16 ":" ){6} ls32 ) | 65 | ( "::" ( h16 ":" ){5} ls32 ) | 66 | ( h16? "::" ( h16 ":" ){4} ls32 ) | 67 | ( ( ( h16 ":" )? h16 )? "::" ( h16 ":" ){3} ls32 ) | 68 | ( ( ( h16 ":" ){,2} h16 )? "::" ( h16 ":" ){2} ls32 ) | 69 | ( ( ( h16 ":" ){,3} h16 )? "::" h16 ":" ls32 ) | 70 | ( ( ( h16 ":" ){,4} h16 )? "::" ls32 ) | 71 | ( ( ( h16 ":" ){,5} h16 )? "::" h16 ) | 72 | ( ( ( h16 ":" ){,6} h16 )? "::" ); 73 | IPv6reference = "[" IPv6address "]"; 74 | host = hostname | IPv4address | IPv6reference; 75 | port = ( DIGIT{1,4} | 76 | "1".."5" DIGIT{4} | 77 | "6" "0".."4" DIGIT{3} | 78 | "6" "5" "0".."4" DIGIT{2} | 79 | "6" "5" "5" "0".."2" DIGIT | 80 | "6" "5" "5" "3" "0".."5" 81 | ) - ( "0" | "00" | "000" | "0000" ); 82 | hostport = host ( ":" port )?; 83 | userinfo = ((unreserved | escape | ";" | ":" | "&" | "=" | "+")+ "@")*; 84 | 85 | }%% -------------------------------------------------------------------------------- /ext/websocket_http_parser/grammar_ws_http_headers.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_ws_http_headers; 3 | 4 | DefinedHeader = "Content-Length"i | 5 | "Host"i | 6 | "Connection"i | 7 | "Upgrade"i | 8 | # NOTE: After draft-ietf-hybi-thewebsocketprotocol-13, "Sec-WebSocket-Origin" 9 | # becomes just "Origin" (as in HTTP). 10 | "Origin"i | "Sec-WebSocket-Origin"i | 11 | "Sec-WebSocket-Version"i | 12 | "Sec-WebSocket-Key"i | 13 | "Sec-WebSocket-Protocol"i; 14 | 15 | generic_hdr_name = ( token - DefinedHeader ) >write_hdr_value >start_hdr_field %write_hdr_field; 16 | generic_hdr_value = ( TEXT_UTF8char | UTF8_CONT | LWS )* >start_hdr_value %store_hdr_value; 17 | GenericHeader = generic_hdr_name HCOLON generic_hdr_value; 18 | 19 | ### Content-Length 20 | content_length_value = DIGIT{1,9} >mark %content_length; 21 | Content_Length = "Content-Length"i >write_hdr_value >start_hdr_field %write_hdr_field 22 | HCOLON content_length_value >start_hdr_value %store_hdr_value; 23 | 24 | ### Host 25 | host_value = host >mark %host ( ":" port >mark %port )?; 26 | Host = "Host"i >write_hdr_value >start_hdr_field %write_hdr_field 27 | HCOLON host_value >start_hdr_value %store_hdr_value; 28 | 29 | ### Connection 30 | connection_value = token >mark %hdr_connection_value; 31 | Connection = ( "Connection"i ) >write_hdr_value >start_hdr_field %write_hdr_field HCOLON 32 | ( connection_value >start_hdr_value 33 | ( COMMA connection_value )* ) %store_hdr_value; 34 | 35 | ### Upgrade 36 | upgrade_value = token >mark %hdr_upgrade; 37 | Upgrade = "Upgrade"i >write_hdr_value >start_hdr_field %write_hdr_field 38 | HCOLON upgrade_value >start_hdr_value %store_hdr_value; 39 | 40 | ### Origin 41 | origin_value = PRINTABLE_ASCII+ >mark %hdr_origin; 42 | Origin = ( "Origin"i | "Sec-WebSocket-Origin"i ) >write_hdr_value >start_hdr_field %write_hdr_field 43 | HCOLON origin_value >start_hdr_value %store_hdr_value; 44 | 45 | ### Sec-WebSocket-Version 46 | sec_websocket_version_value = DIGIT{1,3} >mark %hdr_sec_websocket_version; 47 | Sec_WebSocket_Version = "Sec-WebSocket-Version"i >write_hdr_value >start_hdr_field %write_hdr_field 48 | HCOLON sec_websocket_version_value >start_hdr_value %store_hdr_value; 49 | 50 | ### Sec-WebSocket-Key 51 | sec_websocket_key_value = PRINTABLE_ASCII{1,50} >mark %hdr_sec_websocket_key; 52 | Sec_WebSocket_Key = "Sec-WebSocket-Key"i >write_hdr_value >start_hdr_field %write_hdr_field 53 | HCOLON sec_websocket_key_value >start_hdr_value %store_hdr_value; 54 | 55 | ### Sec-WebSocket-Protocol 56 | sec_websocket_protocol_value = token >mark %hdr_sec_websocket_protocol_value; 57 | Sec_WebSocket_Protocol = ( "Sec-WebSocket-Protocol"i ) >write_hdr_value >start_hdr_field %write_hdr_field HCOLON 58 | ( sec_websocket_protocol_value >start_hdr_value 59 | ( COMMA sec_websocket_protocol_value )* ) %store_hdr_value; 60 | 61 | Header = GenericHeader | 62 | Content_Length | 63 | Host | 64 | Connection | 65 | Upgrade | 66 | Origin | 67 | Sec_WebSocket_Version | 68 | Sec_WebSocket_Key | 69 | Sec_WebSocket_Protocol; 70 | }%% -------------------------------------------------------------------------------- /ext/websocket_http_parser/grammar_ws_http_request.rl: -------------------------------------------------------------------------------- 1 | %%{ 2 | machine grammar_ws_http_request; 3 | 4 | include grammar_ws_http_core "grammar_ws_http_core.rl"; 5 | include grammar_ws_http_headers "grammar_ws_http_headers.rl"; 6 | 7 | path = pchar+ ( "/" pchar* )*; 8 | query = ( uchar | reserved )*; 9 | rel_path = path? %request_path ("?" %start_query query %query)?; 10 | absolute_path = "/"+ rel_path; 11 | Fragment = ( uchar | reserved )* >start_fragment %fragment; 12 | Request_URI = (absolute_path ("#" Fragment)?) >mark %request_uri; 13 | 14 | Method = ( "GET" %method_GET | 15 | "POST" %method_POST | 16 | "OPTIONS" %method_OPTIONS | 17 | token ) >mark %method_unknown; 18 | 19 | HTTP_Version = "HTTP"i "/" DIGIT{1,2} "." DIGIT{1,2}; 20 | 21 | Request_Line = Method %req_method SP 22 | Request_URI SP 23 | HTTP_Version >mark %http_version; 24 | 25 | Request = Request_Line :> CRLF 26 | ( Header CRLF )* 27 | CRLF >write_hdr_value @done; 28 | 29 | main := Request; 30 | }%% 31 | -------------------------------------------------------------------------------- /ext/websocket_http_parser/ws_http_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef ws_http_parser_h 2 | #define ws_http_parser_h 3 | 4 | 5 | #include 6 | 7 | #if defined(_WIN32) 8 | #include 9 | #endif 10 | 11 | 12 | enum method { 13 | method_GET = 1, 14 | method_POST, 15 | method_OPTIONS, 16 | method_unknown 17 | }; 18 | 19 | enum uri_scheme { 20 | uri_scheme_http = 1, 21 | uri_scheme_https, 22 | uri_scheme_unknown 23 | }; 24 | 25 | typedef void (*msg_method_cb)(void *data, const char *at, size_t length, enum method method); 26 | typedef void (*uri_scheme_cb)(void *data, const char *at, size_t length, enum uri_scheme); 27 | typedef void (*msg_element_cb)(void *data, const char *at, size_t length); 28 | typedef void (*header_cb)(void *data, const char *hdr_field, size_t hdr_field_len, const char *hdr_value, size_t hdr_value_len); 29 | 30 | 31 | typedef struct struct_request { 32 | msg_method_cb method; 33 | uri_scheme_cb uri_scheme; 34 | msg_element_cb request_uri; 35 | msg_element_cb request_path; 36 | msg_element_cb query; 37 | msg_element_cb fragment; 38 | msg_element_cb http_version; 39 | msg_element_cb host; 40 | msg_element_cb port; 41 | msg_element_cb content_length; 42 | msg_element_cb hdr_connection_value; 43 | msg_element_cb hdr_upgrade; 44 | msg_element_cb hdr_origin; 45 | msg_element_cb hdr_sec_websocket_version; 46 | msg_element_cb hdr_sec_websocket_key; 47 | msg_element_cb hdr_sec_websocket_protocol_value; 48 | } struct_request; 49 | 50 | typedef struct ws_http_request_parser { 51 | /* Parser stuf. */ 52 | int cs; 53 | size_t nread; 54 | char * error_start; 55 | size_t error_len; 56 | int error_pos; 57 | 58 | size_t mark; 59 | size_t hdr_field_start; 60 | size_t hdr_field_len; 61 | size_t hdr_value_start; 62 | size_t hdr_value_len; 63 | size_t query_start; 64 | size_t fragment_start; 65 | 66 | /* Request method. */ 67 | enum method method; 68 | /* URI scheme type. */ 69 | enum uri_scheme uri_scheme; 70 | 71 | header_cb header; 72 | struct_request request; 73 | 74 | /* OverSIP::WebSocket::Request instance. */ 75 | void * data; 76 | } ws_http_request_parser; 77 | 78 | 79 | int ws_http_request_parser_init(ws_http_request_parser *parser); 80 | int ws_http_request_parser_finish(ws_http_request_parser *parser); 81 | size_t ws_http_request_parser_execute(ws_http_request_parser *parser, const char *buffer, size_t len, size_t off); 82 | int ws_http_request_parser_has_error(ws_http_request_parser *parser); 83 | int ws_http_request_parser_is_finished(ws_http_request_parser *parser); 84 | #define ws_http_request_parser_nread(parser) (parser)->nread 85 | 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /lib/oversip.rb: -------------------------------------------------------------------------------- 1 | # 2 | # OverSIP 3 | # Copyright (c) 2012-2014 Iñaki Baz Castillo 4 | # MIT License 5 | # 6 | 7 | # Ruby built-in libraries. 8 | 9 | require "rbconfig" 10 | require "etc" 11 | require "fileutils" 12 | require "socket" 13 | require "timeout" 14 | require "yaml" 15 | require "tempfile" 16 | require "base64" 17 | require "digest/md5" 18 | require "digest/sha1" 19 | require "securerandom" 20 | require "fiber" 21 | require "openssl" 22 | 23 | 24 | # Ruby external gems. 25 | 26 | require "syslog" 27 | gem "eventmachine", "~> 1.2.0" 28 | require "eventmachine" 29 | gem "iobuffer", "= 1.1.2" 30 | require "iobuffer" 31 | gem "em-udns", "= 0.3.6" 32 | require "em-udns" 33 | gem "escape_utils", "= 1.0.1" 34 | require "escape_utils" 35 | gem "term-ansicolor", "= 1.3.2" 36 | require "term/ansicolor" 37 | gem "posix-spawn", "= 0.3.9" 38 | require "posix-spawn" 39 | gem "em-synchrony", "= 1.0.3" 40 | require "em-synchrony" 41 | 42 | 43 | # OverSIP files. 44 | 45 | require "oversip/version.rb" 46 | require "oversip/syslog.rb" 47 | require "oversip/logger.rb" 48 | require "oversip/config.rb" 49 | require "oversip/config_validators.rb" 50 | require "oversip/proxies_config.rb" 51 | require "oversip/errors.rb" 52 | require "oversip/launcher.rb" 53 | require "oversip/utils.#{RbConfig::CONFIG["DLEXT"]}" 54 | require "oversip/utils.rb" 55 | require "oversip/default_server.rb" 56 | require "oversip/system_callbacks.rb" 57 | 58 | require "oversip/sip/sip.rb" 59 | require "oversip/sip/sip_parser.#{RbConfig::CONFIG["DLEXT"]}" 60 | require "oversip/sip/constants.rb" 61 | require "oversip/sip/core.rb" 62 | require "oversip/sip/message.rb" 63 | require "oversip/sip/request.rb" 64 | require "oversip/sip/response.rb" 65 | require "oversip/sip/uri.rb" 66 | require "oversip/sip/name_addr.rb" 67 | require "oversip/sip/message_processor.rb" 68 | require "oversip/sip/listeners.rb" 69 | require "oversip/sip/launcher.rb" 70 | require "oversip/sip/server_transaction.rb" 71 | require "oversip/sip/client_transaction.rb" 72 | require "oversip/sip/transport_manager.rb" 73 | require "oversip/sip/timers.rb" 74 | require "oversip/sip/tags.rb" 75 | require "oversip/sip/rfc3263.rb" 76 | require "oversip/sip/client.rb" 77 | require "oversip/sip/proxy.rb" 78 | require "oversip/sip/uac.rb" 79 | require "oversip/sip/uac_request.rb" 80 | 81 | require "oversip/websocket/websocket.rb" 82 | require "oversip/websocket/ws_http_parser.#{RbConfig::CONFIG["DLEXT"]}" 83 | require "oversip/websocket/constants.rb" 84 | require "oversip/websocket/http_request.rb" 85 | require "oversip/websocket/listeners.rb" 86 | require "oversip/websocket/launcher.rb" 87 | require "oversip/websocket/ws_framing_utils.#{RbConfig::CONFIG["DLEXT"]}" 88 | require "oversip/websocket/ws_framing.rb" 89 | require "oversip/websocket/ws_sip_app.rb" 90 | 91 | require "oversip/fiber_pool.rb" 92 | require "oversip/tls.rb" 93 | require "oversip/stun.#{RbConfig::CONFIG["DLEXT"]}" 94 | 95 | require "oversip/modules/user_assertion.rb" 96 | require "oversip/modules/outbound_mangling.rb" 97 | 98 | require "oversip/ruby_ext/eventmachine.rb" 99 | 100 | 101 | module OverSIP 102 | 103 | class << self 104 | attr_accessor :pid_file, :master_name, :pid, :daemonized, 105 | :configuration, 106 | :proxies, 107 | :tls_public_cert, :tls_private_cert, 108 | :stud_pids, 109 | :is_ready, # true, false 110 | :status, # :loading, :running, :terminating 111 | :root_fiber 112 | 113 | def daemonized? 114 | @daemonized 115 | end 116 | 117 | end 118 | 119 | # Pre-declare internal modules. 120 | module SIP ; end 121 | module WebSocket ; end 122 | module Modules ; end 123 | 124 | # Allow OverSIP::M::MODULE_NAME usage. 125 | M = Modules 126 | 127 | end 128 | -------------------------------------------------------------------------------- /lib/oversip/config_validators.rb: -------------------------------------------------------------------------------- 1 | require "openssl" 2 | 3 | 4 | module OverSIP 5 | 6 | module Config 7 | 8 | module Validators 9 | 10 | extend ::OverSIP::Logger 11 | 12 | DOMAIN_REGEXP = /^(([0-9a-zA-Z\-_])+\.)*([0-9a-zA-Z\-_])+$/ 13 | TLS_PEM_CHAIN_REGEXP = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m 14 | 15 | def boolean value 16 | value == true or value == false 17 | end 18 | 19 | def string value 20 | value.is_a? ::String 21 | end 22 | 23 | def fixnum value 24 | value.is_a? ::Fixnum 25 | end 26 | 27 | def port value 28 | fixnum(value) and value.between?(1,65536) 29 | end 30 | 31 | def ipv4 value 32 | return false unless value.is_a? ::String 33 | ::OverSIP::Utils.ip_type(value) == :ipv4 and value != "0.0.0.0" 34 | end 35 | 36 | def ipv6 value 37 | return false unless value.is_a? ::String 38 | ::OverSIP::Utils.ip_type(value) == :ipv6 and ::OverSIP::Utils.normalize_ipv6(value) != "::" 39 | end 40 | 41 | def ipv4_any value 42 | return false unless value.is_a? ::String 43 | ::OverSIP::Utils.ip_type(value) == :ipv4 44 | end 45 | 46 | def ipv6_any value 47 | return false unless value.is_a? ::String 48 | ::OverSIP::Utils.ip_type(value) == :ipv6 49 | end 50 | 51 | def domain value 52 | value =~ DOMAIN_REGEXP 53 | end 54 | 55 | def choices value, choices 56 | choices.include? value 57 | end 58 | 59 | def greater_than value, minimum 60 | value > minimum rescue false 61 | end 62 | 63 | def greater_equal_than value, minimum 64 | value >= minimum rescue false 65 | end 66 | 67 | def minor_than value, maximum 68 | value < maximum rescue false 69 | end 70 | 71 | def minor_equal_than value, maximum 72 | value <= maximum rescue false 73 | end 74 | 75 | def readable_file file 76 | ::File.file?(file) and ::File.readable?(file) 77 | end 78 | 79 | def readable_dir dir 80 | ::File.directory?(dir) and ::File.readable?(dir) 81 | end 82 | 83 | def tls_pem_chain file 84 | chain = ::File.read file 85 | pems = chain.scan(TLS_PEM_CHAIN_REGEXP).flatten 86 | pem_found = nil 87 | 88 | begin 89 | pems.each do |pem| 90 | ::OpenSSL::X509::Certificate.new pem 91 | pem_found = true 92 | end 93 | rescue => e 94 | log_system_error "#{e.class}: #{e.message}" 95 | return false 96 | end 97 | 98 | if pem_found 99 | return true 100 | else 101 | log_system_error "no valid X509 PEM found in the file" 102 | return false 103 | end 104 | end 105 | 106 | def tls_pem_private file 107 | pem = ::File.read file 108 | key_classes = [::OpenSSL::PKey::RSA, ::OpenSSL::PKey::DSA] 109 | 110 | begin 111 | key_class = key_classes.shift 112 | key_class.new pem 113 | return true 114 | rescue => e 115 | retry if key_classes.any? 116 | log_system_error e.message 117 | end 118 | 119 | return false 120 | end 121 | 122 | end # module Validators 123 | 124 | end # module Config 125 | 126 | end 127 | -------------------------------------------------------------------------------- /lib/oversip/default_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP 2 | 3 | module SystemEvents 4 | 5 | extend ::OverSIP::Logger 6 | 7 | def self.on_initialize 8 | end 9 | 10 | def self.on_started 11 | end 12 | 13 | def self.on_user_reload 14 | end 15 | 16 | def self.on_terminated error 17 | end 18 | 19 | 20 | end 21 | 22 | module SipEvents 23 | 24 | extend ::OverSIP::Logger 25 | 26 | def self.on_request request 27 | end 28 | 29 | def self.on_client_tls_handshake connection, pems 30 | end 31 | 32 | def self.on_server_tls_handshake connection, pems 33 | end 34 | 35 | end 36 | 37 | module WebSocketEvents 38 | 39 | extend ::OverSIP::Logger 40 | 41 | def self.on_connection connection, http_request 42 | end 43 | 44 | def self.on_disconnection connection, client_closed 45 | end 46 | 47 | def self.on_client_tls_handshake connection, pems 48 | end 49 | 50 | end 51 | 52 | end 53 | -------------------------------------------------------------------------------- /lib/oversip/errors.rb: -------------------------------------------------------------------------------- 1 | module OverSIP 2 | 3 | class Error < ::StandardError ; end 4 | 5 | class ConfigurationError < Error ; end 6 | class RuntimeError < Error ; end 7 | 8 | class ParsingError < RuntimeError ; end 9 | 10 | end -------------------------------------------------------------------------------- /lib/oversip/fiber_pool.rb: -------------------------------------------------------------------------------- 1 | # NOTE: Extracted from https://github.com/schmurfy/fiber_pool. 2 | 3 | module OverSIP 4 | class FiberPool 5 | 6 | # Prepare a list of fibers that are able to run different blocks of code 7 | # every time. Once a fiber is done with its block, it attempts to fetch 8 | # another one from the queue. 9 | def initialize count = 100 10 | @fibers,@busy_fibers,@queue = [],{},[] 11 | 12 | count.times do |i| 13 | add_fiber() 14 | end 15 | end 16 | 17 | def add_fiber 18 | fiber = ::Fiber.new do |block| 19 | loop do 20 | block.call 21 | unless @queue.empty? 22 | block = @queue.shift 23 | else 24 | @busy_fibers.delete ::Fiber.current.object_id 25 | @fibers.unshift ::Fiber.current 26 | block = ::Fiber.yield 27 | end 28 | end 29 | end 30 | 31 | @fibers << fiber 32 | fiber 33 | end 34 | private :add_fiber 35 | 36 | # If there is an available fiber use it, otherwise, leave it to linger 37 | # in a queue. 38 | def spawn &block 39 | # resurrect dead fibers 40 | @busy_fibers.values.reject(&:alive?).each do |f| 41 | @busy_fibers.delete f.object_id 42 | add_fiber() 43 | end 44 | 45 | if (fiber = @fibers.shift) 46 | @busy_fibers[fiber.object_id] = fiber 47 | fiber.resume block 48 | else 49 | @queue << block 50 | end 51 | 52 | fiber 53 | end 54 | 55 | end # class FiberPool 56 | end -------------------------------------------------------------------------------- /lib/oversip/logger.rb: -------------------------------------------------------------------------------- 1 | module OverSIP 2 | 3 | # Logging client module. Any class desiring to log messages must include (or extend) this module. 4 | # In order to identify itself in the logs, the class can define log_id() method or set @log_id 5 | # attribute. 6 | module Logger 7 | 8 | def self.load_methods 9 | ::Syslog.close if ::Syslog.opened? 10 | 11 | syslog_options = ::Syslog::LOG_PID | ::Syslog::LOG_NDELAY 12 | syslog_facility = ::OverSIP::Syslog::SYSLOG_FACILITY_MAPPING[::OverSIP.configuration[:core][:syslog_facility]] rescue ::Syslog::LOG_DAEMON 13 | ::Syslog.open(::OverSIP.master_name, syslog_options, syslog_facility) 14 | 15 | begin 16 | @@threshold = ::OverSIP::Syslog::SYSLOG_SEVERITY_MAPPING[::OverSIP.configuration[:core][:syslog_level]] 17 | rescue 18 | @@threshold = 0 # debug. 19 | end 20 | 21 | $oversip_debug = ( @@threshold == 0 ? true : false ) 22 | 23 | ::OverSIP::Syslog::SYSLOG_SEVERITY_MAPPING.each do |level_str, level_value| 24 | method_str = " 25 | def log_system_#{level_str}(msg) 26 | " 27 | 28 | method_str << " 29 | return false if @@threshold > #{level_value} 30 | 31 | ::OverSIP::Syslog.log #{level_value}, msg, log_id, false 32 | " 33 | 34 | if not ::OverSIP.daemonized? 35 | if %w{debug info notice}.include? level_str 36 | method_str << " 37 | puts ::OverSIP::Logger.fg_system_msg2str('#{level_str}', msg, log_id) 38 | " 39 | else 40 | method_str << " 41 | $stderr.puts ::OverSIP::Logger.fg_system_msg2str('#{level_str}', msg, log_id) 42 | " 43 | end 44 | end 45 | 46 | method_str << "end" 47 | 48 | self.module_eval method_str 49 | 50 | 51 | # User logs. 52 | method_str = " 53 | def log_#{level_str}(msg) 54 | return false if @@threshold > #{level_value} 55 | 56 | ::OverSIP::Syslog.log #{level_value}, msg, log_id, true 57 | end 58 | " 59 | 60 | self.module_eval method_str 61 | 62 | end # .each 63 | end 64 | 65 | def self.fg_system_msg2str(level_str, msg, log_id) 66 | case msg 67 | when ::String 68 | "#{level_str.upcase}: <#{log_id}> " << msg 69 | when ::Exception 70 | "#{level_str.upcase}: <#{log_id}> #{msg.message} (#{msg.class })\n#{(msg.backtrace || [])[0..3].join("\n")}" 71 | else 72 | "#{level_str.upcase}: <#{log_id}> " << msg.inspect 73 | end 74 | end 75 | 76 | # Default logging identifier is the class name. If log_id() method is redefined by the 77 | # class including this module, or it sets @log_id, then such a value takes preference. 78 | def log_id 79 | @log_id ||= (self.is_a?(::Module) ? self.name.split("::").last : self.class.name) 80 | end 81 | 82 | end # module Logger 83 | 84 | end 85 | -------------------------------------------------------------------------------- /lib/oversip/modules/outbound_mangling.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::Modules 2 | 3 | module OutboundMangling 4 | 5 | extend ::OverSIP::Logger 6 | 7 | @log_id = "OutboundMangling module" 8 | 9 | def self.add_outbound_to_contact proxy 10 | unless proxy.is_a? ::OverSIP::SIP::Proxy 11 | raise ::OverSIP::RuntimeError, "proxy must be a OverSIP::SIP::Proxy instance" 12 | end 13 | 14 | proxy.on_target do |target| 15 | request = proxy.request 16 | # Just act in case the request has a single Contact, its connection uses Outbound 17 | # and no ;ov-ob param exists in Contact URI. 18 | if request.contact and request.connection_outbound_flow_token and not request.contact.has_param? "ov-ob" 19 | log_system_debug "performing Contact mangling (adding ;ov-ob Outbound param) for #{request.log_id}" if $oversip_debug 20 | 21 | request.contact.set_param "ov-ob", request.connection_outbound_flow_token 22 | 23 | proxy.on_success_response do |response| 24 | if (contacts = response.headers["Contact"]) 25 | log_system_debug "reverting original Contact value (removing ;ov-ob Outbound param) from response" if $oversip_debug 26 | contacts.each { |contact| contact.gsub! /;ov-ob=[_\-0-9A-Za-z]+/, "" } 27 | end 28 | end 29 | end 30 | end 31 | end 32 | 33 | def self.extract_outbound_from_ruri request 34 | # Do nothing if the request already contains a Route header with the Outbound flow token (so 35 | # the registrar *does* support Path). 36 | unless request.incoming_outbound_requested? 37 | if (ov_ob = request.ruri.del_param("ov-ob")) 38 | log_system_debug "incoming Outbound flow token extracted from ;ov-ob param in RURI for #{request.log_id}" if $oversip_debug 39 | request.route_outbound_flow_token = ov_ob 40 | request.incoming_outbound_requested = true 41 | return true 42 | else 43 | return false 44 | end 45 | 46 | else 47 | # If the request already contains a proper Outbound Route header, then at least try to remove 48 | # the ;ov-ob param from the RURI. 49 | request.ruri.del_param("ov-ob") 50 | return false 51 | end 52 | end 53 | 54 | end # module OutboundMangling 55 | 56 | end 57 | -------------------------------------------------------------------------------- /lib/oversip/modules/user_assertion.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::Modules 2 | 3 | module UserAssertion 4 | 5 | extend ::OverSIP::Logger 6 | 7 | @log_id = "UserAssertion module" 8 | 9 | def self.assert_connection message 10 | case message 11 | when ::OverSIP::SIP::Request 12 | request = message 13 | when ::OverSIP::SIP::Response 14 | request = message.request 15 | else 16 | raise ::OverSIP::RuntimeError, "message must be a OverSIP::SIP::Request or OverSIP::SIP::Response" 17 | end 18 | 19 | # Don't do this stuf for UDP or for outbound connections. 20 | return false unless request.connection.class.reliable_transport_listener? 21 | # Return if already set. 22 | return request.cvars[:asserted_user] if request.cvars[:asserted_user] 23 | # Don't do this stuf in case of P-Preferred-Identity header is present. 24 | return false if request.headers["P-Preferred-Identity"] 25 | 26 | log_system_debug "user #{request.from.uri} asserted to connection" if $oversip_debug 27 | # Store the request From URI as "asserted_user" for this connection. 28 | request.cvars[:asserted_user] = request.from.uri 29 | end 30 | 31 | def self.revoke_assertion message 32 | case message 33 | when ::OverSIP::SIP::Request 34 | request = message 35 | when ::OverSIP::SIP::Response 36 | request = message.request 37 | else 38 | raise ::OverSIP::RuntimeError, "message must be a OverSIP::SIP::Request or OverSIP::SIP::Response" 39 | end 40 | 41 | request.cvars.delete :asserted_user 42 | true 43 | end 44 | 45 | def self.add_pai request 46 | # Add P-Asserted-Identity if the user has previously been asserted but JUST 47 | # in case it matches request From URI ! 48 | # NOTE: If the connection is not asserted (it's null) then it will not match this 49 | # comparisson, so OK. 50 | if request.cvars[:asserted_user] == request.from.uri 51 | # Don't add P-Asserted-Identity if the request contains P-Preferred-Identity header. 52 | unless request.headers["P-Preferred-Identity"] 53 | log_system_debug "user asserted, adding P-Asserted-Identity for #{request.log_id}" if $oversip_debug 54 | request.set_header "P-Asserted-Identity", "<" << request.cvars[:asserted_user] << ">" 55 | return true 56 | else 57 | # Remove posible P-Asserted-Identity header! 58 | log_system_debug "user asserted but P-Preferred-Identity header present, P-Asserted-Identity not added for #{request.log_id}" if $oversip_debug 59 | request.headers.delete "P-Asserted-Identity" 60 | return nil 61 | end 62 | 63 | # Otherwise ensure the request has no spoofed P-Asserted-Identity headers! 64 | else 65 | request.headers.delete "P-Asserted-Identity" 66 | return false 67 | 68 | end 69 | end 70 | 71 | end # module UserAssertion 72 | 73 | end 74 | -------------------------------------------------------------------------------- /lib/oversip/ruby_ext/eventmachine.rb: -------------------------------------------------------------------------------- 1 | module EventMachine 2 | 3 | # Fast method for setting an outgoing TCP connection. 4 | def self.oversip_connect_tcp_server bind_addr, server, port, klass, *args 5 | s = bind_connect_server bind_addr, 0, server, port 6 | c = klass.new s, *args 7 | @conns[s] = c 8 | block_given? and yield c 9 | c 10 | end 11 | 12 | 13 | class Connection 14 | 15 | # We require Ruby 1.9 so don't check String#bytesize method. 16 | def send_data data 17 | ::EventMachine::send_data @signature, data, data.bytesize 18 | end 19 | 20 | def send_datagram data, address, port 21 | ::EventMachine::send_datagram @signature, data, data.bytesize, address, port 22 | end 23 | 24 | # Rewrite close_connection so it set an internal attribute (which can be 25 | # inspected when unbind() callback is called). 26 | alias _em_close_connection close_connection 27 | def close_connection after_writing=false 28 | @local_closed = true 29 | _em_close_connection after_writing 30 | end 31 | 32 | def close_connection_after_writing 33 | close_connection true 34 | end 35 | 36 | end 37 | 38 | end -------------------------------------------------------------------------------- /lib/oversip/sip/constants.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | CRLF = "\r\n" 4 | DOUBLE_CRLF = "\r\n\r\n" 5 | 6 | # DOC: http://www.iana.org/assignments/sip-parameters 7 | REASON_PHRASE = { 8 | 100 => "Trying", 9 | 180 => "Ringing", 10 | 181 => "Call Is Being Forwarded", 11 | 182 => "Queued", 12 | 183 => "Session Progress", 13 | 199 => "Early Dialog Terminated", # draft-ietf-sipcore-199 14 | 200 => "OK", 15 | 202 => "Accepted", # RFC 3265 16 | 204 => "No Notification", #RFC 5839 17 | 300 => "Multiple Choices", 18 | 301 => "Moved Permanently", 19 | 302 => "Moved Temporarily", 20 | 305 => "Use Proxy", 21 | 380 => "Alternative Service", 22 | 400 => "Bad Request", 23 | 401 => "Unauthorized", 24 | 402 => "Payment Required", 25 | 403 => "Forbidden", 26 | 404 => "Not Found", 27 | 405 => "Method Not Allowed", 28 | 406 => "Not Acceptable", 29 | 407 => "Proxy Authentication Required", 30 | 408 => "Request Timeout", 31 | 410 => "Gone", 32 | 412 => "Conditional Request Failed", # RFC 3903 33 | 413 => "Request Entity Too Large", 34 | 414 => "Request-URI Too Long", 35 | 415 => "Unsupported Media Type", 36 | 416 => "Unsupported URI Scheme", 37 | 417 => "Unknown Resource-Priority", # RFC 4412 38 | 420 => "Bad Extension", 39 | 421 => "Extension Required", 40 | 422 => "Session Interval Too Small", # RFC 4028 41 | 423 => "Interval Too Brief", 42 | 424 => "Bad Location Information", # RFC 6442 43 | 428 => "Use Identity Header", # RFC 4474 44 | 429 => "Provide Referrer Identity", # RFC 3892 45 | 430 => "Flow Failed", # RFC 5626 46 | 433 => "Anonymity Disallowed", # RFC 5079 47 | 436 => "Bad Identity-Info", # RFC 4474 48 | 437 => "Unsupported Certificate", # RFC 4744 49 | 438 => "Invalid Identity Header", # RFC 4744 50 | 439 => "First Hop Lacks Outbound Support", # RFC 5626 51 | 440 => "Max-Breadth Exceeded", # RFC 5393 52 | 469 => "Bad Info Package", # draft-ietf-sipcore-info-events 53 | 470 => "Consent Needed", # RF C5360 54 | 478 => "Unresolvable Destination", # Custom code copied from Kamailio. 55 | 480 => "Temporarily Unavailable", 56 | 481 => "Call/Transaction Does Not Exist", 57 | 482 => "Loop Detected", 58 | 483 => "Too Many Hops", 59 | 484 => "Address Incomplete", 60 | 485 => "Ambiguous", 61 | 486 => "Busy Here", 62 | 487 => "Request Terminated", 63 | 488 => "Not Acceptable Here", 64 | 489 => "Bad Event", # RFC 3265 65 | 491 => "Request Pending", 66 | 493 => "Undecipherable", 67 | 494 => "Security Agreement Required", # RFC 3329 68 | 500 => "Server Internal Error", 69 | 501 => "Not Implemented", 70 | 502 => "Bad Gateway", 71 | 503 => "Service Unavailable", 72 | 504 => "Server Time-out", 73 | 505 => "Version Not Supported", 74 | 513 => "Message Too Large", 75 | 580 => "Precondition Failure", # RFC 3312 76 | 600 => "Busy Everywhere", 77 | 603 => "Decline", 78 | 604 => "Does Not Exist Anywhere", 79 | 606 => "Not Acceptable" 80 | } 81 | 82 | REASON_PHRASE_NOT_SET = "Reason Phrase Not Set" 83 | 84 | HDR_SERVER = "Server: #{::OverSIP::PROGRAM_NAME}/#{::OverSIP::VERSION}".freeze 85 | HDR_USER_AGENT = "User-Agent: #{::OverSIP::PROGRAM_NAME}/#{::OverSIP::VERSION}".freeze 86 | HDR_ARRAY_CONTENT_LENGTH_0 = [ "0" ].freeze 87 | 88 | end 89 | -------------------------------------------------------------------------------- /lib/oversip/sip/listeners.rb: -------------------------------------------------------------------------------- 1 | # OverSIP files 2 | 3 | require "oversip/sip/listeners/connection" 4 | require "oversip/sip/listeners/udp_connection" 5 | require "oversip/sip/listeners/tcp_connection" 6 | require "oversip/sip/listeners/tls_tunnel_connection" 7 | require "oversip/sip/listeners/tcp_server" 8 | require "oversip/sip/listeners/tls_server" 9 | require "oversip/sip/listeners/tls_tunnel_server" 10 | require "oversip/sip/listeners/tcp_client" 11 | require "oversip/sip/listeners/tls_client" 12 | 13 | require "oversip/sip/listeners/ipv4_udp_server" 14 | require "oversip/sip/listeners/ipv6_udp_server" 15 | require "oversip/sip/listeners/ipv4_tcp_server" 16 | require "oversip/sip/listeners/ipv6_tcp_server" 17 | require "oversip/sip/listeners/ipv4_tls_server" 18 | require "oversip/sip/listeners/ipv6_tls_server" 19 | require "oversip/sip/listeners/ipv4_tls_tunnel_server" 20 | require "oversip/sip/listeners/ipv6_tls_tunnel_server" 21 | require "oversip/sip/listeners/ipv4_tcp_client" 22 | require "oversip/sip/listeners/ipv6_tcp_client" 23 | require "oversip/sip/listeners/ipv4_tls_client" 24 | require "oversip/sip/listeners/ipv6_tls_client" 25 | -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/connection.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class Connection < ::EM::Connection 4 | 5 | include ::OverSIP::Logger 6 | include ::OverSIP::SIP::MessageProcessor 7 | 8 | class << self 9 | attr_accessor :ip_type, :ip, :port, :transport, 10 | :via_core, 11 | :record_route, 12 | :outbound_record_route_fragment, :outbound_path_fragment, 13 | :connections, 14 | :invite_server_transactions, :non_invite_server_transactions, 15 | :invite_client_transactions, :non_invite_client_transactions 16 | 17 | def reliable_transport_listener? 18 | @is_reliable_transport_listener 19 | end 20 | 21 | def outbound_listener? 22 | @is_outbound_listener 23 | end 24 | end 25 | 26 | 27 | attr_reader :cvars 28 | 29 | def initialize 30 | @parser = ::OverSIP::SIP::MessageParser.new 31 | @buffer = ::IO::Buffer.new 32 | @state = :init 33 | @cvars = {} 34 | 35 | end 36 | 37 | def receive_senderror error, data 38 | log_system_error "Socket sending error: #{error.inspect}, #{data.inspect}" 39 | end 40 | 41 | def transport 42 | self.class.transport 43 | end 44 | 45 | def open? 46 | ! error? 47 | end 48 | 49 | # close() method causes @local_closed = true. 50 | alias close close_connection_after_writing 51 | end 52 | 53 | end 54 | 55 | -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv4_tcp_client.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv4TcpClient < TcpClient 4 | 5 | @ip_type = :ipv4 6 | @transport = :tcp 7 | @server_class = ::OverSIP::SIP::IPv4TcpServer 8 | @connections = @server_class.connections 9 | @invite_server_transactions = @server_class.invite_server_transactions 10 | @non_invite_server_transactions = @server_class.non_invite_server_transactions 11 | @invite_client_transactions = @server_class.invite_client_transactions 12 | @non_invite_client_transactions = @server_class.non_invite_client_transactions 13 | 14 | LOG_ID = "SIP TCP IPv4 client" 15 | def log_id 16 | LOG_ID 17 | end 18 | 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv4_tcp_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv4TcpServer < TcpServer 4 | 5 | @ip_type = :ipv4 6 | @transport = :tcp 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP TCP IPv4 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv4_tls_client.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv4TlsClient < TlsClient 4 | 5 | @ip_type = :ipv4 6 | @transport = :tls 7 | @server_class = ::OverSIP::SIP::IPv4TlsServer 8 | @connections = @server_class.connections 9 | @invite_server_transactions = @server_class.invite_server_transactions 10 | @non_invite_server_transactions = @server_class.non_invite_server_transactions 11 | @invite_client_transactions = @server_class.invite_client_transactions 12 | @non_invite_client_transactions = @server_class.non_invite_client_transactions 13 | 14 | LOG_ID = "SIP TLS IPv4 client" 15 | def log_id 16 | LOG_ID 17 | end 18 | 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv4_tls_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv4TlsServer < TlsServer 4 | 5 | @ip_type = :ipv4 6 | @transport = :tls 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP TLS IPv4 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv4_tls_tunnel_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv4TlsTunnelServer < TlsTunnelServer 4 | 5 | @ip_type = :ipv4 6 | @transport = :tls 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP TLS-Tunnel IPv4 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv4_udp_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv4UdpServer < UdpConnection 4 | 5 | @ip_type = :ipv4 6 | @transport = :udp 7 | @connections = nil # To be set after creating the unique server instance. 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_outbound_listener = true 13 | 14 | LOG_ID = "SIP UDP IPv4 server" 15 | def log_id 16 | LOG_ID 17 | end 18 | 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv6_tcp_client.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv6TcpClient < TcpClient 4 | 5 | @ip_type = :ipv6 6 | @transport = :tcp 7 | @server_class = ::OverSIP::SIP::IPv6TcpServer 8 | @connections = @server_class.connections 9 | @invite_server_transactions = @server_class.invite_server_transactions 10 | @non_invite_server_transactions = @server_class.non_invite_server_transactions 11 | @invite_client_transactions = @server_class.invite_client_transactions 12 | @non_invite_client_transactions = @server_class.non_invite_client_transactions 13 | 14 | LOG_ID = "SIP TCP IPv6 client" 15 | def log_id 16 | LOG_ID 17 | end 18 | 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv6_tcp_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv6TcpServer < TcpServer 4 | 5 | @ip_type = :ipv6 6 | @transport = :tcp 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP TCP IPv6 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv6_tls_client.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv6TlsClient < TlsClient 4 | 5 | @ip_type = :ipv6 6 | @transport = :tls 7 | @server_class = ::OverSIP::SIP::IPv6TlsServer 8 | @connections = @server_class.connections 9 | @invite_server_transactions = @server_class.invite_server_transactions 10 | @non_invite_server_transactions = @server_class.non_invite_server_transactions 11 | @invite_client_transactions = @server_class.invite_client_transactions 12 | @non_invite_client_transactions = @server_class.non_invite_client_transactions 13 | 14 | LOG_ID = "SIP TLS IPv6 client" 15 | def log_id 16 | LOG_ID 17 | end 18 | 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv6_tls_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv6TlsServer < TlsServer 4 | 5 | @ip_type = :ipv6 6 | @transport = :tls 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP TLS IPv6 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv6_tls_tunnel_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv6TlsTunnelServer < TlsTunnelServer 4 | 5 | @ip_type = :ipv6 6 | @transport = :tls 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP TLS-Tunnel IPv6 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/ipv6_udp_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class IPv6UdpServer < UdpConnection 4 | 5 | @ip_type = :ipv6 6 | @transport = :udp 7 | @connections = nil # To be set after creating the unique server instance. 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_outbound_listener = true 13 | 14 | LOG_ID = "SIP UDP IPv6 server" 15 | def log_id 16 | LOG_ID 17 | end 18 | 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/tcp_client.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class TcpClient < TcpConnection 4 | 5 | class << self 6 | attr_reader :server_class 7 | end 8 | 9 | attr_reader :connected 10 | attr_reader :pending_client_transactions 11 | 12 | def initialize ip, port 13 | # NOTE: The parent class implementing "initialize" method is Connection, and allows no arguments. 14 | # If we call just "super" from here it will fail since "ip" and "port" will be passed as 15 | # arguments. So we must use "super()" and we are done (no arguments are passed to parent). 16 | super() 17 | 18 | @remote_ip = ip 19 | @remote_port = port 20 | @connection_id = ::OverSIP::SIP::TransportManager.add_connection self, self.class, self.class.ip_type, @remote_ip, @remote_port 21 | @connected = false 22 | @pending_client_transactions = [] 23 | 24 | ### TODO: make it configurable. 25 | set_pending_connect_timeout 2.0 26 | end 27 | 28 | 29 | def connection_completed 30 | log_system_info "TCP connection opened to " << remote_desc 31 | 32 | @connected = true 33 | @pending_client_transactions.clear 34 | end 35 | 36 | 37 | def remote_desc 38 | @remote_desc ||= case self.class.ip_type 39 | when :ipv4 ; "#{@remote_ip}:#{@remote_port.to_s}" 40 | when :ipv6 ; "[#{@remote_ip}]:#{@remote_port.to_s}" 41 | end 42 | end 43 | 44 | 45 | def unbind cause=nil 46 | @state = :ignore 47 | 48 | # Remove the connection. 49 | self.class.connections.delete @connection_id 50 | 51 | @local_closed = true if cause == ::Errno::ETIMEDOUT 52 | 53 | if @connected 54 | log_msg = "connection to #{remote_desc} " 55 | log_msg << ( @local_closed ? "locally closed" : "remotely closed" ) 56 | log_msg << " (cause: #{cause.inspect})" if cause 57 | log_system_debug log_msg if $oversip_debug 58 | 59 | # If the TCP client connection has failed (remote server rejected the connection) then 60 | # inform to all the pending client transactions. 61 | else 62 | log_system_notice "connection to #{remote_desc} failed" if $oversip_debug 63 | 64 | @pending_client_transactions.each do |client_transaction| 65 | client_transaction.connection_failed 66 | end 67 | @pending_client_transactions.clear 68 | end unless $! 69 | 70 | @connected = false 71 | end 72 | 73 | 74 | # For the case in which OverSIP receives a SIP request from a connection open by OverSIP. 75 | def record_route 76 | @record_route and return @record_route 77 | 78 | server_class = self.class.server_class 79 | local_port, local_ip = ::Socket.unpack_sockaddr_in(get_sockname) 80 | 81 | case 82 | when server_class == ::OverSIP::SIP::IPv4TcpServer 83 | uri_ip = local_ip 84 | when server_class == ::OverSIP::SIP::IPv6TcpServer 85 | uri_ip = "[#{local_ip}]" 86 | when server_class == ::OverSIP::SIP::IPv4TlsServer 87 | uri_ip = local_ip 88 | when server_class == ::OverSIP::SIP::IPv6TlsServer 89 | uri_ip = "[#{local_ip}]" 90 | end 91 | 92 | @record_route = "" 93 | end 94 | 95 | end 96 | 97 | end 98 | -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/tcp_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class TcpServer < TcpConnection 4 | 5 | attr_reader :outbound_flow_token 6 | 7 | def post_connection 8 | begin 9 | @remote_port, @remote_ip = ::Socket.unpack_sockaddr_in(get_peername) 10 | rescue => e 11 | log_system_error "error obtaining remote IP/port (#{e.class}: #{e.message}), closing connection" 12 | close_connection 13 | @state = :ignore 14 | return 15 | end 16 | 17 | @connection_id = ::OverSIP::SIP::TransportManager.add_connection self, self.class, self.class.ip_type, @remote_ip, @remote_port 18 | 19 | # Create an Outbound (RFC 5626) flow token for this connection. 20 | @outbound_flow_token = ::OverSIP::SIP::TransportManager.add_outbound_connection self 21 | 22 | ### Testing TCP keepalive. 23 | # https://github.com/bklang/eventmachine/commit/74c65a56c733bc1b6f092e32a9f0d722501ade46 24 | # http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/ 25 | if ::OverSIP::SIP.tcp_keepalive_interval 26 | set_sock_opt Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true 27 | set_sock_opt Socket::SOL_TCP, Socket::TCP_KEEPIDLE, ::OverSIP::SIP.tcp_keepalive_interval # First TCP ping. 28 | set_sock_opt Socket::SOL_TCP, Socket::TCP_KEEPINTVL, ::OverSIP::SIP.tcp_keepalive_interval # Interval between TCP pings. 29 | end 30 | 31 | log_system_debug("connection opened from " << remote_desc) if $oversip_debug 32 | end 33 | 34 | def remote_desc force=nil 35 | if force 36 | @remote_desc = case @remote_ip_type 37 | when :ipv4 ; "#{@remote_ip}:#{@remote_port.to_s}" 38 | when :ipv6 ; "[#{@remote_ip}]:#{@remote_port.to_s}" 39 | end 40 | else 41 | @remote_desc ||= case self.class.ip_type 42 | when :ipv4 ; "#{@remote_ip}:#{@remote_port.to_s}" 43 | when :ipv6 ; "[#{@remote_ip}]:#{@remote_port.to_s}" 44 | end 45 | end 46 | end 47 | 48 | 49 | def unbind cause=nil 50 | @state = :ignore 51 | 52 | # Remove the connection. 53 | self.class.connections.delete @connection_id 54 | 55 | # Remove the Outbound token flow. 56 | ::OverSIP::SIP::TransportManager.delete_outbound_connection @outbound_flow_token 57 | 58 | @local_closed = true if cause == ::Errno::ETIMEDOUT 59 | 60 | if $oversip_debug 61 | log_msg = "connection from #{remote_desc} " 62 | log_msg << ( @local_closed ? "locally closed" : "remotely closed" ) 63 | log_msg << " (cause: #{cause.inspect})" if cause 64 | log_system_debug log_msg 65 | end unless $! 66 | end 67 | 68 | end 69 | 70 | end 71 | 72 | -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/tls_client.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class TlsClient < TcpClient 4 | 5 | TLS_HANDSHAKE_MAX_TIME = 4 6 | 7 | 8 | attr_writer :callback_on_server_tls_handshake 9 | 10 | 11 | def initialize ip, port 12 | super 13 | @pending_messages = [] 14 | end 15 | 16 | 17 | def connection_completed 18 | @server_pems = [] 19 | @server_last_pem = false 20 | 21 | start_tls({ 22 | :verify_peer => @callback_on_server_tls_handshake, 23 | :cert_chain_file => ::OverSIP.tls_public_cert, 24 | :private_key_file => ::OverSIP.tls_private_cert, 25 | :ssl_version => %w(tlsv1 tlsv1_1 tlsv1_2) 26 | }) 27 | 28 | # If the remote server does never send us a TLS certificate 29 | # after the TCP connection we would leak by storing more and 30 | # more messages in @pending_messages array. 31 | @timer_tls_handshake = ::EM::Timer.new(TLS_HANDSHAKE_MAX_TIME) do 32 | unless @connected 33 | log_system_notice "TLS handshake not performed within #{TLS_HANDSHAKE_MAX_TIME} seconds, closing the connection" 34 | close_connection 35 | end 36 | end 37 | end 38 | 39 | 40 | # Called for every certificate provided by the peer. 41 | # This is just called in case @callback_on_server_tls_handshake is true. 42 | def ssl_verify_peer pem 43 | # TODO: Dirty workaround for bug https://github.com/eventmachine/eventmachine/issues/194. 44 | return true if @server_last_pem == pem 45 | 46 | @server_last_pem = pem 47 | @server_pems << pem 48 | 49 | log_system_debug "received certificate num #{@server_pems.size} from server" if $oversip_debug 50 | 51 | # Validation must be done in ssl_handshake_completed after receiving all the certs, so return true. 52 | return true 53 | end 54 | 55 | 56 | # This is called after all the calls to ssl_verify_peer(). 57 | def ssl_handshake_completed 58 | log_system_debug("TLS connection established to " << remote_desc) if $oversip_debug 59 | 60 | # @connected in TlsClient means "TLS connection" rather than 61 | # just "TCP connection". 62 | @connected = true 63 | @timer_tls_handshake.cancel if @timer_tls_handshake 64 | 65 | # Run OverSIP::SipEvents.on_server_tls_handshake. 66 | ::Fiber.new do 67 | if @callback_on_server_tls_handshake 68 | log_system_debug "running OverSIP::SipEvents.on_server_tls_handshake()..." if $oversip_debug 69 | begin 70 | ::OverSIP::SipEvents.on_server_tls_handshake self, @server_pems 71 | rescue ::Exception => e 72 | log_system_error "error calling OverSIP::SipEvents.on_server_tls_handshake():" 73 | log_system_error e 74 | close_connection 75 | end 76 | 77 | # If the user or peer has closed the connection in the on_server_tls_handshake() callback 78 | # then notify pending transactions. 79 | if @local_closed or error? 80 | log_system_debug "connection closed, aborting" if $oversip_debug 81 | @pending_client_transactions.each do |client_transaction| 82 | client_transaction.tls_validation_failed 83 | end 84 | @pending_client_transactions.clear 85 | @pending_messages.clear 86 | @state = :ignore 87 | end 88 | end 89 | 90 | @pending_client_transactions.clear 91 | @pending_messages.each do |msg| 92 | send_data msg 93 | end 94 | @pending_messages.clear 95 | end.resume 96 | end 97 | 98 | def unbind cause=nil 99 | super 100 | @timer_tls_handshake.cancel if @timer_tls_handshake 101 | @pending_messages.clear 102 | end 103 | 104 | # In TLS client, we must wait until ssl_handshake_completed is 105 | # completed before sending data. If not, data will be sent in 106 | # plain TCP. 107 | # http://dev.sipdoc.net/issues/457 108 | def send_sip_msg msg, ip=nil, port=nil 109 | if self.error? 110 | log_system_notice "SIP message could not be sent, connection is closed" 111 | return false 112 | end 113 | 114 | if @connected 115 | send_data msg 116 | else 117 | log_system_debug "TLS handshake not completed yet, waiting before sending the message" if $oversip_debug 118 | @pending_messages << msg 119 | end 120 | true 121 | end 122 | 123 | end 124 | 125 | end 126 | -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/tls_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class TlsServer < TcpServer 4 | 5 | TLS_HANDSHAKE_MAX_TIME = 4 6 | 7 | 8 | def post_init 9 | @client_pems = [] 10 | @client_last_pem = false 11 | 12 | start_tls({ 13 | :verify_peer => true, 14 | :cert_chain_file => ::OverSIP.tls_public_cert, 15 | :private_key_file => ::OverSIP.tls_private_cert, 16 | :ssl_version => %w(tlsv1 tlsv1_1 tlsv1_2) 17 | }) 18 | 19 | # If the remote client does never send us a TLS certificate 20 | # after the TCP connection we would leak by storing more and 21 | # more messages in @pending_messages array. 22 | @timer_tls_handshake = ::EM::Timer.new(TLS_HANDSHAKE_MAX_TIME) do 23 | unless @connected 24 | log_system_notice "TLS handshake not performed within #{TLS_HANDSHAKE_MAX_TIME} seconds, closing the connection" 25 | close_connection 26 | end 27 | end 28 | end 29 | 30 | 31 | def ssl_verify_peer pem 32 | # TODO: Dirty workaround for bug https://github.com/eventmachine/eventmachine/issues/194. 33 | return true if @client_last_pem == pem 34 | 35 | @client_last_pem = pem 36 | @client_pems << pem 37 | 38 | log_system_debug "received certificate num #{@client_pems.size} from client" if $oversip_debug 39 | 40 | # Validation must be done in ssl_handshake_completed after receiving all the certs, so return true. 41 | return true 42 | end 43 | 44 | 45 | def ssl_handshake_completed 46 | log_system_info "TLS connection established from " << remote_desc 47 | 48 | # @connected in TlsServer means "TLS connection" rather than 49 | # just "TCP connection". 50 | @connected = true 51 | @timer_tls_handshake.cancel if @timer_tls_handshake 52 | 53 | if ::OverSIP::SIP.callback_on_client_tls_handshake 54 | # Set the state to :waiting_for_on_client_tls_handshake so data received after TLS handshake but before 55 | # user callback validation is just stored. 56 | @state = :waiting_for_on_client_tls_handshake 57 | 58 | # Run OverSIP::SipEvents.on_client_tls_handshake. 59 | ::Fiber.new do 60 | begin 61 | log_system_debug "running OverSIP::SipEvents.on_client_tls_handshake()..." if $oversip_debug 62 | ::OverSIP::SipEvents.on_client_tls_handshake self, @client_pems 63 | # If the user of the peer has not closed the connection then continue. 64 | unless @local_closed or error? 65 | @state = :init 66 | # Call process_received_data() to process possible data received in the meanwhile. 67 | process_received_data 68 | else 69 | log_system_debug "connection closed, aborting" if $oversip_debug 70 | end 71 | 72 | rescue ::Exception => e 73 | log_system_error "error calling OverSIP::SipEvents.on_client_tls_handshake():" 74 | log_system_error e 75 | close_connection 76 | end 77 | end.resume 78 | end 79 | end 80 | 81 | 82 | def unbind cause=nil 83 | @timer_tls_handshake.cancel if @timer_tls_handshake 84 | super 85 | end 86 | 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/tls_tunnel_connection.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class TlsTunnelConnection < TcpConnection 4 | 5 | # Max size (bytes) of the buffered data when receiving message headers 6 | # (avoid DoS attacks). 7 | HEADERS_MAX_SIZE = 16384 8 | 9 | def process_received_data 10 | @state == :ignore and return 11 | 12 | while (case @state 13 | when :init 14 | @parser.reset 15 | @parser_nbytes = 0 16 | # If it's a TCP connection from the TLS tunnel then parse the HAProxy Protocol line 17 | # if it's not yet done. 18 | unless @haproxy_protocol_parsed 19 | @state = :haproxy_protocol 20 | else 21 | @state = :headers 22 | end 23 | 24 | when :haproxy_protocol 25 | parse_haproxy_protocol 26 | 27 | when :headers 28 | parse_headers 29 | 30 | when :body 31 | get_body 32 | 33 | when :finished 34 | if @msg.request? 35 | process_request 36 | else 37 | process_response 38 | end 39 | 40 | # Set state to :init. 41 | @state = :init 42 | # Return true to continue processing possible remaining data. 43 | true 44 | 45 | when :ignore 46 | false 47 | end) 48 | end # while 49 | 50 | end 51 | 52 | def parse_haproxy_protocol 53 | if (haproxy_protocol_data = ::OverSIP::Utils.parse_haproxy_protocol(@buffer.to_str)) 54 | @haproxy_protocol_parsed = true 55 | 56 | # Update connection information. 57 | @remote_ip_type = haproxy_protocol_data[1] 58 | @remote_ip = haproxy_protocol_data[2] 59 | @remote_port = haproxy_protocol_data[3] 60 | 61 | # Add the connection with the client's source data. Note that we pass a TlsServer as class, but 62 | # the server instance is a TcpServer. 63 | @connection_id = case @remote_ip_type 64 | when :ipv4 65 | ::OverSIP::SIP::TransportManager.add_connection self, ::OverSIP::SIP::IPv4TlsServer, :ipv4, @remote_ip, @remote_port 66 | when :ipv6 67 | ::OverSIP::SIP::TransportManager.add_connection self, ::OverSIP::SIP::IPv6TlsServer, :ipv6, @remote_ip, @remote_port 68 | end 69 | 70 | # Update log information. 71 | remote_desc true 72 | 73 | # Remove the HAProxy Protocol line from the received data. 74 | @buffer.read haproxy_protocol_data[0] 75 | 76 | @state = :headers 77 | 78 | else 79 | log_system_error "HAProxy Protocol parsing error, closing connection" 80 | close_connection_after_writing 81 | @state = :ignore 82 | return false 83 | end 84 | end 85 | 86 | end 87 | 88 | end 89 | 90 | -------------------------------------------------------------------------------- /lib/oversip/sip/listeners/tls_tunnel_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class TlsTunnelServer < TlsTunnelConnection 4 | 5 | attr_reader :outbound_flow_token 6 | 7 | def post_connection 8 | begin 9 | # Temporal @remote_ip and @remote_port until the HAProxy protocol line is parsed. 10 | @remote_port, @remote_ip = ::Socket.unpack_sockaddr_in(get_peername) 11 | rescue => e 12 | log_system_error "error obtaining remote IP/port (#{e.class}: #{e.message}), closing connection" 13 | close_connection 14 | @state = :ignore 15 | return 16 | end 17 | 18 | # Create an Outbound (RFC 5626) flow token for this connection. 19 | @outbound_flow_token = ::OverSIP::SIP::TransportManager.add_outbound_connection self 20 | 21 | log_system_debug ("connection from the TLS tunnel " << remote_desc) if $oversip_debug 22 | end 23 | 24 | def remote_desc force=nil 25 | if force 26 | @remote_desc = case @remote_ip_type 27 | when :ipv4 ; "#{@remote_ip}:#{@remote_port.to_s}" 28 | when :ipv6 ; "[#{@remote_ip}]:#{@remote_port.to_s}" 29 | end 30 | else 31 | @remote_desc ||= case self.class.ip_type 32 | when :ipv4 ; "#{@remote_ip}:#{@remote_port.to_s}" 33 | when :ipv6 ; "[#{@remote_ip}]:#{@remote_port.to_s}" 34 | end 35 | end 36 | end 37 | 38 | 39 | def unbind cause=nil 40 | @state = :ignore 41 | 42 | # Remove the connection. 43 | self.class.connections.delete @connection_id if @connection_id 44 | 45 | # Remove the Outbound token flow. 46 | ::OverSIP::SIP::TransportManager.delete_outbound_connection @outbound_flow_token 47 | 48 | @local_closed = true if cause == ::Errno::ETIMEDOUT 49 | 50 | if $oversip_debug 51 | log_msg = "connection from the TLS tunnel #{remote_desc} " 52 | log_msg << ( @local_closed ? "locally closed" : "remotely closed" ) 53 | log_msg << " (cause: #{cause.inspect})" if cause 54 | log_system_debug log_msg 55 | end unless $! 56 | end 57 | 58 | end 59 | 60 | end 61 | 62 | -------------------------------------------------------------------------------- /lib/oversip/sip/name_addr.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class NameAddr < OverSIP::SIP::Uri 4 | 5 | attr_reader :display_name 6 | 7 | 8 | def self.parse value 9 | name_addr = ::OverSIP::SIP::MessageParser.parse_uri value, true 10 | raise ::OverSIP::ParsingError, "invalid NameAddr #{value.inspect}" unless name_addr.is_a? (::OverSIP::SIP::NameAddr) 11 | name_addr 12 | end 13 | 14 | 15 | def initialize display_name=nil, scheme=:sip, user=nil, host=nil, port=nil 16 | @display_name = display_name 17 | @scheme = scheme.to_sym 18 | @user = user 19 | @host = host 20 | @host_type = ::OverSIP::Utils.ip_type(host) || :domain if host 21 | @port = port 22 | 23 | @name_addr_modified = true 24 | @uri_modified = true 25 | end 26 | 27 | def display_name= value 28 | @display_name = value 29 | @name_addr_modified = true 30 | end 31 | 32 | def to_s 33 | return @name_addr if @name_addr and not @name_addr_modified and not @uri_modified 34 | 35 | @name_addr = "" 36 | ( @name_addr << '"' << @display_name << '" ' ) if @display_name 37 | @name_addr << "<" << uri << ">" 38 | 39 | @name_addr_modified = false 40 | @name_addr 41 | 42 | end 43 | alias :inspect :to_s 44 | 45 | def modified? 46 | @uri_modified or @name_addr_modified 47 | end 48 | 49 | end 50 | 51 | end -------------------------------------------------------------------------------- /lib/oversip/sip/request.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class Request < Message 4 | 5 | include ::OverSIP::SIP::Core 6 | 7 | SECURE_TRANSPORTS = { :tls=>true, :wss=>true } 8 | 9 | attr_accessor :server_transaction 10 | attr_reader :ruri 11 | attr_reader :new_max_forwards 12 | attr_accessor :antiloop_id 13 | attr_accessor :route_outbound_flow_token 14 | attr_writer :outgoing_outbound_requested, :incoming_outbound_requested 15 | attr_accessor :proxied # If true it means that this request has been already proxied. 16 | attr_reader :from_was_modified, :to_was_modified # Set to true if the From / To has been modified prior to routing the request. 17 | 18 | # Used for internal purposes when doing proxy and adding the first Record-Route 19 | # or Path. 20 | attr_accessor :in_rr 21 | 22 | attr_accessor :cvars # Connection variables (a hash). 23 | 24 | 25 | def log_id 26 | @log_id ||= "SIP Request #{@via_branch_id}" 27 | end 28 | 29 | def request? ; true end 30 | def response? ; false end 31 | 32 | def initial? ; ! @to_tag end 33 | def in_dialog? ; @to_tag end 34 | 35 | def secure? 36 | SECURE_TRANSPORTS[@transport] || false 37 | end 38 | 39 | 40 | def reply status_code, reason_phrase=nil, extra_headers=[], body=nil 41 | if @sip_method == :ACK 42 | log_system_error "attemtp to reply to an ACK aborted" 43 | return false 44 | end 45 | return false unless @server_transaction.receive_response(status_code) if @server_transaction 46 | 47 | reason_phrase ||= REASON_PHRASE[status_code] || REASON_PHRASE_NOT_SET 48 | 49 | if status_code > 100 50 | @internal_to_tag ||= @to_tag || ( @server_transaction ? ::SecureRandom.hex(6) : ::OverSIP::SIP::Tags.totag_for_sl_reply ) 51 | end 52 | 53 | response = "SIP/2.0 #{status_code} #{reason_phrase}\r\n" 54 | 55 | @hdr_via.each do |hdr| 56 | response << "Via: " << hdr << CRLF 57 | end 58 | 59 | response << "From: " << @hdr_from << CRLF 60 | 61 | response << "To: " << @hdr_to 62 | response << ";tag=#{@internal_to_tag}" if @internal_to_tag 63 | response << CRLF 64 | 65 | response << "Call-ID: " << @call_id << CRLF 66 | response << "CSeq: " << @cseq.to_s << " " << @sip_method.to_s << CRLF 67 | response << "Content-Length: #{body ? body.bytesize : "0"}\r\n" 68 | 69 | extra_headers.each do |header| 70 | response << header.to_s << CRLF 71 | end if extra_headers 72 | 73 | response << HDR_SERVER << CRLF 74 | response << CRLF 75 | 76 | response << body if body 77 | 78 | @server_transaction.last_response = response if @server_transaction 79 | 80 | log_system_debug "replying #{status_code} \"#{reason_phrase}\"" if $oversip_debug 81 | 82 | send_response(response) 83 | true 84 | end 85 | 86 | 87 | def reply_full response 88 | return false unless @server_transaction.receive_response(response.status_code) if @server_transaction 89 | 90 | # Ensure the response has Content-Length. Add it otherwise. 91 | if response.body 92 | response.headers["Content-Length"] = [ response.body.bytesize.to_s ] 93 | else 94 | response.headers["Content-Length"] = HDR_ARRAY_CONTENT_LENGTH_0 95 | end 96 | 97 | response_leg_a = response.to_s 98 | @server_transaction.last_response = response_leg_a if @server_transaction 99 | 100 | log_system_debug "forwarding response #{response.status_code} \"#{response.reason_phrase}\"" if $oversip_debug 101 | 102 | send_response(response_leg_a) 103 | true 104 | end 105 | 106 | 107 | # RFC 6228 (199 response). 108 | def reply_199 response 109 | # Store the previous internal To-tag (if set). 110 | internal_to_tag = @internal_to_tag 111 | 112 | # Set it with the To-tag of the response for which a 199 must eb generated. 113 | @internal_to_tag = response.to_tag 114 | 115 | # Send the 199 response. 116 | reply 199, "Early Dialog Terminated", [ "Reason: SIP ;cause=#{response.status_code} ;text=\"#{response.reason_phrase}\"" ] 117 | 118 | # Restore the previous internal To-tag. 119 | @internal_to_tag = internal_to_tag 120 | true 121 | end 122 | private :reply_199 123 | 124 | 125 | def ruri= ruri 126 | case ruri 127 | when ::OverSIP::SIP::Uri, ::OverSIP::SIP::NameAddr 128 | @ruri = ruri 129 | when ::String 130 | @ruri = OverSIP::SIP::Uri.parse ruri 131 | else 132 | raise ::OverSIP::RuntimeError, "invalid URI #{ruri.inspect}" 133 | end 134 | end 135 | 136 | 137 | def send_response(response) 138 | unless (case @transport 139 | when :udp 140 | @connection.send_sip_msg response, @source_ip, @via_rport || @via_sent_by_port || 5060 141 | else 142 | @connection.send_sip_msg response 143 | end 144 | ) 145 | log_system_notice "error sending the SIP response" 146 | end 147 | end 148 | 149 | 150 | def to_s 151 | msg = "#{@sip_method.to_s} #{@ruri.uri} SIP/2.0\r\n" 152 | 153 | # Update From/To/Contact headers if modified. 154 | if @from.modified? 155 | @headers["From"] = [ @from.to_s << (@from_tag ? ";tag=#{@from_tag}" : "") ] 156 | @from_was_modified = true 157 | end 158 | if @to.modified? 159 | @headers["To"] = [ @to.to_s << (@to_tag ? ";tag=#{@to_tag}" : "") ] 160 | @to_was_modified = true 161 | end 162 | if @contact and @contact.modified? 163 | @headers["Contact"] = [ @contact.to_s << (@contact_params ? @contact_params : "") ] 164 | end 165 | 166 | @headers.each do |name, values| 167 | values.each do |value| 168 | msg << name << ": #{value}\r\n" 169 | end 170 | end 171 | 172 | msg << CRLF 173 | msg << @body if @body 174 | msg 175 | end 176 | 177 | end # class Request 178 | 179 | end 180 | -------------------------------------------------------------------------------- /lib/oversip/sip/response.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class Response < Message 4 | 5 | attr_accessor :status_code 6 | attr_accessor :reason_phrase 7 | attr_accessor :request # The associated request. 8 | 9 | 10 | def request? ; false end 11 | def response? ; true end 12 | 13 | 14 | def to_s 15 | msg = "SIP/2.0 #{@status_code} #{@reason_phrase}\r\n" 16 | 17 | # Revert changes to From/To headers if modified during the request processing. 18 | @headers["From"] = [ request.hdr_from ] if request.from_was_modified 19 | if request.to_was_modified 20 | hdr_to = @to_tag ? "#{request.hdr_to};tag=#{@to_tag}" : request.hdr_to 21 | @headers["To"] = [ hdr_to ] 22 | end 23 | 24 | @headers.each do |name, values| 25 | values.each do |value| 26 | msg << name << ": #{value}\r\n" 27 | end 28 | end 29 | 30 | msg << CRLF 31 | msg << @body if @body 32 | msg 33 | end 34 | 35 | end # class Response 36 | 37 | end 38 | -------------------------------------------------------------------------------- /lib/oversip/sip/sip.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | def self.module_init 4 | conf = ::OverSIP.configuration 5 | 6 | @local_ipv4 = conf[:sip][:listen_ipv4] 7 | @local_ipv6 = conf[:sip][:listen_ipv6] 8 | 9 | @tcp_keepalive_interval = conf[:sip][:tcp_keepalive_interval] 10 | 11 | @local_aliases = {} 12 | 13 | sip_local_domains = conf[:sip][:local_domains] || [] 14 | sip_local_ips = [] 15 | sip_local_ips << conf[:sip][:listen_ipv4] if conf[:sip][:enable_ipv4] 16 | sip_local_ips << "[#{OverSIP::Utils.normalize_ipv6(conf[:sip][:listen_ipv6])}]" if conf[:sip][:enable_ipv6] 17 | sip_local_ports = [ conf[:sip][:listen_port], conf[:sip][:listen_port_tls] ].compact 18 | sip_local_domains.each do |domain| 19 | @local_aliases[domain] = true 20 | sip_local_ports.each do |port| 21 | @local_aliases["#{domain}:#{port}"] = true 22 | end 23 | end 24 | sip_local_ips.each do |ip| 25 | sip_local_ports.each do |port| 26 | @local_aliases["#{ip}:#{port}"] = true 27 | end 28 | end 29 | sip_local_ips.each do |ip| 30 | @local_aliases[ip] = true if conf[:sip][:listen_port] == 5060 or conf[:sip][:listen_port_tls] == 5061 31 | end 32 | 33 | ws_local_domains = conf[:sip][:local_domains] || [] 34 | ws_local_ips = [] 35 | ws_local_ips << conf[:websocket][:listen_ipv4] if conf[:websocket][:enable_ipv4] 36 | ws_local_ips << "[#{OverSIP::Utils.normalize_ipv6(conf[:websocket][:listen_ipv6])}]" if conf[:websocket][:enable_ipv6] 37 | ws_local_ports = [ conf[:websocket][:listen_port], conf[:websocket][:listen_port_tls] ].compact 38 | ws_local_domains.each do |domain| 39 | @local_aliases[domain] = true 40 | ws_local_ports.each do |port| 41 | @local_aliases["#{domain}:#{port}"] = true 42 | end 43 | end 44 | ws_local_ips.each do |ip| 45 | ws_local_ports.each do |port| 46 | @local_aliases["#{ip}:#{port}"] = true 47 | end 48 | end 49 | ws_local_ips.each do |ip| 50 | @local_aliases[ip] = true if conf[:websocket][:listen_port] == 80 or conf[:websocket][:listen_port_tls] == 443 51 | end 52 | 53 | @callback_on_client_tls_handshake = conf[:sip][:callback_on_client_tls_handshake] 54 | end 55 | 56 | def self.local_aliases 57 | @local_aliases 58 | end 59 | 60 | def self.tcp_keepalive_interval 61 | @tcp_keepalive_interval 62 | end 63 | 64 | def self.local_ipv4 65 | @local_ipv4 66 | end 67 | 68 | def self.local_ipv6 69 | @local_ipv6 70 | end 71 | 72 | def self.callback_on_client_tls_handshake 73 | @callback_on_client_tls_handshake 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /lib/oversip/sip/tags.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | module Tags 4 | 5 | PREFIX_FOR_TOTAG_SL_REPLIED = ::SecureRandom.hex(4) + "." 6 | REGEX_PREFIX_FOR_TOTAG_SL_REPLIED = /^#{PREFIX_FOR_TOTAG_SL_REPLIED}/ 7 | 8 | ROUTE_OVID_VALUE = ::SecureRandom.hex(4) 9 | ROUTE_OVID_VALUE_HASH = ROUTE_OVID_VALUE.hash 10 | 11 | ANTILOOP_CONST = ::SecureRandom.hex(1) 12 | 13 | 14 | def self.totag_for_sl_reply 15 | PREFIX_FOR_TOTAG_SL_REPLIED + ::SecureRandom.hex(4) 16 | end 17 | 18 | def self.check_totag_for_sl_reply totag 19 | return nil unless totag 20 | totag =~ REGEX_PREFIX_FOR_TOTAG_SL_REPLIED 21 | end 22 | 23 | def self.value_for_route_ovid 24 | ROUTE_OVID_VALUE 25 | end 26 | 27 | def self.check_value_for_route_ovid value 28 | return nil unless value 29 | value.hash == ROUTE_OVID_VALUE_HASH 30 | end 31 | 32 | def self.create_antiloop_id request 33 | # It produces a 32 chars string. 34 | ::Digest::MD5.hexdigest "#{ANTILOOP_CONST}#{request.ruri.to_s}#{request.call_id}#{request.routes[0].uri if request.routes}" 35 | end 36 | 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/oversip/sip/timers.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | ### SIP timer values. 4 | # 5 | # RTT Estimate (RFC 3261 17.1.2.1). 6 | T1 = 0.5 7 | # The maximum retransmit interval for non-INVITE requests and INVITE 8 | # responses (RFC 3261 17.1.2.2). 9 | T2 = 4 10 | # Maximum duration a message will remain in the network (RFC 3261 17.1.2.2). 11 | T4 = 5 12 | # INVITE request retransmit interval, for UDP only (RFC 3261 17.1.1.2). 13 | TIMER_A = T1 # initially T1. 14 | # INVITE transaction timeout timer (RFC 3261 17.1.1.2). 15 | TIMER_B = 64*T1 16 | # Proxy INVITE transaction timeout (RFC 3261 16.6 bullet 11). 17 | TIMER_C = 180 # > 3min. 18 | # NOTE: This is a custom timer we use for INVITE server transactions in order to avoid they never end. 19 | TIMER_C2 = TIMER_C + 2 20 | # Wait time for response retransmits (RFC 3261 17.1.1.2). 21 | TIMER_D_UDP = 32 # > 32s for UDP. 22 | TIMER_D_TCP = 0 # 0s for TCP/SCTP. 23 | # Non-INVITE request retransmit interval, UDP only (RFC 3261 17.1.2.2). 24 | TIMER_E = T1 # initially T1 25 | # Non-INVITE transaction timeout timer. 26 | TIMER_F = 64*T1 27 | # INVITE response retransmit interval (RFC 3261 17.2.1). 28 | TIMER_G = T1 # initially T1. 29 | # Wait time for ACK receipt (RFC 3261 17.2.1). 30 | TIMER_H = 64*T1 31 | # Wait time for ACK retransmits (RFC 3261 17.2.1). 32 | TIMER_I_UDP = T4 # T4 for UDP. 33 | TIMER_I_TCP = 0 # 0s for TCP/SCTP. 34 | # Wait time for non-INVITE requests (RFC 3261 17.2.2). 35 | TIMER_J_UDP = 64*T1 # 64*T1 for UDP. 36 | TIMER_J_TCP = 0 # 0s for TCP/SCTP. 37 | # Wait time for response retransmits (RFC 3261 17.1.2.2). 38 | TIMER_K_UDP = T4 # T4 for UDP. 39 | TIMER_K_TCP = 0 # 0s for TCP/SCTP. 40 | # Wait time for accepted INVITE request retransmits (RFC 6026 17.2.1). 41 | TIMER_L = 64*T1 42 | # Wait time for retransmission of 2xx to INVITE or additional 2xx from 43 | # other branches of a forked INVITE (RFC 6026 17.1.1). 44 | TIMER_M = 64*T1 45 | 46 | ### Custom values. 47 | # 48 | # Interval waiting in a non INVITE server transaction before sending 100 49 | # (RFC 4320 - Section 4.1). 50 | INT1 = T2 + 1 51 | # Interval waiting in a non INVITE server transaction before assuming 52 | # timeout (RFC 4320 - Section 4.2). 53 | INT2 = TIMER_F - INT1 54 | 55 | end 56 | -------------------------------------------------------------------------------- /lib/oversip/sip/transport_manager.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | module TransportManager 4 | 5 | extend ::OverSIP::Logger 6 | 7 | @log_id = "TransportManager" 8 | @outbound_connections = {} 9 | 10 | 11 | # Get an existing connection or create a new one (TCP/TLS). 12 | # For UDP it always returns the single UDP reactor instance. 13 | # client_transaction is passed when creating a new clien transaction. In case the 14 | # outgoing connection is a TCP/TLS client connection and it's not connected yet, 15 | # the client transaction is stored in the @pending_client_transactions of the client 16 | # connection. 17 | # This method always returns a connection object, never nil or false. 18 | def self.get_connection klass, ip, port, client_transaction=nil, callback_on_server_tls_handshake=false 19 | # A normal connection (so we arrive here after RFC 3263 procedures). 20 | case klass.transport 21 | 22 | # In UDP there is a single connection (the UDP server unique instance). 23 | when :udp 24 | conn = klass.connections 25 | 26 | # In TCP/TLS first check if there is an existing connection to the given destination. 27 | # If not create a new one. 28 | when :tcp 29 | case klass.ip_type 30 | when :ipv4 31 | conn = klass.connections["#{ip}_#{port}"] || ::EM.oversip_connect_tcp_server(::OverSIP::SIP.local_ipv4, ip, port, ::OverSIP::SIP::IPv4TcpClient, ip, port) 32 | 33 | if conn.is_a? ::OverSIP::SIP::IPv4TcpClient and not conn.connected 34 | conn.pending_client_transactions << client_transaction 35 | end 36 | when :ipv6 37 | conn = klass.connections["#{::OverSIP::Utils.normalize_ipv6 ip}_#{port}"] || ::EM.oversip_connect_tcp_server(::OverSIP::SIP.local_ipv6, ip, port, ::OverSIP::SIP::IPv6TcpClient, ip, port) 38 | 39 | if conn.is_a? ::OverSIP::SIP::IPv6TcpClient and not conn.connected 40 | conn.pending_client_transactions << client_transaction 41 | end 42 | end 43 | 44 | when :tls 45 | case klass.ip_type 46 | when :ipv4 47 | conn = klass.connections["#{ip}_#{port}"] || ::EM.oversip_connect_tcp_server(::OverSIP::SIP.local_ipv4, ip, port, ::OverSIP::SIP::IPv4TlsClient, ip, port) 48 | 49 | if conn.is_a? ::OverSIP::SIP::IPv4TlsClient and not conn.connected 50 | conn.callback_on_server_tls_handshake = callback_on_server_tls_handshake 51 | conn.pending_client_transactions << client_transaction 52 | end 53 | when :ipv6 54 | conn = klass.connections["#{::OverSIP::Utils.normalize_ipv6 ip}_#{port}"] || ::EM.oversip_connect_tcp_server(::OverSIP::SIP.local_ipv6, ip, port, ::OverSIP::SIP::IPv6TlsClient, ip, port) 55 | 56 | if conn.is_a? ::OverSIP::SIP::IPv6TlsClient and not conn.connected 57 | conn.callback_on_server_tls_handshake = callback_on_server_tls_handshake 58 | conn.pending_client_transactions << client_transaction 59 | end 60 | end 61 | end 62 | 63 | # NOTE: Should never happen. 64 | unless conn 65 | ::OverSIP::Launcher.fatal "no connection retrieved from TransportManager.get_connection(), FIXME, it should never occur!!!" 66 | end 67 | 68 | # Return the created/retrieved connection instance. 69 | conn 70 | end 71 | 72 | 73 | def self.add_connection server, server_class, ip_type, ip, port 74 | connection_id = case ip_type 75 | when :ipv4 76 | "#{ip}_#{port}" 77 | when :ipv6 78 | "#{::OverSIP::Utils.normalize_ipv6 ip}_#{port}" 79 | end 80 | 81 | server_class.connections[connection_id] = server 82 | 83 | # Return the connection_id. 84 | connection_id 85 | end 86 | 87 | 88 | # Return a SIP server instance. It could return nil (if the requested connection no longer 89 | # exists) or false (if it's a tampered flow token). 90 | def self.get_outbound_connection flow_token 91 | # If the token flow has been generated for UDP it is "_" followed by the Base64 92 | # encoded representation of "IP_port", so getbyte(0) would return 95. 93 | if flow_token.getbyte(0) == 95 94 | # NOTE: Doing Base64.decode64 automatically removes the leading "_". 95 | # NOTE: Previously when the Outbound flow token was generated, "=" was replaced with "-" so it becomes 96 | # valid for a SIP URI param (needed i.e. for the OutboundMangling module). 97 | ip_type, ip, port = ::OverSIP::Utils.parse_outbound_udp_flow_token(::Base64.decode64 flow_token.gsub(/-/,"=")) 98 | 99 | case ip_type 100 | when :ipv4 101 | return [ ::OverSIP::SIP::IPv4UdpServer.connections, ip, port ] 102 | when :ipv6 103 | return [ ::OverSIP::SIP::IPv6UdpServer.connections, ip, port ] 104 | else 105 | log_system_notice "udp flow token does not contain valid IP and port encoded value" 106 | return false 107 | end 108 | 109 | # If not, the flow token has been generated for a TCP/TLS/WS/WSS connection so let's lookup 110 | # it into the Outbound connection collection and return nil for IP and port. 111 | else 112 | @outbound_connections[flow_token] 113 | end 114 | end 115 | 116 | 117 | def self.add_outbound_connection connection 118 | outbound_flow_token = ::SecureRandom.hex(5) 119 | @outbound_connections[outbound_flow_token] = connection 120 | outbound_flow_token 121 | end 122 | 123 | 124 | def self.delete_outbound_connection outbound_flow_token 125 | @outbound_connections.delete outbound_flow_token 126 | end 127 | 128 | end # module TransportManager 129 | 130 | end 131 | -------------------------------------------------------------------------------- /lib/oversip/sip/uac.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class Uac < Client 4 | 5 | def route request, dst_host=nil, dst_port=nil, dst_transport=nil 6 | unless (@request = request).is_a? ::OverSIP::SIP::UacRequest or @request.is_a? ::OverSIP::SIP::Request 7 | raise ::OverSIP::RuntimeError, "request must be a OverSIP::SIP::UacRequest or OverSIP::SIP::Request instance" 8 | end 9 | 10 | # The destination of the request is taken from: 11 | # - dst_xxx fields if given. 12 | # - The request.ruri (which is an OverSIP::SIP::Uri or OverSIP::SIP::NameAddr). 13 | # Otherwise raise an exception. 14 | 15 | @log_id = "UAC (proxy #{@conf[:name]})" 16 | 17 | # Force the destination. 18 | if dst_host 19 | dst_scheme = :sip 20 | dst_host_type = ::OverSIP::Utils.ip_type(dst_host) || :domain 21 | 22 | # Or use the Request URI. 23 | else 24 | dst_scheme = request.ruri.scheme 25 | dst_host = request.ruri.host 26 | dst_host_type = request.ruri.host_type 27 | dst_port = request.ruri.port 28 | dst_transport = request.ruri.transport_param 29 | end 30 | 31 | # If the destination uri_host is an IPv6 reference, convert it to real IPv6. 32 | if dst_host_type == :ipv6_reference 33 | dst_host = ::OverSIP::Utils.normalize_ipv6(dst_host, true) 34 | dst_host_type = :ipv6 35 | end 36 | 37 | # Loockup in the DNS cache of this proxy. 38 | result = check_dns_cache dst_scheme, dst_host, dst_host_type, dst_port, dst_transport 39 | 40 | case result 41 | when true 42 | return 43 | else # It can be String or nil, so use it as dns_cache_key param. 44 | # Perform RFC 3263 procedures. 45 | do_dns result, @request.via_branch_id, dst_scheme, dst_host, dst_host_type, dst_port, dst_transport 46 | end 47 | 48 | end # def route 49 | 50 | 51 | def receive_response response 52 | log_system_debug "received response #{response.status_code}" if $oversip_debug 53 | 54 | if response.status_code < 200 55 | run_on_provisional_response_cbs response 56 | elsif response.status_code >= 200 && response.status_code <= 299 57 | run_on_success_response_cbs response 58 | elsif response.status_code >= 300 59 | if response.status_code == 503 60 | if @conf[:dns_failover_on_503] 61 | try_next_target nil, nil, response 62 | return 63 | else 64 | run_on_failure_response_cbs response 65 | end 66 | else 67 | run_on_failure_response_cbs response 68 | end 69 | end 70 | end 71 | 72 | 73 | 74 | private 75 | 76 | 77 | def no_more_targets status, reason, full_response, code 78 | # If we have received a [3456]XX response from downstream then run @on_failure_response_cbs. 79 | if full_response 80 | run_on_failure_response_cbs full_response 81 | # If not, generate the response according to the given status and reason. 82 | else 83 | run_on_error_cbs status, reason, code 84 | end 85 | end 86 | 87 | end # class Uac 88 | 89 | end -------------------------------------------------------------------------------- /lib/oversip/sip/uac_request.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class UacRequest 4 | 5 | DEFAULT_MAX_FORWARDS = "20" 6 | DEFAULT_FROM = "\"OverSIP #{::OverSIP::VERSION}\" " 7 | 8 | attr_reader :sip_method, :ruri, :from, :from_tag, :to, :body, :call_id, :cseq 9 | attr_reader :antiloop_id, :via_branch_id 10 | attr_reader :routes # Always nil (needed for OverSIP::SIP::Tags.create_antiloop_id(). 11 | attr_accessor :tvars # Transaction variables (a hash). 12 | 13 | 14 | def initialize data, extra_headers=[], body=nil 15 | unless (@sip_method = data[:sip_method]) 16 | raise ::OverSIP::RuntimeError, "no data[:sip_method] given" 17 | end 18 | unless (ruri = data[:ruri]) 19 | raise ::OverSIP::RuntimeError, "no data[:ruri] given" 20 | end 21 | 22 | case ruri 23 | when ::OverSIP::SIP::Uri, ::OverSIP::SIP::NameAddr 24 | @ruri = ruri 25 | when ::String 26 | @ruri = OverSIP::SIP::Uri.parse ruri 27 | else 28 | raise ::OverSIP::RuntimeError, "invalid URI #{ruri.inspect}" 29 | end 30 | 31 | @from = data[:from] || DEFAULT_FROM 32 | @from_tag = data[:from_tag] || ::SecureRandom.hex(4) 33 | @to = data[:to] || @ruri 34 | @call_id = data[:call_id] || ::SecureRandom.hex(8) 35 | @cseq = data[:cseq] || rand(1000) 36 | @max_forwards = data[:max_forwards] || DEFAULT_MAX_FORWARDS 37 | 38 | @headers = {} 39 | @extra_headers = extra_headers 40 | 41 | @body = body 42 | 43 | @antiloop_id = ::OverSIP::SIP::Tags.create_antiloop_id(self) 44 | @via_branch_id = ::SecureRandom.hex(4) 45 | end 46 | 47 | 48 | def insert_header name, value 49 | @headers[name] = value.to_s 50 | end 51 | 52 | 53 | def delete_header_top name 54 | @headers.delete name 55 | end 56 | 57 | 58 | def to_s 59 | msg = "#{@sip_method.to_s} #{@ruri.uri} SIP/2.0\r\n" 60 | 61 | @headers.each do |name, value| 62 | msg << name << ": #{value}\r\n" 63 | end 64 | 65 | msg << "From: #{@from.to_s};tag=#{@from_tag}\r\n" 66 | msg << "To: #{@to.to_s}\r\n" 67 | msg << "Call-ID: #{@call_id}\r\n" 68 | msg << "CSeq: #{@cseq.to_s} #{@sip_method.to_s}\r\n" 69 | msg << "Content-Length: #{@body ? @body.bytesize : "0"}\r\n" 70 | msg << "Max-Forwards: #{@max_forwards.to_s}\r\n" 71 | msg << HDR_USER_AGENT << CRLF 72 | 73 | @extra_headers.each do |header| 74 | msg << header << CRLF 75 | end 76 | 77 | msg << CRLF 78 | msg << @body if @body 79 | msg 80 | end 81 | 82 | end # class Request 83 | 84 | end 85 | -------------------------------------------------------------------------------- /lib/oversip/sip/uri.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::SIP 2 | 3 | class Uri 4 | attr_reader :scheme, :user, :host, :host_type, :port, :params, :transport_param, :phone_context_param, :ovid_param, :headers 5 | 6 | 7 | def self.parse value 8 | uri = ::OverSIP::SIP::MessageParser.parse_uri value, false 9 | raise ::OverSIP::ParsingError, "invalid URI #{value.inspect}" unless uri.is_a? (::OverSIP::SIP::Uri) 10 | uri 11 | end 12 | 13 | 14 | def initialize scheme=:sip, user=nil, host=nil, port=nil 15 | @scheme = scheme.to_sym 16 | @user = user 17 | @host = host 18 | @host_type = ::OverSIP::Utils.ip_type(host) || :domain if host 19 | @port = port 20 | 21 | @uri_modified = true 22 | end 23 | 24 | def sip? 25 | @scheme == :sip or @scheme == :sips 26 | end 27 | 28 | def tel? 29 | @scheme == :tel 30 | end 31 | 32 | def scheme= value 33 | return nil if unknown_scheme? 34 | @scheme = value 35 | @uri_modified = true 36 | end 37 | 38 | def unknown_scheme? 39 | not @scheme.is_a? Symbol 40 | end 41 | 42 | def user= value 43 | return nil if unknown_scheme? 44 | @user = value 45 | @uri_modified = true 46 | end 47 | alias :number :user 48 | alias :number= :user= 49 | 50 | def host= value 51 | return nil if unknown_scheme? 52 | @host = value 53 | @host_type = ::OverSIP::Utils.ip_type(value) || :domain 54 | @uri_modified = true 55 | end 56 | alias :domain :host 57 | alias :domain= :host= 58 | 59 | def host_type= value 60 | return nil if unknown_scheme? 61 | @host_type = value 62 | end 63 | 64 | def port= value 65 | return nil if unknown_scheme? 66 | @port = value 67 | @uri_modified = true 68 | end 69 | 70 | def params 71 | @params ||= {} 72 | end 73 | 74 | def has_param? k 75 | return nil if unknown_scheme? 76 | params.include? k.to_s.downcase 77 | end 78 | 79 | def get_param k 80 | return nil if unknown_scheme? 81 | params[k.to_s.downcase] 82 | end 83 | 84 | def set_param k, v 85 | return nil if unknown_scheme? 86 | @params ||= {} 87 | @params[k.downcase] = v 88 | @uri_modified = true 89 | end 90 | 91 | def del_param k 92 | return nil if unknown_scheme? 93 | return false unless @params 94 | if @params.include?(k=k.downcase) 95 | @uri_modified = true 96 | return @params.delete(k) 97 | end 98 | false 99 | end 100 | 101 | def clear_params 102 | return nil if unknown_scheme? 103 | return false unless @params 104 | @params.clear 105 | @transport_param = nil 106 | @phone_context_param = nil 107 | @uri_modified = true 108 | true 109 | end 110 | 111 | def transport_param= value 112 | return nil unless @scheme == :sip or @scheme == :sips 113 | if value 114 | @transport_param = value.to_sym 115 | set_param "transport", value.to_s 116 | else 117 | @transport_param = nil 118 | del_param "transport" 119 | end 120 | end 121 | 122 | def phone_context_param= value 123 | return nil unless @scheme == :tel 124 | if value 125 | @phone_context_param = value.to_sym 126 | set_param "phone-context", value.to_s 127 | else 128 | @phone_context_param = nil 129 | del_param "phone-context" 130 | end 131 | end 132 | 133 | def lr_param? 134 | @lr_param ? true : false 135 | end 136 | 137 | def ob_param? 138 | @ob_param ? true : false 139 | end 140 | 141 | def headers= value 142 | return nil if unknown_scheme? 143 | @headers = value 144 | @uri_modified = true 145 | end 146 | 147 | def uri 148 | return @uri unless @uri_modified 149 | 150 | case @scheme 151 | when :sip, :sips 152 | @uri = @scheme.to_s << ":" 153 | ( @uri << ::EscapeUtils.escape_uri(@user) << "@" ) if @user 154 | @uri << @host 155 | ( @uri << ":" << @port.to_s ) if @port 156 | 157 | @params.each do |k,v| 158 | @uri << ";" << k 159 | ( @uri << "=" << v.to_s ) if v 160 | end if @params 161 | 162 | @uri << @headers if @headers 163 | 164 | when :tel 165 | @uri = "tel:" 166 | @uri << @user 167 | 168 | @params.each do |k,v| 169 | @uri << ";" << k 170 | ( @uri << "=" << v.to_s ) if v 171 | end if @params 172 | 173 | end 174 | 175 | @uri_modified = false 176 | @uri 177 | end 178 | alias :to_s :uri 179 | alias :inspect :uri 180 | 181 | # Returns a String with the AoR of the URI: 182 | # - SIP URI: sip:user@domain. 183 | # - TEL URI: tel:number 184 | # - Others: nil 185 | # 186 | def aor 187 | case @scheme 188 | when :sip, :sips 189 | aor = "sip:" 190 | ( aor << ::EscapeUtils.escape_uri(@user) << "@" ) if @user 191 | aor << @host 192 | 193 | when :tel 194 | aor = "tel:" 195 | aor << @user 196 | 197 | end 198 | 199 | aor 200 | end 201 | 202 | def modified? 203 | @uri_modified 204 | end 205 | 206 | end # class Uri 207 | 208 | end -------------------------------------------------------------------------------- /lib/oversip/syslog.rb: -------------------------------------------------------------------------------- 1 | module OverSIP 2 | 3 | module Syslog 4 | 5 | SYSLOG_FACILITY_MAPPING = { 6 | "kern" => ::Syslog::LOG_KERN, 7 | "user" => ::Syslog::LOG_USER, 8 | "daemon" => ::Syslog::LOG_DAEMON, 9 | "local0" => ::Syslog::LOG_LOCAL0, 10 | "local1" => ::Syslog::LOG_LOCAL1, 11 | "local2" => ::Syslog::LOG_LOCAL2, 12 | "local3" => ::Syslog::LOG_LOCAL3, 13 | "local4" => ::Syslog::LOG_LOCAL4, 14 | "local5" => ::Syslog::LOG_LOCAL5, 15 | "local6" => ::Syslog::LOG_LOCAL6, 16 | "local7" => ::Syslog::LOG_LOCAL7 17 | } 18 | 19 | SYSLOG_SEVERITY_MAPPING = { 20 | "debug" => 0, 21 | "info" => 1, 22 | "notice" => 2, 23 | "warn" => 3, 24 | "error" => 4, 25 | "crit" => 5, 26 | "alert" => 6, 27 | "emerg" => 7 28 | } 29 | 30 | def self.log level_value, msg, log_id, user 31 | user = user ? " [user] " : " " 32 | 33 | msg = case msg 34 | when ::String 35 | "<#{log_id}>#{user}#{msg}" 36 | when ::Exception 37 | "<#{log_id}>#{user}#{msg.message} (#{msg.class })\n#{(msg.backtrace || [])[0..3].join("\n")}" 38 | else 39 | "<#{log_id}>#{user}#{msg.inspect}" 40 | end 41 | 42 | msg = msg.gsub(/%/,"%%").gsub(/\x00/,"") 43 | 44 | case level_value 45 | when 0 46 | ::Syslog.debug sprintf("%7s %s", "DEBUG:", msg) 47 | when 1 48 | ::Syslog.info sprintf("%7s %s", "INFO:", msg) 49 | when 2 50 | ::Syslog.notice sprintf("%7s %s", "NOTICE:", msg) 51 | when 3 52 | ::Syslog.warning sprintf("%7s %s", "WARN:", msg) 53 | when 4 54 | ::Syslog.err sprintf("%7s %s", "ERROR:", msg) 55 | when 5 56 | ::Syslog.crit sprintf("%7s %s", "CRIT:", msg) 57 | when 6 58 | ::Syslog.alert sprintf("%7s %s", "ALERT:", msg) 59 | when 7 60 | ::Syslog.emerg sprintf("%7s %s", "EMERG:", msg) 61 | else # Shouldn't occur. 62 | ::Syslog.err sprintf("%7s %s", "UNKNOWN:", msg) 63 | end 64 | end 65 | 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /lib/oversip/system_callbacks.rb: -------------------------------------------------------------------------------- 1 | module OverSIP 2 | 3 | # This module is intended for 3rd party modules that need custom code to be 4 | # executed when OverSIP is started, reloaded or terminated. 5 | # 6 | module SystemCallbacks 7 | 8 | extend ::OverSIP::Logger 9 | 10 | @log_id = "SystemCallbacks" 11 | 12 | class << self 13 | attr_reader :on_started_callbacks 14 | attr_reader :on_terminated_callbacks 15 | attr_reader :on_reload_callbacks 16 | end 17 | 18 | @on_started_callbacks = [] 19 | @on_terminated_callbacks = [] 20 | @on_reload_callbacks = [] 21 | 22 | def self.on_started pr=nil, &bl 23 | block = pr || bl 24 | raise ::ArgumentError, "no block given" unless block.is_a? ::Proc 25 | 26 | @on_started_callbacks << block 27 | end 28 | 29 | def self.on_terminated pr=nil, &bl 30 | block = pr || bl 31 | raise ::ArgumentError, "no block given" unless block.is_a? ::Proc 32 | 33 | @on_terminated_callbacks << block 34 | end 35 | 36 | def self.on_reload pr=nil, &bl 37 | block = pr || bl 38 | raise ::ArgumentError, "no block given" unless block.is_a? ::Proc 39 | 40 | @on_reload_callbacks << block 41 | end 42 | 43 | end 44 | 45 | end -------------------------------------------------------------------------------- /lib/oversip/utils.rb: -------------------------------------------------------------------------------- 1 | module OverSIP 2 | 3 | module Utils 4 | 5 | # It ensures that two identical byte secuences are matched regardless 6 | # they have different encoding. 7 | # For example in Ruby the following returns false: 8 | # "iñaki".force_encoding(::Encoding::BINARY) == "iñaki" 9 | def self.string_compare string1, string2 10 | string1.to_s.force_encoding(::Encoding::BINARY) == string2.to_s.force_encoding(::Encoding::BINARY) 11 | end 12 | 13 | # This avoid "invalid byte sequence in UTF-8" when the directly doing: 14 | # string =~ /EXPRESSION/ 15 | # and string has invalid UTF-8 bytes secuence. 16 | # Also avoids "incompatible encoding regexp match (UTF-8 regexp with ASCII-8BIT string)" 17 | # NOTE: expression argument must be a String or a Regexp. 18 | def self.regexp_compare string, expression 19 | string = string.to_s.force_encoding(::Encoding::BINARY) 20 | if expression.is_a? ::Regexp 21 | expression = /#{expression.source.force_encoding(::Encoding::BINARY)}/ 22 | else 23 | expression = /#{expression.to_s.force_encoding(::Encoding::BINARY)}/ 24 | end 25 | string =~ expression 26 | end 27 | 28 | end 29 | 30 | end -------------------------------------------------------------------------------- /lib/oversip/version.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | module OverSIP 4 | 5 | module Version 6 | MAJOR = 2 7 | MINOR = 0 8 | TINY = 4 9 | DEVEL = nil # Set to nil for stable releases. 10 | end 11 | 12 | PROGRAM_NAME = "OverSIP" 13 | VERSION = [Version::MAJOR, Version::MINOR, Version::TINY].join(".") 14 | VERSION << ".#{Version::DEVEL}" if Version::DEVEL 15 | AUTHOR = "Inaki Baz Castillo" 16 | AUTHOR_EMAIL = "ibc@aliax.net" 17 | HOMEPAGE = "http://oversip.net" 18 | year = "2012-2014" 19 | DESCRIPTION = "#{PROGRAM_NAME} #{VERSION}\n#{HOMEPAGE}\n#{year}, #{AUTHOR} <#{AUTHOR_EMAIL}>" 20 | 21 | end 22 | -------------------------------------------------------------------------------- /lib/oversip/websocket/constants.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | CRLF = "\r\n" 4 | 5 | REASON_PHRASE = { 6 | 100 => "Continue", 7 | 101 => "Switching Protocols", 8 | 200 => "OK", 9 | 201 => "Created", 10 | 202 => "Accepted", 11 | 203 => "Non-Authoritative Information", 12 | 204 => "No Content", 13 | 205 => "Reset Content", 14 | 206 => "Partial Content", 15 | 300 => "Multiple Choices", 16 | 301 => "Moved Permanently", 17 | 302 => "Found", 18 | 303 => "See Other", 19 | 304 => "Not Modified", 20 | 305 => "Use Proxy", 21 | 307 => "Temporary Redirect", 22 | 400 => "Bad Request", 23 | 401 => "Unauthorized", 24 | 402 => "Payment Required", 25 | 403 => "Forbidden", 26 | 404 => "Not Found", 27 | 405 => "Method Not Allowed", 28 | 406 => "Not Acceptable", 29 | 407 => "Proxy Authentication Required", 30 | 408 => "Request Timeout", 31 | 409 => "Conflict", 32 | 410 => "Gone", 33 | 411 => "Length Required", 34 | 412 => "Precondition Failed", 35 | 413 => "Request Entity Too Large", 36 | 414 => "Request-URI Too Long", 37 | 415 => "Unsupported Media Type", 38 | 416 => "Requested Range Not Satisfiable", 39 | 417 => "Expectation Failed", 40 | 426 => "Upgrade Required", # RFC 2817 41 | 500 => "Server Internal Error", 42 | 501 => "Not Implemented", 43 | 502 => "Bad Gateway", 44 | 503 => "Service Unavailable", 45 | 504 => "Gateway Time-out", 46 | 505 => "HTTP Version Not Supported" 47 | } 48 | 49 | REASON_PHRASE_NOT_SET = "Reason Phrase Not Set" 50 | 51 | HDR_SERVER = "Server: #{::OverSIP::PROGRAM_NAME}/#{::OverSIP::VERSION}".freeze 52 | 53 | WS_SIP_PROTOCOL = "sip" 54 | 55 | end 56 | -------------------------------------------------------------------------------- /lib/oversip/websocket/http_request.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class HttpRequest < ::Hash 4 | 5 | include ::OverSIP::Logger 6 | 7 | attr_accessor :connection 8 | 9 | # HTTP request attributes. 10 | attr_reader :http_method 11 | attr_reader :http_version 12 | attr_reader :uri_scheme 13 | attr_reader :uri 14 | attr_reader :uri_path 15 | attr_reader :uri_query 16 | attr_reader :uri_fragment 17 | attr_reader :host 18 | attr_reader :port 19 | attr_reader :content_length 20 | attr_reader :hdr_connection 21 | attr_reader :hdr_upgrade 22 | attr_reader :hdr_origin 23 | attr_reader :hdr_sec_websocket_version 24 | attr_reader :hdr_sec_websocket_key 25 | attr_reader :hdr_sec_websocket_protocol 26 | 27 | 28 | LOG_ID = "HTTP WS Request" 29 | def log_id 30 | LOG_ID 31 | end 32 | 33 | def unknown_method? ; @is_unknown_method end 34 | 35 | 36 | def reply status_code, reason_phrase=nil, extra_headers={} 37 | reason_phrase ||= REASON_PHRASE[status_code] || REASON_PHRASE_NOT_SET 38 | extra_headers ||= {} 39 | 40 | response = "#{@http_version} #{status_code} #{reason_phrase}\r\n" 41 | 42 | extra_headers.each {|header| response << header << "\r\n"} 43 | 44 | response << HDR_SERVER << "\r\n\r\n" 45 | 46 | log_system_debug "replying #{status_code} \"#{reason_phrase}\"" if $oversip_debug 47 | 48 | if @connection.error? 49 | log_system_warn "cannot send response, connection is closed" 50 | return false 51 | end 52 | 53 | @connection.send_data response 54 | return true 55 | end 56 | 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners.rb: -------------------------------------------------------------------------------- 1 | # OverSIP files 2 | 3 | require "oversip/websocket/listeners/connection" 4 | require "oversip/websocket/listeners/ws_server" 5 | require "oversip/websocket/listeners/wss_server" 6 | require "oversip/websocket/listeners/wss_tunnel_server" 7 | 8 | require "oversip/websocket/listeners/ipv4_ws_server" 9 | require "oversip/websocket/listeners/ipv6_ws_server" 10 | require "oversip/websocket/listeners/ipv4_wss_server" 11 | require "oversip/websocket/listeners/ipv6_wss_server" 12 | require "oversip/websocket/listeners/ipv4_wss_tunnel_server" 13 | require "oversip/websocket/listeners/ipv6_wss_tunnel_server" 14 | -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners/connection.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class Connection < ::EM::Connection 4 | 5 | include ::OverSIP::Logger 6 | include ::OverSIP::SIP::MessageProcessor 7 | 8 | class << self 9 | attr_accessor :ip_type, :ip, :port, :transport, 10 | :via_core, 11 | :record_route, 12 | :outbound_record_route_fragment, :outbound_path_fragment, 13 | :connections, 14 | :invite_server_transactions, :non_invite_server_transactions, 15 | :invite_client_transactions, :non_invite_client_transactions 16 | 17 | def reliable_transport_listener? 18 | @is_reliable_transport_listener 19 | end 20 | 21 | def outbound_listener? 22 | @is_outbound_listener 23 | end 24 | end 25 | 26 | 27 | attr_reader :cvars 28 | 29 | def initialize 30 | @buffer = ::IO::Buffer.new 31 | @state = :init 32 | @cvars = {} 33 | end 34 | 35 | def open? 36 | ! error? 37 | end 38 | 39 | def close status=nil, reason=nil 40 | # When in WebSocket protocol send a close control frame before closing 41 | # the connection. 42 | if @state == :websocket 43 | @ws_framing.send_close_frame status, reason 44 | end 45 | 46 | close_connection_after_writing 47 | @state = :ignore 48 | end 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners/ipv4_ws_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class IPv4WsServer < WsServer 4 | 5 | @ip_type = :ipv4 6 | @transport = :ws 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP WS IPv4 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners/ipv4_wss_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class IPv4WssServer < WssServer 4 | 5 | @ip_type = :ipv4 6 | @transport = :wss 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP WSS IPv4 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners/ipv4_wss_tunnel_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class IPv4WssTunnelServer < WssTunnelServer 4 | 5 | @ip_type = :ipv4 6 | @transport = :wss 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP WSS-Tunnel IPv4 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners/ipv6_ws_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class IPv6WsServer < WsServer 4 | 5 | @ip_type = :ipv6 6 | @transport = :ws 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP WS IPv6 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners/ipv6_wss_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class IPv6WssServer < WssServer 4 | 5 | @ip_type = :ipv6 6 | @transport = :wss 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP WSS IPv6 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners/ipv6_wss_tunnel_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class IPv6WssTunnelServer < WssTunnelServer 4 | 5 | @ip_type = :ipv6 6 | @transport = :wss 7 | @connections = {} 8 | @invite_server_transactions = {} 9 | @non_invite_server_transactions = {} 10 | @invite_client_transactions = {} 11 | @non_invite_client_transactions = {} 12 | @is_reliable_transport_listener = true 13 | @is_outbound_listener = true 14 | 15 | LOG_ID = "SIP WSS-Tunnel IPv6 server" 16 | def log_id 17 | LOG_ID 18 | end 19 | 20 | end 21 | 22 | end -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners/wss_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class WssServer < WsServer 4 | 5 | TLS_HANDSHAKE_MAX_TIME = 4 6 | 7 | 8 | def post_init 9 | @client_pems = [] 10 | @client_last_pem = false 11 | 12 | start_tls({ 13 | :verify_peer => true, 14 | :cert_chain_file => ::OverSIP.tls_public_cert, 15 | :private_key_file => ::OverSIP.tls_private_cert, 16 | :ssl_version => %w(sslv2) # USE SSL instead of TLS. TODO: yes? 17 | }) 18 | 19 | # If the remote client does never send us a TLS certificate 20 | # after the TCP connection we would leak by storing more and 21 | # more messages in @pending_messages array. 22 | @timer_tls_handshake = ::EM::Timer.new(TLS_HANDSHAKE_MAX_TIME) do 23 | unless @connected 24 | log_system_notice "TLS handshake not performed within #{TLS_HANDSHAKE_MAX_TIME} seconds, closing the connection" 25 | close_connection 26 | end 27 | end 28 | end 29 | 30 | 31 | def ssl_verify_peer pem 32 | # TODO: Dirty workaround for bug https://github.com/eventmachine/eventmachine/issues/194. 33 | return true if @client_last_pem == pem 34 | 35 | @client_last_pem = pem 36 | @client_pems << pem 37 | 38 | log_system_debug "received certificate num #{@client_pems.size} from client" if $oversip_debug 39 | 40 | # Validation must be done in ssl_handshake_completed after receiving all the certs, so return true. 41 | return true 42 | end 43 | 44 | 45 | def ssl_handshake_completed 46 | log_system_debug ("TLS connection established from " << remote_desc) if $oversip_debug 47 | 48 | # @connected in WssServer means "TLS connection" rather than 49 | # just "TCP connection". 50 | @connected = true 51 | @timer_tls_handshake.cancel if @timer_tls_handshake 52 | 53 | if ::OverSIP::WebSocket.callback_on_client_tls_handshake 54 | # Set the state to :waiting_for_on_client_tls_handshake so data received after TLS handshake but before 55 | # user callback validation is just stored. 56 | @state = :waiting_for_on_client_tls_handshake 57 | 58 | # Run OverSIP::WebSocketEvents.on_client_tls_handshake. 59 | ::Fiber.new do 60 | begin 61 | log_system_debug "running OverSIP::SipWebSocketEvents.on_client_tls_handshake()..." if $oversip_debug 62 | ::OverSIP::WebSocketEvents.on_client_tls_handshake self, @client_pems 63 | # If the user of the peer has not closed the connection then continue. 64 | unless @local_closed or error? 65 | @state = :init 66 | # Call process_received_data() to process possible data received in the meanwhile. 67 | process_received_data 68 | else 69 | log_system_debug "connection closed during OverSIP::SipWebSocketEvents.on_client_tls_handshake(), aborting" if $oversip_debug 70 | end 71 | 72 | rescue ::Exception => e 73 | log_system_error "error calling OverSIP::WebSocketEvents.on_client_tls_handshake():" 74 | log_system_error e 75 | close_connection 76 | end 77 | end.resume 78 | end 79 | end 80 | 81 | 82 | def unbind cause=nil 83 | @timer_tls_handshake.cancel if @timer_tls_handshake 84 | super 85 | end 86 | 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/oversip/websocket/listeners/wss_tunnel_server.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class WssTunnelServer < WsServer 4 | 5 | def post_connection 6 | begin 7 | # Temporal @remote_ip and @remote_port until the HAProxy protocol line is parsed. 8 | @remote_port, @remote_ip = ::Socket.unpack_sockaddr_in(get_peername) 9 | rescue => e 10 | log_system_error "error obtaining remote IP/port (#{e.class}: #{e.message}), closing connection" 11 | close_connection 12 | @state = :ignore 13 | return 14 | end 15 | 16 | # Create an Outbound (RFC 5626) flow token for this connection. 17 | @outbound_flow_token = ::OverSIP::SIP::TransportManager.add_outbound_connection self 18 | 19 | log_system_debug ("connection from the TLS tunnel " << remote_desc) if $oversip_debug 20 | end 21 | 22 | 23 | def unbind cause=nil 24 | @state = :ignore 25 | 26 | # Remove the connection. 27 | self.class.connections.delete @connection_id if @connection_id 28 | 29 | # Remove the Outbound token flow. 30 | ::OverSIP::SIP::TransportManager.delete_outbound_connection @outbound_flow_token 31 | 32 | @local_closed = true if cause == ::Errno::ETIMEDOUT 33 | @local_closed = false if @client_closed 34 | 35 | if $oversip_debug 36 | log_msg = "connection from the TLS tunnel #{remote_desc} " 37 | log_msg << ( @local_closed ? "locally closed" : "remotely closed" ) 38 | log_msg << " (cause: #{cause.inspect})" if cause 39 | log_system_debug log_msg 40 | end unless $! 41 | 42 | if @ws_established 43 | # Run OverSIP::WebSocketEvents.on_disconnection 44 | ::Fiber.new do 45 | begin 46 | ::OverSIP::WebSocketEvents.on_disconnection self, !@local_closed 47 | rescue ::Exception => e 48 | log_system_error "error calling OverSIP::WebSocketEvents.on_disconnection():" 49 | log_system_error e 50 | end 51 | end.resume 52 | end unless $! 53 | end 54 | 55 | 56 | def process_received_data 57 | @state == :ignore and return 58 | 59 | while (case @state 60 | when :init 61 | @http_parser = ::OverSIP::WebSocket::HttpRequestParser.new 62 | @http_request = ::OverSIP::WebSocket::HttpRequest.new 63 | @http_parser.reset 64 | @http_parser_nbytes = 0 65 | @bytes_remaining = 0 66 | # If it's a TCP connection from the TLS proxy then parse the HAProxy Protocol line 67 | # if it's not yet done. 68 | unless @haproxy_protocol_parsed 69 | @state = :haproxy_protocol 70 | else 71 | @state = :http_headers 72 | end 73 | 74 | when :haproxy_protocol 75 | parse_haproxy_protocol 76 | 77 | when :http_headers 78 | parse_http_headers 79 | 80 | when :check_http_request 81 | check_http_request 82 | 83 | when :on_connection_callback 84 | do_on_connection_callback 85 | false 86 | 87 | when :accept_ws_handshake 88 | accept_ws_handshake 89 | 90 | when :websocket 91 | @ws_established = true 92 | return false if @buffer.size.zero? 93 | @ws_framing.receive_data 94 | false 95 | 96 | when :ignore 97 | false 98 | end) 99 | end # while 100 | 101 | end 102 | 103 | 104 | def parse_haproxy_protocol 105 | if (haproxy_protocol_data = ::OverSIP::Utils.parse_haproxy_protocol(@buffer.to_str)) 106 | @haproxy_protocol_parsed = true 107 | 108 | # Update connection information. 109 | @remote_ip_type = haproxy_protocol_data[1] 110 | @remote_ip = haproxy_protocol_data[2] 111 | @remote_port = haproxy_protocol_data[3] 112 | 113 | # Update log information. 114 | remote_desc true 115 | 116 | # Remove the HAProxy Protocol line from the received data. 117 | @buffer.read haproxy_protocol_data[0] 118 | 119 | @state = :http_headers 120 | 121 | # If parsing fails then the TLS proxy has sent us a wrong HAProxy Protocol line ¿? 122 | else 123 | log_system_error "HAProxy Protocol parsing error, closing connection" 124 | close_connection_after_writing 125 | @state = :ignore 126 | return false 127 | end 128 | end 129 | 130 | end 131 | 132 | end 133 | 134 | -------------------------------------------------------------------------------- /lib/oversip/websocket/websocket.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | def self.module_init 4 | conf = ::OverSIP.configuration 5 | 6 | @callback_on_client_tls_handshake = conf[:websocket][:callback_on_client_tls_handshake] 7 | end 8 | 9 | def self.callback_on_client_tls_handshake 10 | @callback_on_client_tls_handshake 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /lib/oversip/websocket/ws_sip_app.rb: -------------------------------------------------------------------------------- 1 | module OverSIP::WebSocket 2 | 3 | class WsSipApp 4 | 5 | include ::OverSIP::Logger 6 | include ::OverSIP::SIP::MessageProcessor 7 | 8 | def self.class_init 9 | @@max_message_size = ::OverSIP.configuration[:websocket][:max_ws_message_size] 10 | @@ws_keepalive_interval = ::OverSIP.configuration[:websocket][:ws_keepalive_interval] 11 | end 12 | 13 | 14 | LOG_ID = "WsSipApp" 15 | def log_id 16 | LOG_ID 17 | end 18 | 19 | 20 | def initialize connection, ws_framing 21 | @connection = connection 22 | @ws_framing = ws_framing 23 | @ws_message = ::IO::Buffer.new 24 | 25 | # Mantain WebSocket keepalive. 26 | @ws_framing.do_keep_alive @@ws_keepalive_interval if @@ws_keepalive_interval 27 | 28 | # WebSocket is message boundary so we just need a SIP parser instance. 29 | @@parser ||= ::OverSIP::SIP::MessageParser.new 30 | @parser = @@parser 31 | end 32 | 33 | 34 | def receive_payload_data payload_data 35 | # payload_data is always Encoding::BINARY so also @ws_message.to_str. 36 | @ws_message << payload_data 37 | 38 | # Check max message size. 39 | return false if @ws_message.size > @@max_message_size 40 | true 41 | end 42 | 43 | 44 | def message_done type 45 | log_system_debug "received WS message: type=#{type}, length=#{@ws_message.size}" if $oversip_debug 46 | 47 | # Better to encode it as BINARY (to later extract the body). 48 | process_sip_message @ws_message.to_str.force_encoding ::Encoding::BINARY 49 | 50 | @ws_message.clear 51 | true 52 | end 53 | 54 | 55 | def process_sip_message ws_message 56 | # Just a single SIP message allowed per WS message. 57 | @parser.reset 58 | 59 | unless parser_nbytes = @parser.execute(ws_message, 0) 60 | if wrong_message = @parser.parsed 61 | log_system_warn "SIP parsing error for #{MSG_TYPE[wrong_message.class]}: \"#{@parser.error}\"" 62 | else 63 | log_system_warn "SIP parsing error: \"#{@parser.error}\"" 64 | end 65 | @connection.close 4000, "SIP message parsing error" 66 | return 67 | end 68 | 69 | unless @parser.finished? 70 | log_system_warn "SIP parsing error: message not completed" 71 | 72 | @connection.close 4001, "SIP message incomplete" 73 | return 74 | end 75 | 76 | # At this point we've got a SIP::Request, SIP::Response or :outbound_keepalive symbol. 77 | @msg = @parser.parsed 78 | 79 | # Received data is a SIP Outbound keealive (double CRLF). Reply with single CRLF. 80 | if @msg == :outbound_keepalive 81 | log_system_debug "Outbound keepalive received, replying single CRLF" if $oversip_debug 82 | @ws_framing.send_text_frame(CRLF) 83 | return 84 | end 85 | 86 | @parser.post_parsing 87 | 88 | @msg.connection = @connection 89 | @msg.transport = @connection.class.transport 90 | @msg.source_ip = @connection.remote_ip 91 | @msg.source_port = @connection.remote_port 92 | @msg.source_ip_type = @connection.remote_ip_type 93 | 94 | return unless valid_message? @parser 95 | # TODO: Make it configurable: 96 | #add_via_received_rport if @msg.request? 97 | return unless check_via_branch 98 | 99 | # Get the body. 100 | if parser_nbytes != ws_message.bytesize 101 | @msg.body = ws_message[parser_nbytes..-1].force_encoding(::Encoding::UTF_8) 102 | 103 | if @msg.content_length and @msg.content_length != @msg.body.bytesize 104 | log_system_warn "SIP message body size (#{@msg.body.bytesize}) does not match Content-Length (#{@msg.content_length.inspect}), ignoring message" 105 | @connection.close 4002, "SIP message body size does not match Content-Length" 106 | return 107 | end 108 | end 109 | 110 | if @msg.request? 111 | process_request 112 | else 113 | process_response 114 | end 115 | 116 | end 117 | 118 | end 119 | 120 | end 121 | -------------------------------------------------------------------------------- /oversip.gemspec: -------------------------------------------------------------------------------- 1 | require "./lib/oversip/version" 2 | 3 | ::Gem::Specification.new do |spec| 4 | spec.name = "oversip" 5 | spec.version = ::OverSIP::VERSION 6 | spec.licenses = ["MIT"] 7 | spec.date = ::Time.now 8 | spec.authors = [::OverSIP::AUTHOR] 9 | spec.email = [::OverSIP::AUTHOR_EMAIL] 10 | spec.homepage = ::OverSIP::HOMEPAGE 11 | spec.summary = "OverSIP (the SIP framework you dreamed about)" 12 | spec.description = <<-_END_ 13 | OverSIP is an async SIP proxy/server programmable in Ruby language. Some features of OverSIP are: 14 | - SIP transports: UDP, TCP, TLS and WebSocket. 15 | - Full IPv4 and IPv6 support. 16 | - RFC 3263: SIP DNS mechanism (NAPTR, SRV, A, AAAA) for failover and load balancing based on DNS. 17 | - RFC 5626: OverSIP is a perfect Outbound Edge Proxy, including an integrated STUN server. 18 | - Fully programmable in Ruby language (make SIP easy). 19 | - Fast and efficient: OverSIP core is coded in C language. 20 | OverSIP is build on top of EventMachine async library which follows the Reactor Design Pattern, allowing thousands of concurrent connections and requests in a never-blocking fashion. 21 | _END_ 22 | 23 | spec.required_ruby_version = ">= 1.9.3" 24 | spec.add_dependency "eventmachine", "~> 1.2.0", ">= 1.2.0.1" 25 | spec.add_dependency "iobuffer", "= 1.1.2" 26 | spec.add_dependency "em-udns", "= 0.3.6" 27 | spec.add_dependency "escape_utils", "= 1.0.1" 28 | spec.add_dependency "term-ansicolor", "= 1.3.2" 29 | spec.add_dependency "tins", "= 1.6.0" # For term-ansicolor: Last version that supports Ruby 1.9 30 | spec.add_dependency "posix-spawn", "= 0.3.9" 31 | spec.add_dependency "em-synchrony", "= 1.0.3" 32 | spec.add_development_dependency "rake", "~> 10.3", ">= 10.3.2" 33 | 34 | spec.files = ::Dir.glob %w{ 35 | lib/oversip.rb 36 | lib/oversip/*.rb 37 | lib/oversip/ruby_ext/*.rb 38 | 39 | lib/oversip/sip/*.rb 40 | lib/oversip/sip/listeners/*.rb 41 | lib/oversip/sip/grammar/*.rb 42 | 43 | lib/oversip/websocket/*.rb 44 | lib/oversip/websocket/listeners/*.rb 45 | 46 | lib/oversip/modules/*.rb 47 | 48 | ext/common/*.h 49 | 50 | ext/sip_parser/extconf.rb 51 | ext/sip_parser/*.h 52 | ext/sip_parser/*.c 53 | 54 | ext/stun/extconf.rb 55 | ext/stun/*.h 56 | ext/stun/*.c 57 | 58 | ext/utils/extconf.rb 59 | ext/utils/*.h 60 | ext/utils/*.c 61 | 62 | ext/websocket_http_parser/extconf.rb 63 | ext/websocket_http_parser/*.h 64 | ext/websocket_http_parser/*.c 65 | 66 | ext/websocket_framing_utils/extconf.rb 67 | ext/websocket_framing_utils/*.h 68 | ext/websocket_framing_utils/*.c 69 | 70 | ext/stud/extconf.rb 71 | 72 | thirdparty/stud/stud.tar.gz 73 | 74 | etc/* 75 | etc/tls/* 76 | etc/tls/ca/* 77 | etc/tls/utils/* 78 | 79 | Rakefile 80 | README.md 81 | AUTHORS 82 | LICENSE 83 | } 84 | 85 | spec.extensions = %w{ 86 | ext/sip_parser/extconf.rb 87 | ext/stun/extconf.rb 88 | ext/utils/extconf.rb 89 | ext/websocket_http_parser/extconf.rb 90 | ext/websocket_framing_utils/extconf.rb 91 | ext/stud/extconf.rb 92 | } 93 | 94 | spec.executables = ["oversip"] 95 | 96 | spec.test_files = ::Dir.glob %w{ 97 | test/oversip_test_helper.rb 98 | test/test_*.rb 99 | } 100 | 101 | spec.has_rdoc = false 102 | end 103 | -------------------------------------------------------------------------------- /test/oversip_test_helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "oversip" 3 | 4 | 5 | class OverSIPTest < Test::Unit::TestCase 6 | 7 | def assert_true(object, message="") 8 | assert_equal(true, object, message) 9 | end 10 | 11 | def assert_false(object, message="") 12 | assert_equal(false, object, message) 13 | end 14 | 15 | def assert_equal_options(options, element) 16 | assert options.include?(element) 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /test/test_http_parser.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "oversip_test_helper" 4 | 5 | 6 | class TestHttpParser < OverSIPTest 7 | 8 | def parse data 9 | parser = OverSIP::WebSocket::HttpRequestParser.new 10 | buffer = IO::Buffer.new 11 | request = OverSIP::WebSocket::HttpRequest.new 12 | 13 | buffer << data 14 | 15 | unless bytes_parsed = parser.execute(request, buffer.to_str, 0) 16 | raise "ERROR: parsing error: \"#{parser.error}\"" 17 | end 18 | 19 | if parser.finished? 20 | buffer.read bytes_parsed 21 | if request.content_length and ! request.content_length.zero? 22 | request.body = buffer.read request.content_length 23 | end 24 | if buffer.size != 0 25 | raise "ERROR: buffer is not empty after parsing" 26 | end 27 | else 28 | raise "ERROR: parsed NOT finished!" 29 | end 30 | 31 | [parser, request] 32 | end 33 | private :parse 34 | 35 | def test_parse_http_get 36 | parser, request = parse <<-END 37 | GET /chat?qwe=QWE&asd=#fragment HTTP/1.1\r 38 | Host: server.example.Com.\r 39 | Upgrade: WebSocket\r 40 | Connection: keep-Alive , Upgrade\r 41 | Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r 42 | Sec-WebSocket-protocol: foo , chat.lalala.com\r 43 | Sec-WebSocket-protocol: xmpp.nonaino.org\r 44 | Origin: http://example.Com\r 45 | Sec-WebSocket-Version: 13\r 46 | noNaino-lALA : qwe\r 47 | NOnaino-lala: asd\r 48 | \r 49 | END 50 | 51 | assert_equal request.http_method, :GET 52 | assert_equal request.http_version, "HTTP/1.1" 53 | 54 | assert_equal "/chat?qwe=QWE&asd=#fragment", request.uri 55 | assert_equal "/chat", request.uri_path 56 | assert_equal "qwe=QWE&asd=", request.uri_query 57 | assert_equal "fragment", request.uri_fragment 58 | assert_equal request.uri_scheme, nil 59 | assert_equal "server.example.com", request.host 60 | assert_nil request.port 61 | 62 | assert_nil request.content_length 63 | assert request.hdr_connection.include?("upgrade") 64 | assert_equal "websocket", request.hdr_upgrade 65 | assert_equal 13, request.hdr_sec_websocket_version 66 | assert_equal "dGhlIHNhbXBsZSBub25jZQ==", request.hdr_sec_websocket_key 67 | assert_equal "http://example.com", request.hdr_origin 68 | assert_equal ["foo", "chat.lalala.com", "xmpp.nonaino.org"], request.hdr_sec_websocket_protocol 69 | 70 | assert_equal ["qwe", "asd"], request["Nonaino-Lala"] 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /test/test_name_addr.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "oversip_test_helper" 4 | 5 | 6 | class TestNameAddr < OverSIPTest 7 | 8 | def test_name_addr 9 | full_name_addr = '"Iñaki Baz Castillo" ' 10 | aor = "sip:i%C3%B1aki@aliax.net" 11 | 12 | name_addr = ::OverSIP::SIP::NameAddr.new "Iñaki Baz Castillo", :sips, "iñaki", "aliax.net", 5060 13 | name_addr.transport_param = :tcp 14 | name_addr.set_param "FOO", "123" 15 | name_addr.set_param "baz", nil 16 | name_addr.headers = "?X-Header-1=qwe&X-Header-2=asd" 17 | 18 | assert_true name_addr.sip? 19 | assert_false name_addr.tel? 20 | assert_false name_addr.unknown_scheme? 21 | assert_equal "iñaki", name_addr.user 22 | assert_equal "123", name_addr.get_param("Foo") 23 | assert_equal aor, name_addr.aor 24 | assert_equal full_name_addr, name_addr.to_s 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /test/test_name_addr_parser.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "oversip_test_helper" 4 | 5 | 6 | class TestNameAddrParser < OverSIPTest 7 | 8 | def test_parse_name_addr 9 | name_addr_str = '"Iñaki" ' 10 | aor = "sip:i%C3%B1aki@aliax.net" 11 | 12 | name_addr = ::OverSIP::SIP::NameAddr.parse name_addr_str 13 | 14 | assert_equal ::OverSIP::SIP::NameAddr, name_addr.class 15 | assert_equal "Iñaki", name_addr.display_name 16 | assert_true name_addr.sip? 17 | assert_false name_addr.unknown_scheme? 18 | assert_equal "iñaki", name_addr.user 19 | assert_equal "123", name_addr.get_param("Foo") 20 | assert_equal aor, name_addr.aor 21 | assert_equal name_addr_str, name_addr.to_s 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /test/test_sip_uri_parser.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "oversip_test_helper" 4 | 5 | 6 | class TestSipUriParser < OverSIPTest 7 | 8 | def test_parse_sip_uri 9 | uri_str = "sips:i%C3%B1aki@aliax.net:5060;transport=tcp;foo=123;baz?X-Header-1=qwe&X-Header-2=asd" 10 | aor = "sip:i%C3%B1aki@aliax.net" 11 | 12 | uri = ::OverSIP::SIP::Uri.parse uri_str 13 | 14 | assert_equal ::OverSIP::SIP::Uri, uri.class 15 | assert_true uri.sip? 16 | assert_false uri.unknown_scheme? 17 | assert_equal "iñaki", uri.user 18 | assert_true uri.has_param? "FOO" 19 | assert_false uri.has_param? "LALALA" 20 | assert_equal "123", uri.get_param("Foo") 21 | assert_equal aor, uri.aor 22 | assert_equal uri_str, uri.to_s 23 | end 24 | 25 | def test_parse_tel_uri 26 | uri_str = "tel:944991212;foo=bar;phone-context=+34" 27 | aor = "tel:944991212" 28 | 29 | uri = ::OverSIP::SIP::Uri.parse uri_str 30 | 31 | assert_equal ::OverSIP::SIP::Uri, uri.class 32 | assert_true uri.tel? 33 | assert_false uri.unknown_scheme? 34 | assert_equal "944991212", uri.number 35 | assert_true uri.has_param? "FOO" 36 | assert_false uri.has_param? "LALALA" 37 | assert_equal "bar", uri.get_param("Foo") 38 | assert_equal aor, uri.aor 39 | assert_equal uri_str, uri.to_s 40 | end 41 | 42 | def test_parse_http_uri 43 | uri_str = "http://oversip.net/authors/" 44 | aor = nil 45 | 46 | uri = ::OverSIP::SIP::Uri.parse uri_str 47 | 48 | assert_equal ::OverSIP::SIP::Uri, uri.class 49 | assert_false uri.sip? 50 | assert_false uri.tel? 51 | assert_true uri.unknown_scheme? 52 | assert_nil uri.has_param? "FOO" 53 | assert_nil uri.aor 54 | assert_equal uri_str, uri.to_s 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/test_uri.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require "oversip_test_helper" 4 | 5 | 6 | class TestUri < OverSIPTest 7 | 8 | def test_sip_uri 9 | full_uri = "sips:i%C3%B1aki@aliax.net:5060;transport=tcp;foo=123;baz?X-Header-1=qwe&X-Header-2=asd" 10 | aor = "sip:i%C3%B1aki@aliax.net" 11 | 12 | uri = ::OverSIP::SIP::Uri.new :sips, "iñaki", "aliax.net", 5060 13 | uri.transport_param = :tcp 14 | uri.set_param "FOO", "123" 15 | uri.set_param "baz", nil 16 | uri.headers = "?X-Header-1=qwe&X-Header-2=asd" 17 | 18 | assert_true uri.sip? 19 | assert_false uri.tel? 20 | assert_false uri.unknown_scheme? 21 | assert_equal "iñaki", uri.user 22 | assert_equal "123", uri.get_param("Foo") 23 | assert_equal aor, uri.aor 24 | assert_equal full_uri, uri.to_s 25 | 26 | uri.clear_params 27 | 28 | assert_equal({}, uri.params) 29 | assert_equal "sips:i%C3%B1aki@aliax.net:5060?X-Header-1=qwe&X-Header-2=asd", uri.to_s 30 | end 31 | 32 | def test_tel_uri 33 | full_uri = "tel:944991212;foo=bar;phone-context=+34" 34 | aor = "tel:944991212" 35 | 36 | uri = ::OverSIP::SIP::Uri.new :tel, "944991212" 37 | uri.set_param "FOO", "bar" 38 | uri.phone_context_param = "+34" 39 | 40 | assert_false uri.sip? 41 | assert_true uri.tel? 42 | assert_false uri.unknown_scheme? 43 | assert_equal "944991212", uri.number 44 | assert_equal "bar", uri.get_param("Foo") 45 | assert_equal aor, uri.aor 46 | assert_equal full_uri, uri.to_s 47 | 48 | uri.clear_params 49 | 50 | assert_equal({}, uri.params) 51 | assert_equal aor, uri.to_s 52 | end 53 | 54 | def test_http_uri 55 | full_uri = "http://oversip.net/authors/" 56 | aor = nil 57 | 58 | uri = ::OverSIP::SIP::Uri.allocate 59 | uri.instance_variable_set :@uri, full_uri 60 | 61 | assert_false uri.sip? 62 | assert_false uri.tel? 63 | assert_true uri.unknown_scheme? 64 | assert_nil uri.aor 65 | assert_equal full_uri, uri.to_s 66 | assert_nil uri.clear_params 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /thirdparty/stud/NOTES: -------------------------------------------------------------------------------- 1 | - stud's original Makefile has been modified for adding /usr/include/libev into the library path (workaround for CentOS): 2 | See: https://github.com/versatica/OverSIP/issues/23#issuecomment-9649288 3 | 4 | -------------------------------------------------------------------------------- /thirdparty/stud/stud.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/versatica/OverSIP/8d8dd360be71047935fedb00ccc0a9d8eced469c/thirdparty/stud/stud.tar.gz --------------------------------------------------------------------------------