module OverSIP module Config # Pre-declaration of Validators module (defined in other file). module Config::Validators ; end extend ::OverSIP::Logger extend ::OverSIP::Config::Validators DEFAULT_CONFIG_DIR = "/etc/oversip" DEFAULT_TLS_DIR = "tls" DEFAULT_TLS_CA_DIR = "tls/ca" DEFAULT_CONFIG_FILE = "oversip.conf" PROXIES_FILE = "proxies.conf" SERVER_FILE = "server.rb" def self.log_id @log_id ||= "Config" end @configuration = { :core => { :nameservers => nil, :syslog_facility => "user", :syslog_level => "info" }, :sip => { :sip_udp => true, :sip_tcp => true, :sip_tls => false, :enable_ipv4 => true, :listen_ipv4 => nil, :enable_ipv6 => true, :listen_ipv6 => nil, :listen_port => 5060, :listen_port_tls => 5061, :use_tls_tunnel => false, :listen_port_tls_tunnel => 5062, :callback_on_client_tls_handshake => true, :local_domains => nil, :tcp_keepalive_interval => nil, :record_route_hostname_tls_ipv4 => nil, :record_route_hostname_tls_ipv6 => nil }, :websocket => { :sip_ws => false, :sip_wss => false, :enable_ipv4 => true, :listen_ipv4 => nil, :enable_ipv6 => true, :listen_ipv6 => nil, :listen_port => 10080, :listen_port_tls => 10443, :use_tls_tunnel => false, :listen_port_tls_tunnel => 10444, :callback_on_client_tls_handshake => true, :max_ws_message_size => 65536, :max_ws_frame_size => 65536, :ws_keepalive_interval => nil }, :tls => { :public_cert => nil, :private_cert => nil, :ca_dir => nil } } CONFIG_VALIDATIONS = { :core => { :nameservers => [ :ipv4, :multi_value ], :syslog_facility => [ [ :choices, %w{ kern user daemon local0 local1 local2 local3 local4 local5 local6 local7 } ] ], :syslog_level => [ [ :choices, %w{ debug info notice warn error crit } ] ], }, :sip => { :sip_udp => :boolean, :sip_tcp => :boolean, :sip_tls => :boolean, :enable_ipv4 => :boolean, :listen_ipv4 => :ipv4, :enable_ipv6 => :boolean, :listen_ipv6 => :ipv6, :listen_port => :port, :listen_port_tls => :port, :use_tls_tunnel => :boolean, :listen_port_tls_tunnel => :port, :callback_on_client_tls_handshake => :boolean, :local_domains => [ :domain, :multi_value ], :tcp_keepalive_interval => [ :fixnum, [ :greater_equal_than, 180 ] ], :record_route_hostname_tls_ipv4 => :domain, :record_route_hostname_tls_ipv6 => :domain, }, :websocket => { :sip_ws => :boolean, :sip_wss => :boolean, :enable_ipv4 => :boolean, :listen_ipv4 => :ipv4, :enable_ipv6 => :boolean, :listen_ipv6 => :ipv6, :listen_port => :port, :listen_port_tls => :port, :use_tls_tunnel => :boolean, :listen_port_tls_tunnel => :port, :callback_on_client_tls_handshake => :boolean, :max_ws_message_size => [ :fixnum, [ :minor_than, 1048576 ] ], :max_ws_frame_size => [ :fixnum, [ :minor_than, 1048576 ] ], :ws_keepalive_interval => [ :fixnum, [ :greater_equal_than, 180 ] ] }, :tls => { :public_cert => [ :readable_file, :tls_pem_chain ], :private_cert => [ :readable_file, :tls_pem_private ], :ca_dir => :readable_dir } } def self.load config_dir=nil, config_file=nil @config_dir = (::File.expand_path(config_dir) if config_dir) || DEFAULT_CONFIG_DIR @config_file = ::File.join(@config_dir, config_file || DEFAULT_CONFIG_FILE) @proxies_file = ::File.join(@config_dir, PROXIES_FILE) @server_file = ::File.join(@config_dir, SERVER_FILE) # Load the oversip.conf YAML file. begin conf_yaml = ::YAML.load_file @config_file rescue ::Exception => e log_system_crit "error loading Main Configuration file '#{@config_file}':" ::OverSIP::Launcher.fatal e end # Load the proxies.conf YAML file. begin proxies_yaml = ::YAML.load_file @proxies_file rescue ::Exception => e log_system_crit "error loading Proxies Configuration file '#{@proxies_file}':" ::OverSIP::Launcher.fatal e end # Load the server.rb file. begin require @server_file rescue ::Exception => e log_system_crit "error loading Server file '#{@server_file}':" ::OverSIP::Launcher.fatal e end # Process the oversip.conf file. begin pre_check(conf_yaml) CONFIG_VALIDATIONS.each_key do |section| CONFIG_VALIDATIONS[section].each do |parameter, validations| values = conf_yaml[section.to_s][parameter.to_s] rescue nil validations = [ validations ] unless validations.is_a?(Array) if values == nil if validations.include? :required ::OverSIP::Launcher.fatal "#{section}[#{parameter}] requires a value" end next end if values.is_a? ::Array unless validations.include? :multi_value ::OverSIP::Launcher.fatal "#{section}[#{parameter}] does not allow multiple values" end if validations.include? :non_empty and values.empty? ::OverSIP::Launcher.fatal "#{section}[#{parameter}] does not allow empty values" end end values = ( values.is_a?(::Array) ? values : [ values ] ) values.each do |value| validations.each do |validation| if validation.is_a? ::Symbol args = [] elsif validation.is_a? ::Array args = validation[1..-1] validation = validation[0] end next if [:required, :multi_value, :non_empty].include? validation unless send validation, value, *args ::OverSIP::Launcher.fatal "#{section}[#{parameter}] has invalid value '#{humanize_value value}' (does not satisfy '#{validation}' validation requirement)" end end @configuration[section][parameter] = ( validations.include?(:multi_value) ? values : values[0] ) end end # CONFIG_VALIDATIONS[section].each end # CONFIG_VALIDATIONS.each_key post_process post_check rescue ::OverSIP::ConfigurationError => e ::OverSIP::Launcher.fatal "configuration error: #{e.message}" rescue => e ::OverSIP::Launcher.fatal e end ::OverSIP.configuration = @configuration # Process the proxies.conf file. begin ::OverSIP::ProxiesConfig.load proxies_yaml rescue ::OverSIP::ConfigurationError => e ::OverSIP::Launcher.fatal "error loading Proxies Configuration file '#{@proxies_file}': #{e.message}" rescue ::Exception => e log_system_crit "error loading Proxies Configuration file '#{@proxies_file}':" ::OverSIP::Launcher.fatal e end end def self.pre_check conf_yaml # If TLS files/directories are given as relative path, convert them into absolute paths. tls_public_cert = conf_yaml["tls"]["public_cert"] rescue nil tls_private_cert = conf_yaml["tls"]["private_cert"] rescue nil tls_ca_dir = conf_yaml["tls"]["ca_dir"] rescue nil if tls_public_cert.is_a?(::String) and tls_public_cert[0] != "/" conf_yaml["tls"]["public_cert"] = ::File.join(@config_dir, DEFAULT_TLS_DIR, tls_public_cert) end if tls_private_cert.is_a?(::String) and tls_private_cert[0] != "/" conf_yaml["tls"]["private_cert"] = ::File.join(@config_dir, DEFAULT_TLS_DIR, tls_private_cert) end if tls_ca_dir.is_a?(::String) and tls_ca_dir[0] != "/" conf_yaml["tls"]["ca_dir"] = ::File.join(@config_dir, DEFAULT_TLS_DIR, tls_ca_dir) end end def self.post_process if @configuration[:tls][:public_cert] and @configuration[:tls][:private_cert] @use_tls = true # Generate a full PEM file containing both the public and private certificate (for Stud). full_cert = ::Tempfile.new("oversip_full_cert_") full_cert.puts ::File.read(@configuration[:tls][:public_cert]) full_cert.puts ::File.read(@configuration[:tls][:private_cert]) @configuration[:tls][:full_cert] = full_cert.path full_cert.close else @configuration[:sip][:sip_tls] = false @configuration[:websocket][:sip_wss] = false end if @configuration[:sip][:sip_udp] or @configuration[:sip][:sip_tcp] @use_sip_udp_or_tcp = true else @configuration[:sip][:listen_port] = nil end if @configuration[:sip][:sip_tls] and @use_tls @use_sip_tls = true else @configuration[:sip][:listen_port_tls] = nil end unless @use_sip_udp_or_tcp or @use_sip_tls @configuration[:sip][:listen_ipv4] = nil @configuration[:sip][:listen_ipv6] = nil @configuration[:sip][:enable_ipv4] = nil @configuration[:sip][:enable_ipv6] = nil end unless @configuration[:sip][:enable_ipv4] @configuration[:sip][:listen_ipv4] = nil end unless @configuration[:sip][:enable_ipv6] @configuration[:sip][:listen_ipv6] = nil end if @configuration[:websocket][:sip_ws] @use_sip_ws = true else @configuration[:websocket][:listen_port] = nil end if @configuration[:websocket][:sip_wss] and @use_tls @use_sip_wss = true else @configuration[:websocket][:listen_port_tls] = nil end unless @use_sip_ws or @use_sip_wss @configuration[:websocket][:listen_ipv4] = nil @configuration[:websocket][:listen_ipv6] = nil @configuration[:websocket][:enable_ipv4] = nil @configuration[:websocket][:enable_ipv6] = nil end unless @configuration[:websocket][:enable_ipv4] @configuration[:websocket][:listen_ipv4] = nil end unless @configuration[:websocket][:enable_ipv6] @configuration[:websocket][:listen_ipv6] = nil end if ( @use_sip_udp_or_tcp or @use_sip_tls ) and @configuration[:sip][:listen_ipv4] == nil and @configuration[:sip][:enable_ipv4] unless (@configuration[:sip][:listen_ipv4] = discover_local_ip(:ipv4)) log_system_warn "disabling IPv4 for SIP" @configuration[:sip][:listen_ipv4] = nil @configuration[:sip][:enable_ipv4] = false end end if ( @use_sip_udp_or_tcp or @use_sip_tls ) and @configuration[:sip][:listen_ipv6] == nil and @configuration[:sip][:enable_ipv6] unless (@configuration[:sip][:listen_ipv6] = discover_local_ip(:ipv6)) log_system_warn "disabling IPv6 for SIP" @configuration[:sip][:listen_ipv6] = nil @configuration[:sip][:enable_ipv6] = false end end if ( @use_sip_ws or @use_sip_wss ) and @configuration[:websocket][:listen_ipv4] == nil and @configuration[:websocket][:enable_ipv4] unless (@configuration[:websocket][:listen_ipv4] = discover_local_ip(:ipv4)) log_system_warn "disabling IPv4 for WebSocket" @configuration[:websocket][:listen_ipv4] = nil @configuration[:websocket][:enable_ipv4] = false end end if ( @use_sip_ws or @use_sip_wss ) and @configuration[:websocket][:listen_ipv6] == nil and @configuration[:websocket][:enable_ipv6] unless (@configuration[:websocket][:listen_ipv6] = discover_local_ip(:ipv6)) log_system_warn "disabling IPv6 for WebSocket" @configuration[:websocket][:listen_ipv6] = nil @configuration[:websocket][:enable_ipv6] = false end end if @configuration[:sip][:local_domains] if @configuration[:sip][:local_domains].is_a? ::String @configuration[:sip][:local_domains] = [ @configuration[:sip][:local_domains].downcase ] end @configuration[:sip][:local_domains].each {|local_domain| local_domain.downcase!} end end # def self.post_process def self.post_check binds = { :udp => [], :tcp => [] } if @configuration[:sip][:enable_ipv4] ipv4 = @configuration[:sip][:listen_ipv4] if @configuration[:sip][:sip_udp] binds[:udp] << [ ipv4, @configuration[:sip][:listen_port] ] end if @configuration[:sip][:sip_tcp] binds[:tcp] << [ ipv4, @configuration[:sip][:listen_port] ] end if @configuration[:sip][:sip_tls] unless @configuration[:sip][:use_tls_tunnel] binds[:tcp] << [ ipv4, @configuration[:sip][:listen_port_tls] ] else binds[:tcp] << [ "127.0.0.1", @configuration[:sip][:listen_port_tls_tunnel] ] end end end if @configuration[:sip][:enable_ipv6] ipv6 = @configuration[:sip][:listen_ipv6] if @configuration[:sip][:sip_udp] binds[:udp] << [ ipv6, @configuration[:sip][:listen_port] ] end if @configuration[:sip][:sip_tcp] binds[:tcp] << [ ipv6, @configuration[:sip][:listen_port] ] end if @configuration[:sip][:sip_tls] unless @configuration[:sip][:use_tls_tunnel] binds[:tcp] << [ ipv6, @configuration[:sip][:listen_port_tls] ] else binds[:tcp] << [ "::1", @configuration[:sip][:listen_port_tls_tunnel] ] end end end if @configuration[:websocket][:enable_ipv4] ipv4 = @configuration[:websocket][:listen_ipv4] if @configuration[:websocket][:sip_ws] binds[:tcp] << [ ipv4, @configuration[:websocket][:listen_port] ] end if @configuration[:websocket][:sip_wss] unless @configuration[:sip][:use_tls_tunnel] binds[:tcp] << [ ipv4, @configuration[:websocket][:listen_port_tls] ] else binds[:tcp] << [ "127.0.0.1", @configuration[:websocket][:listen_port_tls_tunnel] ] end end end if @configuration[:websocket][:enable_ipv6] ipv6 = @configuration[:websocket][:listen_ipv6] if @configuration[:websocket][:sip_ws] binds[:tcp] << [ ipv6, @configuration[:websocket][:listen_port] ] end if @configuration[:websocket][:sip_wss] unless @configuration[:sip][:use_tls_tunnel] binds[:tcp] << [ ipv6, @configuration[:websocket][:listen_port_tls] ] else binds[:tcp] << [ "::1", @configuration[:websocket][:listen_port_tls_tunnel] ] end end end unless @configuration[:sip][:use_tls_tunnel] @configuration[:sip][:listen_port_tls_tunnel] = nil end unless @configuration[:websocket][:use_tls_tunnel] @configuration[:websocket][:listen_port_tls_tunnel] = nil end [:udp, :tcp].each do |transport| transport_str = transport.to_s.upcase binds[transport].each do |ip, port| begin unless (ip_type = ::OverSIP::Utils.ip_type(ip)) raise ::OverSIP::ConfigurationError, "given IP '#{ip}' is not IPv4 nor IPv6" end case transport when :udp case ip_type when :ipv4 socket = ::UDPSocket.new ::Socket::AF_INET when :ipv6 socket = ::UDPSocket.new ::Socket::AF_INET6 end socket.bind ip, port when :tcp socket = ::TCPServer.open ip, port end socket.close rescue ::Errno::EADDRNOTAVAIL raise ::OverSIP::ConfigurationError, "cannot bind in #{transport_str} IP '#{ip}', address not available" rescue ::Errno::EADDRINUSE raise ::OverSIP::ConfigurationError, "#{transport_str} IP '#{ip}' and port #{port} already in use" rescue ::Errno::EACCES raise ::OverSIP::ConfigurationError, "no permission to bind in #{transport_str} IP '#{ip}' and port #{port}" rescue => e raise e.class, "error binding in #{transport_str} IP '#{ip}' and port #{port} (#{e.class}: #{e.message})" end end end end # def self.post_check def self.print colorize=true color = ::Term::ANSIColor if colorize puts @configuration.each_key do |section| if colorize puts " #{color.bold(section.to_s)}:" else puts " #{section.to_s}:" end @configuration[section].each do |parameter, value| humanized_value = humanize_value value color_value = case value when ::TrueClass colorize ? color.bold(color.green(humanized_value)) : humanized_value when ::FalseClass colorize ? color.bold(color.red(humanized_value)) : humanized_value when ::NilClass humanized_value when ::String, ::Symbol colorize ? color.yellow(humanized_value) : humanized_value when ::Array colorize ? color.yellow(humanized_value) : humanized_value when ::Fixnum, ::Float colorize ? color.bold(color.blue(humanized_value)) : humanized_value else humanized_value end printf(" %-32s: %s\n", parameter, color_value) end puts end end def self.humanize_value value case value when ::TrueClass ; "yes" when ::FalseClass ; "no" when ::NilClass ; "null" when ::String ; value when ::Symbol ; value.to_s when ::Array ; value.join(", ") when ::Fixnum, ::Float ; value.to_s else ; value.to_s end end def self.discover_local_ip(type) begin if type == :ipv4 socket = ::UDPSocket.new ::Socket::AF_INET socket.connect("1.2.3.4", 1) ip = socket.local_address.ip_address socket.close socket = ::UDPSocket.new ::Socket::AF_INET elsif type == :ipv6 socket = ::UDPSocket.new ::Socket::AF_INET6 socket.connect("2001::1", 1) ip = socket.local_address.ip_address socket.close socket = ::UDPSocket.new ::Socket::AF_INET6 end # Test whether the IP is in fact bindeable (not true for link-scope IPv6 addresses). begin socket.bind ip, 0 rescue => e log_system_warn "cannot bind in autodiscovered local #{type == :ipv4 ? "IPv4" : "IPv6"} '#{ip}': #{e.message} (#{e.class})" return false ensure socket.close end # Valid IP, return it. return ip rescue => e log_system_warn "cannot autodiscover local #{type == :ipv4 ? "IPv4" : "IPv6"}: #{e.message} (#{e.class})" return false end end def self.system_reload log_system_notice "reloading OverSIP..." # Load and process the proxies.conf file. begin proxies_yaml = ::YAML.load_file @proxies_file ::OverSIP::ProxiesConfig.load proxies_yaml, reload=true log_system_notice "Proxies Configuration file '#{@proxies_file}' reloaded" rescue ::OverSIP::ConfigurationError => e log_system_crit "error reloading Proxies Configuration file '#{@proxies_file}': #{e.message}" rescue ::Exception => e log_system_crit "error reloading Proxies Configuration file '#{@proxies_file}':" log_system_crit e end # Load the server.rb file. begin ::Kernel.load @server_file log_system_notice "Server file '#{@server_file}' reloaded" rescue ::Exception => e log_system_crit "error reloading Server file '#{@server_file}':" log_system_crit e end end end end