#
# Copyright (C) 2006 SIPfoundry Inc.
# Licensed by SIPfoundry under the LGPL license.
#
# Copyright (C) 2006 Pingtel Corp.
# Licensed to SIPfoundry under a Contributor Agreement.
#
##############################################################################
# Application requires. Assume that the load path has been set up for us.
require 'call_resolver_configure'
require 'configure'
require 'database_url'
require 'exceptions'
require 'utils'
# StunnelConnection attempts to open stunnel connections to all configured
# distributed machines.
class StunnelConnection
# Constants
LOCALHOST = 'localhost'
SIPX_PREFIX = 'SIPX_PREFIX'
STUNNEL_CONFIG_FILE = 'stunnel-config.tmp'
STUNNEL_EXEC = '/usr/sbin/stunnel'
ERROR_OUT = "Error: "
CSE_STUNNEL_DEBUG_LEVEL = 'SIP_CALLRESOLVER_STUNNEL_DEBUG'
# Default debug level NOTICE
CSE_STUNNEL_DEBUG_LEVEL_DEFAULT = '5'
CSE_CONNECT_PORT = '9300'
# No default for the CA file name - if HA is configured this has to be set
CSE_CA = 'SIP_CALLRESOLVER_CSE_CA'
SIGNAL_SIGQUIT = 3
SIPXPBX_SSLPATH = 'etc/sipxpbx/ssl'
public
def initialize(resolver)
@resolver = resolver
@connection_established = false
@prefix = ENV[SIPX_PREFIX]
end
def open(config)
# Look up the config param and generate the stunnel config file.
# Set the @ha_enabled variable depending on if any distributed
# machines were configured.
get_stunnel_config(config)
# Open stunnel connection if HA is enabled
if @ha_enabled
if ! check_stunnel_running
fork do exec("#{STUNNEL_EXEC} #{STUNNEL_CONFIG_FILE}") end
# Give stunnel time to start up
sleep 1
# Get the Pid of the process we just started (stored as instance variable),
# also check if it really started
if ! check_stunnel_running
raise_exception("stunnel could not be started.")
else
@connection_established = true
end
else
raise_exception("An instance of stunnel is already running with Pid #{@pid}. It must be shut " +
" down before restarting the call resolver.")
end
end
end
def close
# Only do something if connection was established
if @connection_established
log.debug {"StunnelConnection.close: Killing pid #{@pid}"}
Process.kill(SIGNAL_SIGQUIT, @pid.to_i)
File.delete("#{STUNNEL_CONFIG_FILE}")
end
end
private
# Get possible distributed CSE hosts from configuration file. Generate
# an stunnel configuration script and return an array of ports.
def get_stunnel_config(config)
host_list = config.host_list
host_url_list = []
host_port_list = []
@ha_enabled = false
# Split host list into separate host:port names, then build two
# arrays of URLs and ports.
host_array = host_list.split(',')
host_array.each do |host_string|
host_elements = host_string.split(':')
# Strip leading and trailing whitespace
host_elements[0] = host_elements[0].strip
# Test if port was specified
if host_elements.length == 1
# Supply default port for localhost
if host_elements[0] == LOCALHOST
host_elements[1] = DatabaseUrl::DATABASE_PORT_DEFAULT.to_s
else
Utils.raise_exception(
"No port specified for host \"#{host_elements[0]}\" in #{CSE_HOSTS}. " +
"A port number for hosts other than \"localhost\" must be specified.",
ConfigException)
end
else
# Strip whitespace from port
host_elements[1] = host_elements[1].strip
end
host_url_list << host_elements[0]
host_port_list << host_elements[1]
log.debug {"get_stunnel_config: host name #{host_elements[0]}, host port: #{host_elements[1]}"}
# If at least one of the hosts != 'localhost' we are HA enabled
if host_elements[0] != 'localhost' && ! @ha_enabled
@ha_enabled = true
log.debug {"get_stunnel_config: Found host other than localhost - enable HA"}
end
end
# Check if we are HA enabled
if @ha_enabled
# get the name of the CA file - no defaults here, must be specified
ca_file = config[CSE_CA]
err_msg = "No CA file name specified. If hosts other than \"localhost\" " +
"are specified in #{CallResolverConfigure::CSE_HOSTS}, then the " +
"parameter #{CSE_CA} must be set to the CA file name."
if ca_file == nil
raise_exception(err_msg, ConfigException)
else
ca_file = ca_file.strip
if ca_file.length == 0
raise_exception(err_msg, ConfigException)
end
end
# Test if file exists
if ! test(?e, "#{@prefix}/#{SIPXPBX_SSLPATH}/authorities/#{ca_file}")
err_msg = "CA file \"#{@prefix}/#{SIPXPBX_SSLPATH}/authorities/#{ca_file}\" does not exist."
raise_exception(err_msg, ConfigException)
end
debug_level = config[CSE_STUNNEL_DEBUG_LEVEL]
debug_level ||= CSE_STUNNEL_DEBUG_LEVEL_DEFAULT
generate_stunnel_config(host_url_list, host_port_list, ca_file, debug_level)
end
end
# Generate the stunnel configuration based on the call resolver configuration
def generate_stunnel_config(host_list, port_list, ca_file, debug_level)
config_file = File.new("#{STUNNEL_CONFIG_FILE}", "w")
log.debug {"Master machine stunnel configuration:"}
config_file.puts "# This file was generated by call_resolver.rb"
config_file.puts "client = yes"
config_file.puts "CAfile = #{@prefix}/#{SIPXPBX_SSLPATH}/authorities/#{ca_file}"
config_file.puts "cert = #{@prefix}/#{SIPXPBX_SSLPATH}/ssl.crt"
config_file.puts "key = #{@prefix}/#{SIPXPBX_SSLPATH}/ssl.key"
config_file.puts "verify = 2"
config_file.puts "debug = #{debug_level}"
config_file.puts "output = #{@prefix}/var/log/sipxpbx/sipstunnel.log"
log.debug {"CAfile = #{@prefix}/#{SIPXPBX_SSLPATH}/authorities/#{ca_file}"}
log.debug {"cert = #{@prefix}/#{SIPXPBX_SSLPATH}/ssl.crt"}
log.debug {"key = #{@prefix}/#{SIPXPBX_SSLPATH}/ssl.key"}
log.debug {"debug = #{debug_level}"}
host_list.each_with_index do |host, i|
# Don't generate entry for localhost
if host_list[i] != LOCALHOST
config_file.puts ""
config_file.puts "[Postgres-#{i}]"
config_file.puts "accept = #{port_list[i]}"
config_file.puts "connect = #{host}:#{CSE_CONNECT_PORT}"
log.debug {"accept = #{port_list[i]}"}
log.debug {"connect = #{host}:#{CSE_CONNECT_PORT}"}
end
end
config_file.close()
end
def check_stunnel_running()
shellReturn = `ps -fC stunnel | grep #{STUNNEL_EXEC}`
shellReturn = shellReturn.strip
if /\A\D+\s+(\d+)\s+.*stunnel.*/ =~ shellReturn
@pid = $1
running = true
else
@pid = '0'
running = false
end
running
end
# Use the Call Resolver's Logger
def log
@resolver.log
end
def raise_exception(err_msg, klass = CallResolverException)
puts "Error: #{err_msg}"
Utils.raise_exception(err_msg, klass)
end
end
syntax highlighted by Code2HTML, v. 0.9.1