require 'cgi'
require 'base64'
module Lumberg
module Whm
class Server < Base
# Server
attr_accessor :host
# Remote access hash
attr_accessor :hash
# Base URL to the WHM API
attr_accessor :base_url
# Enable Basic Authentication with API - default false
attr_accessor :basic_auth
# API username - :default => root
attr_accessor :user
# Raw HTTP response from WHM
attr_accessor :raw_response
# WHM parsed response
attr_reader :response
# HTTP Params used for API requests
attr_accessor :params
# WHM API function name
attr_reader :function
# Use ssl?
attr_accessor :ssl
# HTTP SSL verify mode
attr_accessor :ssl_verify
# Returned params to transfor to booleans
attr_accessor :boolean_params
# Force response type...ARG!
attr_accessor :force_response_type
#
# ==== Required
# * :host - PENDING
# * :hash - PENDING
#
# ==== Optional
# * :user - PENDING
# * :ssl - PENDING
# * :basic_auth
def initialize(options)
Args.new(options) do |c|
c.requires :host, :hash
c.optionals :user, :ssl, :basic_auth
end
@ssl_verify ||= false
@ssl = options.delete(:ssl)
@host = options.delete(:host)
@hash = format_hash(options.delete(:hash))
@user = (options.has_key?(:user) ? options.delete(:user) : 'root')
@basic_auth = options.has_key?(:basic_auth) && options.delete(:basic_auth)
@base_url = format_url(options)
end
def perform_request(function, options = {})
@function = function
# WHM sometime uses different keys for the result hash
@key = options.delete(:key) || 'result'
@params = format_query(options)
uri = URI.parse("#{@base_url}#{function}?#{@params}")
yield self if block_given?
req = prepare_request(uri)
# Do the request
res = do_request(uri, req)
@raw_response = res
@response = JSON.parse(@raw_response.body)
format_response
end
def get_hostname
perform_request('gethostname', {:key => 'hostname'})
end
def version
perform_request('version', {:key => 'version'})
end
def load_average
@force_response_type = :query
result = perform_request('loadavg')
result[:success] = result[:params].has_key?(:one)
result
end
def system_load_average(options = {})
Args.new(options) do |c|
c.requires "api.version".to_sym
end
perform_request('systemloadavg', options.merge(:key => 'data'))
end
def languages
perform_request('getlanglist', {:key => 'lang'})
end
def list_ips
perform_request('listips', {:key => 'result'})
end
def add_ip(options = {})
Args.new(options) do |c|
c.requires :ip, :netmask
end
perform_request('addip', options.merge(:key => 'addip'))
end
def delete_ip(options = {})
Args.new(options) do |c|
c.requires :ip
c.optionals :ethernetdev
c.booleans :skipifshutdown
end
perform_request('delip', options.merge(:key => 'delip'))
end
def set_hostname(options = {})
Args.new(options) do |c|
c.requires :hostname
end
perform_request('sethostname', options.merge(:key => 'sethostname'))
end
def set_resolvers(options = {})
Args.new(options) do |c|
c.requires :nameserver1
c.optionals :nameserver2, :nameserver3
end
perform_request('setresolvers', options.merge(:key => 'setresolvers'))
end
def show_bandwidth(options = {})
Args.new(options) do |c|
c.optionals :month, :year, :showres, :search, :searchtype
end
perform_request('showbw', options.merge(:key => 'bandwidth'))
end
def set_nv_var(options = {})
Args.new(options) do |c|
c.requires :key
c.optionals :value
end
perform_request('nvset', options.merge(:key => 'nvset'))
end
def get_nv_var(options = {})
Args.new(options) do |c|
c.requires :key
c.optionals :value
end
perform_request('nvget', options.merge(:key => 'nvget'))
end
def reboot
perform_request('reboot', {:key => "reboot"})
end
protected
def response_type
if !@force_response_type.nil?
@force_response_type
elsif !@response.respond_to?(:has_key?)
:unknown
elsif @response.has_key?('error')
:error
elsif @response.has_key?(@key)
:action
elsif @response.has_key?('status') && @response.has_key?('statusmsg')
:query
else
:unknown
end
end
def format_response
success, message, params = false, nil, {}
case response_type
when :action
success, message, params = format_action_response
when :query
success, message, params = format_query_response
when :error
message = @response['error']
when :unknown
message = "Unknown error occurred #{@response.inspect}"
end
params = Whm::to_bool(params, @boolean_params) unless @boolean_params.nil?
# Reset this for subsequent requests
@force_response_type = nil
{:success => success, :message => message, :params => Whm::symbolize_keys(params)}
end
def format_url(options = {})
@ssl = true if @ssl.nil?
port = (@ssl ? 2087 : 2086)
proto = (@ssl ? 'https' : 'http')
"#{proto}://#{@host}:#{port}/json-api/"
end
def format_hash(hash)
raise Lumberg::WhmArgumentError.new("Missing WHM hash") unless hash.is_a?(String)
hash.gsub(/\n|\s/, '')
end
def format_query(hash)
elements = []
hash.each do |key, value|
value = 1 if value === true
value = 0 if value === false
elements << "#{CGI::escape(key.to_s)}=#{CGI::escape(value.to_s)}"
end
elements.sort.join('&')
end
private
def do_request(uri, req)
begin
Net::HTTP.skip_bad_headers = true
http = Net::HTTP.new(uri.host, uri.port)
http.set_debug_output($stderr) if ENV['LUMBERG_DEBUG']
enable_ssl(http) if uri.port == 2087
http.start do |h|
h.request(req)
end
rescue Exception => e
puts "Error when sending the request.
Enable debug output by setting the environment variable LUMBERG_DEBUG and try again."
raise e
ensure
Net::HTTP.skip_bad_headers = false
end
end
def format_action_response
# Some API methods ALSO return a 'status' as
# part of a result. We only use this value if it's
# part of the results hash
item = @response[@key]
unless item.is_a?(Array) || item.is_a?(Hash)
res = {@key => item}
success, message = true, ""
else
result = nil
if item.first.is_a?(Hash)
result = item.first
res = (item.size > 1 ? item.dup : item.first.dup)
else
res = item.dup
# more hacks for WHM silly API
if @response.has_key?('result')
result_node = @response['result']
node_with_key_status = result_node.is_a?(Hash) && result_node.has_key?('status')
result = (node_with_key_status ? result_node : result_node.first)
else
res.delete('status')
res.delete('statusmsg')
end
end
unless result.nil?
success = result['status'].to_i == 1
message = result['statusmsg']
end
end
return success, message, res
end
def format_query_response
success = @response['status'].to_i == 1
message = @response['statusmsg']
# returns the rest as a params arg
res = @response.dup
res.delete('status')
res.delete('statusmsg')
return success, message, res
end
# Creates WHM::Whatever.new(:server => @server)
# automagically
def auto_accessors
[:account, :dns, :reseller]
end
def method_missing(meth, *args, &block)
if auto_accessors.include?(meth.to_sym)
ivar = instance_variable_get("@#{meth}")
if ivar.nil?
constant = Whm.const_get(meth.to_s.capitalize)
return instance_variable_set("@#{meth}", constant.new(:server => self))
else
return ivar
end
else
super
end
end
def prepare_request(uri)
# Setup request URL
url = uri.path
query = uri.query
url << "?" + query unless query.nil? || query.empty?
req = Net::HTTP::Get.new(url)
# Add Auth Header
if basic_auth
encoded = Base64.encode64("#{@user}:#{@hash}")
auth = "Basic #{encoded}"
else
auth = "WHM #{@user}:#{@hash}"
end
req.add_field("Authorization", auth)
req
end
def enable_ssl(http)
if @ssl_verify
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.ca_file = File.join(Lumberg::base_path, "cacert.pem")
else
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
http.use_ssl = true
end
end
end
end