├── .gitignore ├── lib └── epp-client │ ├── version.rb │ ├── exceptions.rb │ ├── ssl.rb │ ├── session.rb │ ├── poll.rb │ ├── connection.rb │ ├── rgp.rb │ ├── base.rb │ ├── xml.rb │ ├── smallregistry.rb │ ├── secdns.rb │ ├── contact.rb │ ├── domain.rb │ └── afnic.rb ├── ChangeLog ├── Gemfile ├── README ├── Gemfile.lock ├── MIT-LICENSE ├── epp-client-rgp.gemspec ├── Rakefile ├── epp-client-secdns.gemspec ├── epp-client-smallregistry.gemspec ├── epp-client-afnic.gemspec ├── epp-client-base.gemspec ├── EXAMPLE.SMALLREGISTRY ├── vendor ├── smallregistry │ └── sr-1.0.xsd ├── ietf │ ├── secDNS-1.0.xsd │ ├── eppcom-1.0.xsd │ ├── secDNS-1.1.xsd │ ├── rgp-1.0.xsd │ ├── host-1.0.xsd │ ├── contact-1.0.xsd │ ├── domain-1.0.xsd │ └── epp-1.0.xsd └── afnic │ ├── frnic-1.0.xsd │ ├── frnic-1.1.xsd │ └── frnic-1.2.xsd └── EXAMPLE.AFNIC /.gitignore: -------------------------------------------------------------------------------- 1 | */.*.orig 2 | ..*.sw? 3 | pkg/* 4 | *.gem 5 | doc 6 | -------------------------------------------------------------------------------- /lib/epp-client/version.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | VERSION = "0.13.1" 3 | end 4 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2010-05-14 mat 2 | * first release 3 | 4 | 2010-05-04 mat 5 | * Initial commit 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | # Specify your gem's dependencies in epp-client.gemspec 4 | Dir['*.gemspec'].each do |i| 5 | gemspec :name => i.sub(/\.gemspec$/, '') 6 | end 7 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | An extensible EPP client library 2 | 3 | When possible, the objects that are received via EPP are translated to their 4 | equivalent in ruby, that is, dates in Date object, timestamps in DateTime 5 | objects. 6 | -------------------------------------------------------------------------------- /lib/epp-client/exceptions.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | class EPPErrorResponse < StandardError 3 | attr_accessor :response_xml, :response_code, :message 4 | 5 | # An exception with an added field so that it can store the xml response 6 | # that generated it. 7 | def initialize(attrs = {}) 8 | @response_xml = attrs[:xml] 9 | @response_code = attrs[:code] 10 | @message = attrs[:message] 11 | end 12 | 13 | def to_s #:nodoc: 14 | "#{@message} (code #{@response_code})" 15 | end 16 | 17 | def inspect #:nodoc: 18 | "#<#{self.class}: code: #{@response_code}, message: #{@message.inspect}, xml: #{@response_xml}>" 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | epp-client-afnic (0.12.0) 5 | builder (>= 2.1.2) 6 | epp-client-base (= 0.12.0) 7 | epp-client-rgp (= 0.12.0) 8 | epp-client-secdns (= 0.12.0) 9 | nokogiri (~> 1.4) 10 | epp-client-base (0.12.0) 11 | builder (>= 2.1.2) 12 | nokogiri (~> 1.4) 13 | epp-client-rgp (0.12.0) 14 | builder (>= 2.1.2) 15 | nokogiri (~> 1.4) 16 | epp-client-secdns (0.12.0) 17 | builder (>= 2.1.2) 18 | nokogiri (~> 1.4) 19 | epp-client-smallregistry (0.12.0) 20 | builder (>= 2.1.2) 21 | epp-client-base (= 0.12.0) 22 | nokogiri (~> 1.4) 23 | 24 | GEM 25 | remote: http://rubygems.org/ 26 | specs: 27 | builder (3.1.4) 28 | nokogiri (1.5.6) 29 | 30 | PLATFORMS 31 | ruby 32 | 33 | DEPENDENCIES 34 | bundler (>= 1.0.0) 35 | epp-client-afnic! 36 | epp-client-base! 37 | epp-client-rgp! 38 | epp-client-secdns! 39 | epp-client-smallregistry! 40 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2010 Mathieu Arnold, Absolight 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /epp-client-rgp.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/epp-client/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.name = 'epp-client-rgp' 6 | gem.version = EPPClient::VERSION 7 | gem.authors = ['Mathieu Arnold'] 8 | gem.email = ['m@absolight.fr'] 9 | gem.description = 'RGP EPP client library.' 10 | gem.summary = 'RGP EPP client library' 11 | gem.homepage = "https://github.com/Absolight/epp-client" 12 | 13 | gem.required_ruby_version = '>= 1.8.7' 14 | gem.required_rubygems_version = ">= 1.3.6" 15 | 16 | gem.files = [ 17 | 'ChangeLog', 18 | 'Gemfile', 19 | 'MIT-LICENSE', 20 | 'README', 21 | 'Rakefile', 22 | 'epp-client-rgp.gemspec', 23 | 'lib/epp-client/rgp.rb', 24 | 'vendor/ietf/rfc3915.txt', 25 | 'vendor/ietf/rgp-1.0.xsd', 26 | ] 27 | 28 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 29 | gem.require_paths = ['lib'] 30 | 31 | gem.add_development_dependency "bundler", ">= 1.0.0" 32 | gem.add_dependency('nokogiri', '~> 1.4') 33 | gem.add_dependency('builder', '>= 2.1.2') 34 | end 35 | -------------------------------------------------------------------------------- /lib/epp-client/ssl.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | module SSL 3 | attr_reader :ssl_cert, :ssl_key 4 | 5 | def ssl_key=(key) #:nodoc: 6 | case key 7 | when OpenSSL::PKey::RSA 8 | @ssl_key = key 9 | when String 10 | unless key =~ /-----BEGIN RSA PRIVATE KEY-----/ 11 | key = File.read(key) 12 | end 13 | @ssl_key = OpenSSL::PKey::RSA.new(key) 14 | else 15 | raise ArgumentError, "Must either be an OpenSSL::PKey::RSA object, a filename or a key" 16 | end 17 | end 18 | 19 | def ssl_cert=(cert) #:nodoc: 20 | case cert 21 | when OpenSSL::X509::Certificate 22 | @ssl_cert = cert 23 | when String 24 | unless cert =~ /-----BEGIN CERTIFICATE-----/ 25 | cert = File.read(cert) 26 | end 27 | @ssl_cert = OpenSSL::X509::Certificate.new(cert) 28 | else 29 | raise ArgumentError, "Must either be an OpenSSL::X509::Certificate object, a filename or a certificate" 30 | end 31 | end 32 | 33 | def open_connection # :nodoc: 34 | @context.cert ||= ssl_cert if ssl_cert.is_a?(OpenSSL::X509::Certificate) 35 | @context.key ||= ssl_key if ssl_key.is_a?(OpenSSL::PKey::RSA) 36 | super 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require 'rake' 3 | require 'rdoc/task' 4 | require 'rubygems/package_task' 5 | require "bundler/gem_helper" 6 | 7 | MY_GEMS = Dir['*.gemspec'].map {|g| g.sub(/.*-(.*)\.gemspec/, '\1')} 8 | 9 | MY_GEMS.each do |g| 10 | namespace g do 11 | bgh = Bundler::GemHelper.new(Dir.pwd, "epp-client-#{g}") 12 | bgh.install 13 | task :push do 14 | sh "gem push #{File.join(Dir.pwd, 'pkg', "epp-client-#{g}-#{bgh.__send__(:version)}.gem")}" 15 | end 16 | end 17 | end 18 | 19 | namespace :all do 20 | task :build => MY_GEMS.map { |f| "#{f}:build" } 21 | task :install => MY_GEMS.map { |f| "#{f}:install" } 22 | task :push => MY_GEMS.map { |f| "#{f}:push" } 23 | end 24 | 25 | task :build => 'all:build' 26 | task :install => 'all:install' 27 | task :push => 'all:push' 28 | 29 | desc "Generate documentation for the Rails framework" 30 | Rake::RDocTask.new do |rdoc| 31 | rdoc.rdoc_dir = 'doc/rdoc' 32 | rdoc.title = "Documentation" 33 | 34 | rdoc.options << '--line-numbers' << '--inline-source' 35 | rdoc.options << '--charset' << 'utf-8' 36 | 37 | rdoc.rdoc_files.include('README') 38 | rdoc.rdoc_files.include('ChangeLog') 39 | rdoc.rdoc_files.include('lib/**/*.rb') 40 | end 41 | 42 | -------------------------------------------------------------------------------- /epp-client-secdns.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/epp-client/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.name = 'epp-client-secdns' 6 | gem.version = EPPClient::VERSION 7 | gem.authors = ['Mathieu Arnold'] 8 | gem.email = ['m@absolight.fr'] 9 | gem.description = 'SecDNS EPP client library.' 10 | gem.summary = 'SecDNS EPP client library' 11 | gem.homepage = "https://github.com/Absolight/epp-client" 12 | 13 | gem.required_ruby_version = '>= 1.8.7' 14 | gem.required_rubygems_version = ">= 1.3.6" 15 | 16 | gem.files = [ 17 | 'ChangeLog', 18 | 'Gemfile', 19 | 'MIT-LICENSE', 20 | 'README', 21 | 'Rakefile', 22 | 'epp-client-secdns.gemspec', 23 | 'lib/epp-client/secdns.rb', 24 | 'vendor/ietf/rfc4310.txt', 25 | 'vendor/ietf/rfc5910.txt', 26 | 'vendor/ietf/secDNS-1.0.xsd', 27 | 'vendor/ietf/secDNS-1.1.xsd', 28 | ] 29 | 30 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 31 | gem.require_paths = ['lib'] 32 | 33 | gem.add_development_dependency "bundler", ">= 1.0.0" 34 | gem.add_dependency('nokogiri', '~> 1.4') 35 | gem.add_dependency('builder', '>= 2.1.2') 36 | end 37 | -------------------------------------------------------------------------------- /lib/epp-client/session.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | module Session 3 | 4 | # Sends an hello epp command. 5 | def hello 6 | send_request(command do |xml| 7 | xml.hello 8 | end) 9 | end 10 | 11 | def login_xml(new_pw = nil) #:nodoc: 12 | command do |xml| 13 | xml.login do 14 | xml.clID(@client_id) 15 | xml.pw(@password) 16 | xml.newPW(new_pw) unless new_pw.nil? 17 | xml.options do 18 | xml.version(@version) 19 | xml.lang(@lang) 20 | end 21 | xml.svcs do 22 | services.each do |s| 23 | xml.objURI(s) 24 | end 25 | unless extensions.empty? 26 | xml.svcExtension do 27 | extensions.each do |e| 28 | xml.extURI(e) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | private :login_xml 37 | 38 | # Perform the login command on the server. Takes an optionnal argument, the 39 | # new password for the account. 40 | def login(new_pw = nil) 41 | response = send_request(login_xml(new_pw)) 42 | 43 | get_result(response) 44 | end 45 | 46 | # Performs the logout command, after it, the server terminates the 47 | # connection. 48 | def logout 49 | response = send_request(command do |xml| 50 | xml.logout 51 | end) 52 | 53 | get_result(response) 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /epp-client-smallregistry.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/epp-client/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.name = 'epp-client-smallregistry' 6 | gem.version = EPPClient::VERSION 7 | gem.authors = ['Mathieu Arnold'] 8 | gem.email = ['m@absolight.fr'] 9 | gem.description = 'Smallregistry EPP client library.' 10 | gem.summary = 'Smallregistry EPP client library' 11 | gem.homepage = "https://github.com/Absolight/epp-client" 12 | 13 | gem.required_ruby_version = '>= 1.8.7' 14 | gem.required_rubygems_version = ">= 1.3.6" 15 | 16 | gem.files = [ 17 | 'ChangeLog', 18 | 'EXAMPLE.SMALLREGISTRY', 19 | 'Gemfile', 20 | 'MIT-LICENSE', 21 | 'README', 22 | 'Rakefile', 23 | 'epp-client-smallregistry.gemspec', 24 | 'lib/epp-client/smallregistry.rb', 25 | 'vendor/smallregistry/sr-1.0.xsd', 26 | ] 27 | 28 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 29 | gem.require_paths = ['lib'] 30 | 31 | gem.add_development_dependency "bundler", ">= 1.0.0" 32 | gem.add_dependency('nokogiri', '~> 1.4') 33 | gem.add_dependency('builder', '>= 2.1.2') 34 | gem.add_dependency('epp-client-base', "#{EPPClient::VERSION}") 35 | gem.add_dependency('epp-client-secdns', "#{EPPClient::VERSION}") 36 | end 37 | -------------------------------------------------------------------------------- /epp-client-afnic.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/epp-client/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.name = 'epp-client-afnic' 6 | gem.version = EPPClient::VERSION 7 | gem.authors = ['Mathieu Arnold'] 8 | gem.email = ['m@absolight.fr'] 9 | gem.description = 'AFNIC EPP client library.' 10 | gem.summary = 'AFNIC EPP client library' 11 | gem.homepage = "https://github.com/Absolight/epp-client" 12 | 13 | gem.required_ruby_version = '>= 1.8.7' 14 | gem.required_rubygems_version = ">= 1.3.6" 15 | 16 | gem.files = [ 17 | 'ChangeLog', 18 | 'EXAMPLE.AFNIC', 19 | 'Gemfile', 20 | 'MIT-LICENSE', 21 | 'README', 22 | 'Rakefile', 23 | 'epp-client-afnic.gemspec', 24 | 'lib/epp-client/afnic.rb', 25 | 'vendor/afnic/frnic-1.0.xsd', 26 | 'vendor/afnic/frnic-1.1.xsd', 27 | 'vendor/afnic/frnic-1.2.xsd', 28 | ] 29 | 30 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 31 | gem.require_paths = ['lib'] 32 | 33 | gem.add_development_dependency "bundler", ">= 1.0.0" 34 | gem.add_dependency('nokogiri', '~> 1.4') 35 | gem.add_dependency('builder', '>= 2.1.2') 36 | gem.add_dependency('epp-client-base', "#{EPPClient::VERSION}") 37 | gem.add_dependency('epp-client-rgp', "#{EPPClient::VERSION}") 38 | gem.add_dependency('epp-client-secdns', "#{EPPClient::VERSION}") 39 | end 40 | -------------------------------------------------------------------------------- /epp-client-base.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/epp-client/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.name = 'epp-client-base' 6 | gem.version = EPPClient::VERSION 7 | gem.authors = ['Mathieu Arnold'] 8 | gem.email = ['m@absolight.fr'] 9 | gem.description = 'An extensible EPP client library.' 10 | gem.summary = 'An extensible EPP client library' 11 | gem.homepage = "https://github.com/Absolight/epp-client" 12 | 13 | gem.required_ruby_version = '>= 1.8.7' 14 | gem.required_rubygems_version = ">= 1.3.6" 15 | 16 | gem.files = [ 17 | 'ChangeLog', 18 | 'Gemfile', 19 | 'MIT-LICENSE', 20 | 'README', 21 | 'Rakefile', 22 | 'epp-client-base.gemspec', 23 | 'lib/epp-client/base.rb', 24 | 'lib/epp-client/connection.rb', 25 | 'lib/epp-client/contact.rb', 26 | 'lib/epp-client/domain.rb', 27 | 'lib/epp-client/exceptions.rb', 28 | 'lib/epp-client/poll.rb', 29 | 'lib/epp-client/session.rb', 30 | 'lib/epp-client/ssl.rb', 31 | 'lib/epp-client/version.rb', 32 | 'lib/epp-client/xml.rb', 33 | 'vendor/ietf/contact-1.0.xsd', 34 | 'vendor/ietf/domain-1.0.xsd', 35 | 'vendor/ietf/epp-1.0.xsd', 36 | 'vendor/ietf/eppcom-1.0.xsd', 37 | 'vendor/ietf/host-1.0.xsd', 38 | 'vendor/ietf/rfc4310.txt', 39 | 'vendor/ietf/rfc5730.txt', 40 | 'vendor/ietf/rfc5731.txt', 41 | 'vendor/ietf/rfc5732.txt', 42 | 'vendor/ietf/rfc5733.txt', 43 | 'vendor/ietf/rfc5734.txt', 44 | 'vendor/ietf/rfc5910.txt', 45 | ] 46 | 47 | gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 48 | gem.require_paths = ['lib'] 49 | 50 | gem.add_development_dependency "bundler", ">= 1.0.0" 51 | gem.add_dependency('nokogiri', '~> 1.4') 52 | gem.add_dependency('builder', '>= 2.1.2') 53 | end 54 | -------------------------------------------------------------------------------- /EXAMPLE.SMALLREGISTRY: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'pp' 4 | require 'rubygems' 5 | require 'epp-client/smallregistry' 6 | 7 | c = EPPClient::SmallRegistry.new( 8 | :client_id => 'test', 9 | :password => 'test', 10 | :ssl_cert => 'test.crt', 11 | :ssl_key => 'test.key', 12 | :test => true 13 | ) 14 | 15 | begin 16 | 17 | c.open_connection 18 | c.login 19 | pp c.domain_check('mat.cc', 'bidule-truc.aeroport.fr', 'truc-chose.aeroport.fr') 20 | pp c.domain_info('truc-chose.aeroport.fr') 21 | pp c.contact_check('PP1-SMALL', 'PP2-SMALL', 'foobar') 22 | pp c.contact_info('PP1-SMALL') 23 | pp contact = c.contact_create( { 24 | :email=>"foo@example.org", 25 | :authInfo => "foobar", 26 | :voice=>"+33.821801821", 27 | :postalInfo=> { "loc"=> { :addr=> { :city=>"Paris", :cc=>"FR", :pc=>"75001", :street=>["BP 18", "1, rue Royale"] }, :name=>"Dupond, Jean", :org => "FooBar Inc." }, }, 28 | # l'un ou l'autre 29 | #:person => { :birthDate => '1978-01-01', :birthPlace => 'Paris', }, 30 | :org => { :companySerial => '505404125', }, 31 | }) 32 | pp c.contact_info(contact[:id]) 33 | pp c.contact_update({ 34 | :id => contact[:id], 35 | :chg => { 36 | :email => 'bazar@example.com', 37 | :org => { :companySerial => '505404126', }, 38 | }}) 39 | pp c.contact_delete(contact[:id]) 40 | pp c.contact_info(contact[:id]) 41 | pp c.domain_create({ 42 | :registrant => "PP1-SMALL", 43 | :contacts => {:billing => ["PP1-SMALL"], :tech => ["PP1-SMALL"], :admin => ["PP1-SMALL"]}, 44 | :ns => ["ns1.absolight.net", "ns2.absolight.net", "ns3.absolight.net", "ns4.absolight.net"], 45 | #:ns => [{:hostAddrv4 => ["1.2.3.4", "1.4.2.3"], :hostName => "ns1.truc-#{$$}.aeroport.fr"}, {:hostAddrv4 => ["1.2.3.5"], :hostAddrv6 => ["2a01:678::1", "2a01:678::2"], :hostName => "ns2.truc-#{$$}.aeroport.fr"}], 46 | :name => "truc-#{$$}.aeroport.fr", 47 | :authInfo => "PN16IZ0V-XLYDZC-AT70X58L" 48 | }) 49 | pp c.domain_info("truc-#{$$}.aeroport.fr") 50 | pp c.domain_update({ 51 | :name => "truc-#{$$}.aeroport.fr", 52 | :chg => { :authInfo => 'Bazar' }, 53 | :rem => { 54 | :ns => ["ns4.absolight.net"], 55 | } 56 | }) 57 | pp c.domain_info("truc-#{$$}.aeroport.fr") 58 | pp c.domain_delete('truc-70487.aeroport.fr') 59 | ensure 60 | c.logout 61 | end 62 | -------------------------------------------------------------------------------- /lib/epp-client/poll.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | module Poll 3 | def poll_req_xml #:nodoc: 4 | command do |xml| 5 | xml.poll(:op => :req) 6 | end 7 | end 8 | 9 | # sends a command to the server. 10 | # 11 | # if there is a message in the queue, returns a hash with the following keys : 12 | # [:qDate] the date and time that the message was enqueued. 13 | # [:msg, :msg_xml] 14 | # a human readble message, the :msg version has all the possible 15 | # xml stripped, whereas the :msg_xml contains the original 16 | # message. 17 | # [:obj, :obj_xml] 18 | # contains a possible object, the original one in 19 | # :obj_xml, and if a parser is available, the parsed one in 20 | # :obj. 21 | def poll_req 22 | response = send_request(poll_req_xml) 23 | 24 | get_result(:xml => response, :callback => :poll_req_process) 25 | end 26 | 27 | PARSERS = {} 28 | 29 | def poll_req_process(xml) #:nodoc: 30 | ret = {} 31 | if (date = xml.xpath("epp:msgQ/epp:qDate", EPPClient::SCHEMAS_URL)).size > 0 32 | ret[:qDate] = DateTime.parse(date.text) 33 | end 34 | if (msg = xml.xpath("epp:msgQ/epp:msg", EPPClient::SCHEMAS_URL)).size > 0 35 | ret[:msg] = msg.text 36 | ret[:msg_xml] = msg.to_s 37 | end 38 | if (obj = xml.xpath('epp:resData', EPPClient::SCHEMAS_URL)).size > 0 || 39 | (obj = xml.xpath('epp:extension', EPPClient::SCHEMAS_URL)).size > 0 40 | ret[:obj_xml] = obj.to_s 41 | PARSERS.each do |xpath,parser| 42 | if obj.xpath(xpath, EPPClient::SCHEMAS_URL).size > 0 43 | ret[:obj] = case parser 44 | when Symbol 45 | send(parser, xml) 46 | else 47 | raise NotImplementedError 48 | end 49 | end 50 | end 51 | end 52 | ret 53 | end 54 | 55 | def poll_ack_xml(mid) #:nodoc: 56 | command do |xml| 57 | xml.poll(:op => :ack, :msgID => mid) 58 | end 59 | end 60 | 61 | # sends a command to the server. 62 | # Most of the time, you should not pass any argument, as it will "do the 63 | # right thing". 64 | def poll_ack(mid = @msgQ_id) 65 | response = send_request(poll_ack_xml(mid)) 66 | 67 | get_result(response) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /vendor/smallregistry/sr-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Extensible Provisioning Protocol v1.0 21 | SmallRegistry extensions. 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 76 | 77 | -------------------------------------------------------------------------------- /lib/epp-client/connection.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | module Connection 3 | 4 | attr_reader :sent_frame, :recv_frame, :srv_version, :srv_lang, :srv_ns, :srv_ext 5 | 6 | # Establishes the connection to the server, if successful, will return the 7 | # greeting frame. 8 | def open_connection 9 | @tcpserver = TCPSocket.new(server, port) 10 | @socket = OpenSSL::SSL::SSLSocket.new(@tcpserver, @context) 11 | 12 | # Synchronously close the connection & socket 13 | @socket.sync_close 14 | 15 | # Connect 16 | @socket.connect 17 | 18 | # Get the initial greeting frame 19 | greeting_process(get_frame) 20 | end 21 | 22 | def greeting_process(xml) #:nodoc: 23 | @srv_version = xml.xpath('epp:epp/epp:greeting/epp:svcMenu/epp:version', EPPClient::SCHEMAS_URL).map {|n| n.text} 24 | @srv_lang = xml.xpath('epp:epp/epp:greeting/epp:svcMenu/epp:lang', EPPClient::SCHEMAS_URL).map {|n| n.text} 25 | @srv_ns = xml.xpath('epp:epp/epp:greeting/epp:svcMenu/epp:objURI', EPPClient::SCHEMAS_URL).map {|n| n.text} 26 | if (ext = xml.xpath('epp:epp/epp:greeting/epp:svcMenu/epp:svcExtension/epp:extURI', EPPClient::SCHEMAS_URL)).size > 0 27 | @srv_ext = ext.map {|n| n.text} 28 | end 29 | 30 | return xml 31 | end 32 | 33 | # Gracefully close the connection 34 | def close_connection 35 | if defined?(@socket) and @socket.is_a?(OpenSSL::SSL::SSLSocket) 36 | @socket.close 37 | @socket = nil 38 | end 39 | 40 | if defined?(@tcpserver) and @tcpserver.is_a?(TCPSocket) 41 | @tcpserver.close 42 | @tcpserver = nil 43 | end 44 | 45 | return true if @tcpserver.nil? and @socket.nil? 46 | end 47 | 48 | # Sends a frame and returns the server's answer 49 | def send_request(xml) 50 | send_frame(xml) 51 | get_frame 52 | end 53 | 54 | # sends a frame 55 | def send_frame(xml) 56 | @sent_frame = xml 57 | @socket.write([xml.size + 4].pack("N") + xml) 58 | sent_frame_to_xml 59 | return 60 | end 61 | 62 | # gets a frame from the socket and returns the parsed response. 63 | def get_frame 64 | size = @socket.read(4) 65 | if size.nil? 66 | if @socket.eof? 67 | raise SocketError, "Connection closed by remote server" 68 | else 69 | raise SocketError, "Error reading frame from remote server" 70 | end 71 | else 72 | size = size.unpack('N')[0] 73 | @recv_frame = @socket.read(size - 4) 74 | recv_frame_to_xml 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /EXAMPLE.AFNIC: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'pp' 4 | require 'rubygems' 5 | require 'epp-client/afnic' 6 | 7 | c = EPPClient::AFNIC.new( 8 | :client_id => '-test-.fr', 9 | :password => 'pass', 10 | :ssl_cert => 'server.crt', 11 | :ssl_key => 'server.key', 12 | :test => true 13 | ) 14 | 15 | begin 16 | 17 | c.open_connection 18 | c.login 19 | if c.msgQ_count > 0 20 | pp msg = c.poll_req 21 | pp c.poll_ack if Hash === msg[:obj] 22 | end 23 | pp c.domain_check('mat.fr', 'nonexistantdomain.fr', 'paris.fr', 'trafiquants.fr', 'toto.wf') 24 | pp c.domain_info('afnic.fr') 25 | # pas de contact check 26 | pp c.contact_info('A7534') # legal 27 | pp c.contact_info('CT214') # particulier 28 | pp contact = c.contact_create( { 29 | :email=>"foo@example.org", 30 | :authInfo => "foobar", 31 | :voice=>"+33.123456787", 32 | :postalInfo=> { "loc"=> { :addr=> { :city=>"Paris", :cc=>"FR", :pc=>"75001", :street=>["BP 18", "1, rue Royale"] }, :name=>"Dupond", :org => 'FooBar Inc.' }, }, 33 | #:list => 'restrictedPublication', 34 | #:individualInfos => {:birthDate => '1978-01-01', :birthCc => 'FR', :birthPc => '75013', :birthCity => 'Paris'}, 35 | #:firstName => 'Jean', 36 | :legalEntityInfos=> {:siren=>"418565404", :legalStatus=>"company"}, 37 | #:legalEntityInfos=> {:siren=>"418565404", :legalStatus=>"association", :asso => { :decl => '2001-01-01', :publ => {:date => '2001-01-02', :announce => 5, :page => 3}}}, 38 | #:legalEntityInfos=> {:siren=>"418565404", :legalStatus=>"association", :asso => { :waldec => 2 }}, 39 | }) 40 | pp c.contact_info(contact[:id]) 41 | pp c.contact_update({ 42 | :id => contact[:id], 43 | :chg => { 44 | :email => 'bazar@example.com', 45 | }}) 46 | pp c.contact_info(contact[:id]) 47 | pp c.contact_info(contact[:id]) 48 | pp c.domain_create({ 49 | :registrant => "A7534", 50 | :contacts => {:tech => ["ADM3"], :admin => ["ADM3"]}, 51 | :name => "truc-#{$$}.fr", 52 | :authInfo => "PN16IZ0V" 53 | }) 54 | pp c.domain_info(:name => "truc-#{$$}.fr") 55 | pp c.domain_update({ 56 | :name => "truc-#{$$}.fr", 57 | #:chg => { :authInfo => 'bazar' }, 58 | :add => { 59 | :ns => %w(ns1.absolight.net ns2.absolight.net ns3.absolight.net ns4.absolight.net), 60 | }, 61 | }) 62 | pp c.domain_info(:name => "truc-#{$$}.fr") 63 | pp c.domain_delete("truc-#{$$}.fr") 64 | pp c.domain_info(:name => "truc-#{$$}.fr") 65 | pp c.domain_restore(:name => "truc-#{$$}.fr") 66 | pp c.domain_info(:name => "truc-#{$$}.fr") 67 | 68 | ensure 69 | c.logout 70 | end 71 | -------------------------------------------------------------------------------- /vendor/ietf/secDNS-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | Extensible Provisioning Protocol v1.0 11 | domain name extension schema for provisioning 12 | DNS security (DNSSEC) extensions. 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 93 | 94 | -------------------------------------------------------------------------------- /vendor/ietf/eppcom-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | Extensible Provisioning Protocol v1.0 11 | shared structures schema. 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 105 | 106 | -------------------------------------------------------------------------------- /lib/epp-client/rgp.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | # RFC3915[http://tools.ietf.org/html/rfc3915] 3 | # 4 | # Domain Registry Grace Period Mapping for the 5 | # Extensible Provisioning Protocol (EPP) 6 | # 7 | # Has to be included after the initialize, domain_info and domain_update 8 | # definitions. 9 | module RGP 10 | def initialize(args) #:nodoc: 11 | super 12 | @extensions << EPPClient::SCHEMAS_URL['rgp'] 13 | end 14 | 15 | def domain_info_process(xml) #:nodoc: 16 | ret = super(xml) 17 | if (rgp_status = xml.xpath('epp:extension/rgp:infData/rgp:rgpStatus', EPPClient::SCHEMAS_URL)).size > 0 18 | ret[:rgpStatus] = rgp_status.map {|s| s.attr('s')} 19 | end 20 | ret 21 | end 22 | 23 | def domain_restore_xml(args) #:nodoc: 24 | command(lambda do |xml| 25 | xml.update do 26 | xml.update('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do 27 | xml.name args[:name] 28 | end 29 | end 30 | end, lambda do |xml| 31 | xml.update('xmlns' => EPPClient::SCHEMAS_URL['rgp-1.0']) do 32 | if args.key?(:report) 33 | xml.restore(:op => 'report') do 34 | [:preData, :postData, :delTime, :resTime, :resReason].each do |v| 35 | xml.__send__(v, args[:report][v]) 36 | end 37 | args[:report][:statements].each do |s| 38 | xml.statement s 39 | end 40 | if args[:report].key?(:other) 41 | xml.other args[:report][:other] 42 | end 43 | end 44 | else 45 | xml.restore(:op => 'request') 46 | end 47 | end 48 | end) 49 | end 50 | 51 | # Restores a domain. 52 | # 53 | # takes a hash as arguments, with the following keys : 54 | # [:name] 55 | # the fully qualified name of the domain object to be updated. 56 | # [:report] 57 | # the optional report with the following fields : 58 | # [:preData] 59 | # a copy of the registration data that existed for the domain name prior 60 | # to the domain name being deleted. 61 | # [:postData] 62 | # a copy of the registration data that exists for the domain name at the 63 | # time the restore report is submitted. 64 | # [:delTime] 65 | # the date and time when the domain name delete request was sent to the 66 | # server. 67 | # [:resTime] 68 | # the date and time when the original command was sent to 69 | # the server. 70 | # [:resReason] 71 | # a brief explanation of the reason for restoring the domain name. 72 | # [:statements] 73 | # an array of two statements : 74 | # 1. a text statement that the client has not restored the domain name in 75 | # order to assume the rights to use or sell the domain name for itself 76 | # or for any third party. Supporting information related to this 77 | # statement MAY be supplied in the :other element described 78 | # below. 79 | # 2. a text statement that the information in the restore report is 80 | # factual to the best of the client's knowledge. 81 | # [:other] 82 | # an optional element that contains any information needed to support the 83 | # statements provided by the client. 84 | # 85 | # Returns an array of rgpStatus. 86 | def domain_restore(args) 87 | response = send_request(domain_restore_xml(args)) 88 | 89 | get_result(:xml => response, :callback => :domain_restore_process) 90 | end 91 | 92 | def domain_restore_process(xml) #:nodoc: 93 | xml.xpath('epp:extension/rgp:upData/rgp:rgpStatus', EPPClient::SCHEMAS_URL).map {|s| s.attr('s')} 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/epp-client/base.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | require 'socket' 3 | require 'nokogiri' 4 | require 'builder' 5 | require 'date' 6 | require "epp-client/version" 7 | require 'epp-client/xml' 8 | require 'epp-client/session' 9 | require 'epp-client/connection' 10 | require 'epp-client/exceptions' 11 | require 'epp-client/ssl' 12 | require 'epp-client/poll' 13 | require 'epp-client/domain' 14 | require 'epp-client/contact' 15 | 16 | module EPPClient 17 | class Base 18 | 19 | SCHEMAS = %w[ 20 | epp-1.0 21 | domain-1.0 22 | host-1.0 23 | contact-1.0 24 | ] 25 | SCHEMAS_EXT_IETF = %w[ 26 | rgp-1.0 27 | ] 28 | 29 | EPPClient::SCHEMAS_URL = SCHEMAS.inject({}) do |a,s| 30 | a[s.sub(/-1\.0$/, '')] = "urn:ietf:params:xml:ns:#{s}" if s =~ /-1\.0$/ 31 | a[s] = "urn:ietf:params:xml:ns:#{s}" 32 | a 33 | end.merge!(SCHEMAS_EXT_IETF.inject({}) do |a,s| 34 | a[s.sub(/-1\.0$/, '')] = "urn:ietf:params:xml:ns:#{s}" if s =~ /-1\.0$/ 35 | a[s] = "urn:ietf:params:xml:ns:#{s}" 36 | a 37 | end) 38 | 39 | include EPPClient::XML 40 | include EPPClient::Session 41 | include EPPClient::Connection 42 | include EPPClient::SSL 43 | include EPPClient::Poll # keep before object definition modules. 44 | include EPPClient::Domain 45 | include EPPClient::Contact 46 | 47 | attr_accessor :client_id, :password, :server, :port, :services, :lang, :extensions, :version, :context 48 | attr_writer :clTRID 49 | 50 | # ==== Required Attributes 51 | # 52 | # [:server] The EPP server to connect to 53 | # [:client_id] 54 | # The tag or username used with requests. 55 | # [:password] The password used with requests. 56 | # 57 | # ==== Optional Attributes 58 | # 59 | # [:port] 60 | # The EPP standard port is 700. However, you can choose a different port to 61 | # use. 62 | # [:clTRID] 63 | # The client transaction identifier is an element that EPP specifies MAY be 64 | # used to uniquely identify the command to the server. The string 65 | # "-" will be added to it, index being incremented at each command. 66 | # Defaults to "test--" 67 | # [:lang] Set custom language attribute. Default is 'en'. 68 | # [:services] 69 | # Use custom EPP services in the frame. The defaults use the EPP 70 | # standard domain, contact and host 1.0 services. 71 | # [:extensions] 72 | # URLs to custom extensions to standard EPP. Use these to extend the 73 | # standard EPP (e.g., AFNIC, smallregistry uses extensions). Defaults to 74 | # none. 75 | # [:version] Set the EPP version. Defaults to "1.0". 76 | # [:ssl_cert] The file containing the client certificate. 77 | # [:ssl_key] The file containing the key of the certificate. 78 | def initialize(attrs) 79 | unless attrs.key?(:server) && attrs.key?(:client_id) && attrs.key?(:password) 80 | raise ArgumentError, "server, client_id and password are required" 81 | end 82 | 83 | attrs.each do |k,v| 84 | begin 85 | self.send("#{k}=", v) 86 | rescue NoMethodError 87 | raise ArgumentError, "there is no #{k} argument" 88 | end 89 | end 90 | 91 | @port ||= 700 92 | @lang ||= "en" 93 | @services ||= EPPClient::SCHEMAS_URL.values_at('domain', 'contact', 'host') 94 | @extensions ||= [] 95 | @version ||= "1.0" 96 | @clTRID ||= "test-#{$$}-#{rand(1000)}" 97 | @clTRID_index = 0 98 | 99 | @context ||= OpenSSL::SSL::SSLContext.new 100 | 101 | @logged_in = false 102 | end 103 | 104 | def clTRID # :nodoc: 105 | @clTRID_index += 1 106 | @clTRID + "-#{@clTRID_index}" 107 | end 108 | 109 | def debug 110 | $DEBUG || ENV['EPP_CLIENT_DEBUG'] 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /vendor/ietf/secDNS-1.1.xsd: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | Extensible Provisioning Protocol v1.0 11 | domain name extension schema 12 | for provisioning DNS security (DNSSEC) extensions. 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 30 | 31 | 33 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 91 | 93 | 95 | 96 | 97 | 98 | 99 | 102 | 103 | 104 | 105 | 107 | 109 | 110 | 111 | 112 | 115 | 116 | 117 | 119 | 120 | 121 | 122 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /vendor/ietf/rgp-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | Extensible Provisioning Protocol v1.0 11 | domain name extension schema for registry grace period 12 | processing. 13 | 14 | 15 | 16 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 90 | 91 | 92 | 93 | 96 | 97 | 98 | 100 | 101 | 102 | 103 | 108 | 109 | 110 | 111 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 133 | 134 | -------------------------------------------------------------------------------- /lib/epp-client/xml.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | module XML 3 | 4 | attr_reader :sent_xml, :recv_xml, :msgQ_count, :msgQ_id, :trID 5 | 6 | # Parses a frame and returns a Nokogiri::XML::Document. 7 | def parse_xml(string) #:doc: 8 | Nokogiri::XML::Document.parse(string) do |opts| 9 | opts.options = 0 10 | opts.noblanks 11 | end 12 | end 13 | private :parse_xml 14 | 15 | def recv_frame_to_xml #:nodoc: 16 | @recv_xml = parse_xml(@recv_frame) 17 | puts @recv_xml.to_s.gsub(/^/, '<< ') if debug 18 | return @recv_xml 19 | end 20 | 21 | def sent_frame_to_xml #:nodoc: 22 | @send_xml = parse_xml(@sent_frame) 23 | puts @send_xml.to_s.gsub(/^/, '>> ') if debug 24 | return @send_xml 25 | end 26 | 27 | def raw_builder(opts = {}) #:nodoc: 28 | xml = Builder::XmlMarkup.new(opts) 29 | yield xml 30 | end 31 | 32 | # creates a Builder::XmlMarkup object, mostly only used by +command+ 33 | def builder(opts = {}) 34 | raw_builder(opts) do |xml| 35 | xml.instruct! :xml, :version =>"1.0", :encoding => "UTF-8" 36 | xml.epp('xmlns' => EPPClient::SCHEMAS_URL['epp'], 'xmlns:epp' => EPPClient::SCHEMAS_URL['epp']) do 37 | yield xml 38 | end 39 | end 40 | end 41 | 42 | # Takes a xml response and checks that the result is in the right range of 43 | # results, that is, between 1000 and 1999, which are results meaning all 44 | # went well. 45 | # 46 | # In case all went well, it either calls the callback if given, or returns 47 | # true. 48 | # 49 | # In case there was a problem, an EPPErrorResponse exception is raised. 50 | def get_result(args) 51 | xml = case args 52 | when Hash 53 | args.delete(:xml) 54 | else 55 | xml = args 56 | args = {} 57 | xml 58 | end 59 | 60 | args[:range] ||= 1000..1999 61 | 62 | if (mq = xml.xpath('epp:epp/epp:response/epp:msgQ', EPPClient::SCHEMAS_URL)).size > 0 63 | @msgQ_count = mq.attribute('count').value.to_i 64 | @msgQ_id = mq.attribute('id').value 65 | puts "DEBUG: MSGQ : count=#{@msgQ_count}, id=#{@msgQ_id}\n" if debug 66 | else 67 | @msgQ_count = 0 68 | @msgQ_id = nil 69 | end 70 | 71 | if (trID = xml.xpath('epp:epp/epp:response/epp:trID', EPPClient::SCHEMAS_URL)).size > 0 72 | @trID = get_trid(trID) 73 | end 74 | 75 | res = xml.xpath('epp:epp/epp:response/epp:result', EPPClient::SCHEMAS_URL) 76 | code = res.attribute('code').value.to_i 77 | if args[:range].include?(code) 78 | if args.key?(:callback) 79 | case cb = args[:callback] 80 | when Symbol 81 | return send(cb, xml.xpath('epp:epp/epp:response', EPPClient::SCHEMAS_URL)) 82 | else 83 | raise ArgumentError, "Invalid callback type" 84 | end 85 | else 86 | return true 87 | end 88 | else 89 | raise EPPClient::EPPErrorResponse.new(:xml => xml, :code => code, :message => res.xpath('epp:msg', EPPClient::SCHEMAS_URL).text) 90 | end 91 | end 92 | 93 | def get_trid(xml) 94 | { 95 | :clTRID => xml.xpath('epp:clTRID', EPPClient::SCHEMAS_URL).text, 96 | :svTRID => xml.xpath('epp:svTRID', EPPClient::SCHEMAS_URL).text, 97 | } 98 | end 99 | 100 | # Creates the xml for the command. 101 | # 102 | # You can either pass a block to it, in that case, it's the command body, 103 | # or a series of procs, the first one being the commands, the other ones 104 | # being the extensions. 105 | # 106 | # command do |xml| 107 | # xml.logout 108 | # end 109 | # 110 | # or 111 | # 112 | # command(lambda do |xml| 113 | # xml.logout 114 | # end, lambda do |xml| 115 | # xml.extension 116 | # end) 117 | def command(*args, &block) 118 | builder do |xml| 119 | xml.command do 120 | if block_given? 121 | yield xml 122 | else 123 | command = args.shift 124 | command.call(xml) 125 | args.each do |ext| 126 | xml.extension do 127 | ext.call(xml) 128 | end 129 | end 130 | end 131 | xml.clTRID(clTRID) 132 | end 133 | end 134 | end 135 | 136 | # Wraps the content in an epp:extension. 137 | def extension 138 | raw_builder do |xml| 139 | xml.extension do 140 | yield(xml) 141 | end 142 | end 143 | end 144 | 145 | # Insert xml2 in xml1 before pattern 146 | def insert_extension(xml1, xml2, pattern = //) 147 | xml1.sub(pattern, "#{xml2}\\&") 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/epp-client/smallregistry.rb: -------------------------------------------------------------------------------- 1 | require 'epp-client/base' 2 | require 'epp-client/secdns' 3 | 4 | module EPPClient 5 | class SmallRegistry < Base 6 | 7 | SCHEMAS_SR = %w[ 8 | sr-1.0 9 | ] 10 | 11 | EPPClient::SCHEMAS_URL.merge!(SCHEMAS_SR.inject({}) do |a,s| 12 | a[s.sub(/-1\.0$/, '')] = "https://www.smallregistry.net/schemas/#{s}.xsd" if s =~ /-1\.0$/ 13 | a[s] = "https://www.smallregistry.net/schemas/#{s}.xsd" 14 | a 15 | end) 16 | 17 | # 18 | # Sets the default for Smallregistry, that is, server and port, according 19 | # to Smallregistry's documentation. 20 | # https://www.smallregistry.net/faqs/quelles-sont-les-specificites-du-serveur-epp 21 | # 22 | # ==== Required Attributes 23 | # 24 | # [:client_id] the tag or username used with requests. 25 | # [:password] the password used with requests. 26 | # [:ssl_cert] the file containing the client certificate. 27 | # [:ssl_key] the file containing the key of the certificate. 28 | # 29 | # ==== Optional Attributes 30 | # [:test] sets the server to be the test server. 31 | # 32 | # See EPPClient for other attributes. 33 | def initialize(attrs) 34 | unless attrs.key?(:client_id) && attrs.key?(:password) && attrs.key?(:ssl_cert) && attrs.key?(:ssl_key) 35 | raise ArgumentError, "client_id, password, ssl_cert and ssl_key are required" 36 | end 37 | if attrs.delete(:test) == true 38 | attrs[:server] ||= 'epp.test.smallregistry.net' 39 | attrs[:port] ||= 2700 40 | else 41 | attrs[:server] ||= 'epp.smallregistry.net' 42 | attrs[:port] ||= 700 43 | end 44 | @services = EPPClient::SCHEMAS_URL.values_at('domain', 'contact') 45 | super(attrs) 46 | @extensions << EPPClient::SCHEMAS_URL['sr'] 47 | end 48 | 49 | # Extends the EPPClient::Contact#contact_info so that the specific 50 | # smallregistry's informations are processed, the additionnal informations 51 | # are : 52 | # 53 | # one of : 54 | # [:org] 55 | # indicating that the contact is an organisation with the following 56 | # informations : 57 | # [:companySerial] 58 | # the company's SIREN / RPPS / whatever serial number is required. 59 | # [:person] 60 | # indicating that the contact is a human person with the following 61 | # informations : 62 | # [:birthDate] the person's birth date. 63 | # [:birthPlace] the person's birth place. 64 | def contact_info(xml) 65 | super # placeholder so that I can add some doc 66 | end 67 | 68 | def contact_info_process(xml) #:nodoc: 69 | ret = super 70 | if (contact = xml.xpath('epp:extension/sr:ext/sr:infData/sr:contact', EPPClient::SCHEMAS_URL)).size > 0 71 | if (person = contact.xpath('sr:person', EPPClient::SCHEMAS_URL)).size > 0 72 | ret[:person] = { 73 | :birthDate => Date.parse(person.xpath('sr:birthDate', EPPClient::SCHEMAS_URL).text), 74 | :birthPlace => person.xpath('sr:birthPlace', EPPClient::SCHEMAS_URL).text, 75 | } 76 | end 77 | if (org = contact.xpath('sr:org', EPPClient::SCHEMAS_URL)).size > 0 78 | ret[:org] = { :companySerial => org.xpath('sr:companySerial', EPPClient::SCHEMAS_URL).text } 79 | end 80 | end 81 | ret 82 | end 83 | 84 | # Extends the EPPClient::Contact#contact_create so that the specific 85 | # smallregistry's information are sent, the additionnal informations are : 86 | # 87 | # one of : 88 | # [:org] 89 | # indicating that the contact is an organisation with the following 90 | # informations : 91 | # [:companySerial] 92 | # the company's SIREN / RPPS / whatever serial number is required. 93 | # [:person] 94 | # indicating that the contact is a human person with the following 95 | # informations : 96 | # [:birthDate] the person's birth date. 97 | # [:birthPlace] the person's birth place. 98 | def contact_create(contact) 99 | super # placeholder so that I can add some doc 100 | end 101 | 102 | def contact_create_xml(contact) #:nodoc: 103 | ret = super 104 | 105 | ext = extension do |xml| 106 | xml.ext( :xmlns => EPPClient::SCHEMAS_URL['sr']) do 107 | xml.create do 108 | xml.contact do 109 | if contact.key?(:org) 110 | xml.org do 111 | xml.companySerial(contact[:org][:companySerial]) 112 | end 113 | elsif contact.key?(:person) 114 | xml.person do 115 | xml.birthDate(contact[:person][:birthDate]) 116 | xml.birthPlace(contact[:person][:birthPlace]) 117 | end 118 | end 119 | end 120 | end 121 | end 122 | end 123 | 124 | insert_extension(ret, ext) 125 | end 126 | 127 | def contact_update_xml(args) #:nodoc: 128 | ret = super 129 | 130 | if args.key?(:chg) && (args[:chg].key?(:org) || args[:chg].key?(:person)) 131 | ext = extension do |xml| 132 | xml.ext( :xmlns => EPPClient::SCHEMAS_URL['sr']) do 133 | xml.update do 134 | xml.contact do 135 | if args[:chg].key?(:org) 136 | xml.org do 137 | xml.companySerial(args[:chg][:org][:companySerial]) 138 | end 139 | elsif args[:chg].key?(:person) 140 | xml.person do 141 | xml.birthDate(args[:chg][:person][:birthDate]) 142 | xml.birthPlace(args[:chg][:person][:birthPlace]) 143 | end 144 | end 145 | end 146 | end 147 | end 148 | end 149 | 150 | return insert_extension(ret, ext) 151 | else 152 | return ret 153 | end 154 | end 155 | 156 | # Extends the EPPClient::Contact#contact_update so that the specific afnic 157 | # update informations can be sent, the additionnal informations are : 158 | # 159 | # [:chg] 160 | # changes one of : 161 | # [:org] 162 | # indicating that the contact is an organisation with the following 163 | # informations : 164 | # [:companySerial] 165 | # the company's SIREN / RPPS / whatever serial number is required. 166 | # [:person] 167 | # indicating that the contact is a human person with the following 168 | # informations : 169 | # [:birthDate] the person's birth date. 170 | # [:birthPlace] the person's birth place. 171 | # 172 | def contact_update(args) 173 | super # placeholder so that I can add some doc 174 | end 175 | 176 | # keep that at the end. 177 | include EPPClient::SecDNS 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /vendor/ietf/host-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | Extensible Provisioning Protocol v1.0 19 | host provisioning schema. 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 78 | 79 | 80 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 92 | 94 | 96 | 97 | 98 | 99 | 102 | 103 | 104 | 106 | 108 | 109 | 110 | 111 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 151 | 152 | 153 | 154 | 155 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 168 | 169 | 170 | 171 | 172 | 174 | 176 | 177 | 178 | 179 | 181 | 183 | 185 | 186 | 187 | 188 | 192 | 193 | 194 | 195 | 197 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 233 | 234 | 235 | 236 | 237 | 240 | 241 | -------------------------------------------------------------------------------- /lib/epp-client/secdns.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | module SecDNS 3 | SCHEMAS_SECDNS = %w[ 4 | secDNS-1.1 5 | ] 6 | 7 | EPPClient::SCHEMAS_URL.merge!(SCHEMAS_SECDNS.inject({}) do |a,s| 8 | a[s.sub(/-1\.1$/, '')] = "urn:ietf:params:xml:ns:#{s}" if s =~ /-1\.1$/ 9 | a[s] = "urn:ietf:params:xml:ns:#{s}" 10 | a 11 | end) 12 | 13 | def initialize(args) 14 | super 15 | @extensions << EPPClient::SCHEMAS_URL['secDNS-1.1'] 16 | end 17 | 18 | # Extends the EPPClient::Domain#domain_info so that the specific secDNS 19 | # elements can be added. 20 | # 21 | # either: 22 | # [:keyData] 23 | # containing an array of keyData objects with the following fields : 24 | # [:flags] 25 | # The flags field value as described in {section 2.1.1 of RFC 26 | # 4034}[http://tools.ietf.org/html/rfc4034#section-2.1.1]. 27 | # [:protocol] 28 | # The protocol field value as described in {section 2.1.2 of RFC 29 | # 4034}[http://tools.ietf.org/html/rfc4034#section-2.1.2]. 30 | # [:alg] 31 | # The algorithm number field value as described in {section 2.1.3 of RFC 32 | # 4034}[http://tools.ietf.org/html/rfc4034#section-2.1.3]. 33 | # [:pubKey] 34 | # The encoded public key field value as described in {Section 2.1.4 of 35 | # RFC 4034}[http://tools.ietf.org/html/rfc4034#section-2.1.4]. 36 | # [:dsData] 37 | # containing an array of dsData objects with the following fields : 38 | # [:keyTag] 39 | # The key tag value as described in {Section 5.1.1 of RFC 40 | # 4034}[http://tools.ietf.org/html/rfc4034#section-5.1.1]. 41 | # [:alg] 42 | # The algorithm value as described in {Section 5.1.2 of RFC 43 | # 4034}[http://tools.ietf.org/html/rfc4034#section-5.1.2]. 44 | # [:digestType] 45 | # The digest type value as described in {Section 5.1.3 of RFC 46 | # 4034}[http://tools.ietf.org/html/rfc4034#section-5.1.3]. 47 | # [:digest] 48 | # The digest value as described in {Section 5.1.1 of RFC 49 | # 4034}[http://tools.ietf.org/html/rfc4034#section-5.1.1]. 50 | # [:keyData] 51 | # An optional element that describes the key data used as input in the DS 52 | # hash calculation for use in server validation. The :keyData 53 | # element contains the child elements defined above. 54 | # 55 | # Optionnaly : 56 | # [:maxSigLife] 57 | # An element that indicates a child's preference for the number of seconds 58 | # after signature generation when the parent's signature on the DS 59 | # information provided by the child will expire. 60 | def domain_info(domain) 61 | super # placeholder so that I can add some doc 62 | end 63 | 64 | def domain_info_process(xml) #:nodoc: 65 | ret = super 66 | ret_secdns = {} 67 | if (maxSigLife = xml.xpath('epp:extension/secDNS:infData/secDNS:maxSigLife', EPPClient::SCHEMAS_URL)).size > 0 68 | ret_secdns[:maxSigLife] = maxSigLife.text 69 | end 70 | ret_secdns[:dsData] = xml.xpath('epp:extension/secDNS:infData/secDNS:dsData', EPPClient::SCHEMAS_URL).map do |s| 71 | parse_ds_data(s) 72 | end 73 | ret_secdns[:keyData] = xml.xpath('epp:extension/secDNS:infData/secDNS:keyData', EPPClient::SCHEMAS_URL).map do |s| 74 | parse_key_data(s) 75 | end 76 | 77 | ret[:secDNS] = ret_secdns unless ret_secdns.values.reject {|v| v.nil?}.size == 0 78 | ret 79 | end 80 | 81 | # Extends the EPPClient::Domain#domain_create so that the specific secDNS 82 | # create informations can be sent, the additionnal informations are : 83 | # 84 | # either: 85 | # [:keyData] 86 | # containing an array of keyData objects as described in the domain_info function. 87 | # [:dsData] 88 | # containing an array of dsData objects as described in the domain_info function. 89 | # 90 | # Optionnaly : 91 | # [:maxSigLife] 92 | # as described in the domain_info function. 93 | def domain_create(domain) 94 | super # placeholder so that I can add some doc 95 | end 96 | 97 | def domain_create_xml(domain) #:nodoc: 98 | ret = super 99 | 100 | if domain.key?(:maxSigLife) || domain.key?(:dsData) || domain.key?(:keyData) 101 | ext = extension do |xml| 102 | xml.create( :xmlns => EPPClient::SCHEMAS_URL['secDNS']) do 103 | if domain.key?(:maxSigLife) 104 | xml.maxSigLife(domain[:maxSigLife]) 105 | end 106 | if domain.key?(:dsData) 107 | domain[:dsData].each do |ds| 108 | make_ds_data(xml, ds) 109 | end 110 | elsif domain.key?(:keyData) 111 | domain[:keyData].each do |key| 112 | make_key_data(xml, key) 113 | end 114 | end 115 | end 116 | end 117 | return insert_extension(ret, ext) 118 | else 119 | return ret 120 | end 121 | end 122 | 123 | # Extends the EPPClient::Domain#domain_update so that secDNS informations 124 | # can be sent, the additionnal informations are contained in an 125 | # :secDNS object : 126 | # 127 | # [:rem] 128 | # To remove keys or ds from the delegation, with possible attributes one of : 129 | # 130 | # [:all] 131 | # used to remove all DS and key data with a value of boolean true. A 132 | # value of boolean false will do nothing. Removing all DS information 133 | # can remove the ability of the parent to secure the delegation to the 134 | # child zone. 135 | # [:dsData] 136 | # an array of dsData elements described in the domain_info function. 137 | # [:keyData] 138 | # an array of keyData elements as described in the domain_info function. 139 | # 140 | # [:add] 141 | # To add keys or DS from the delegation, with possible attributes one of : 142 | # 143 | # [:dsData] 144 | # an array of dsData elements described in the domain_info function. 145 | # [:keyData] 146 | # an array of keyData elements as described in the domain_info function. 147 | # [:chg] 148 | # contains security information to be changed, one of : 149 | # 150 | # [:maxSigLife] 151 | # optional, as described in the domain_info function. 152 | def domain_update(args) 153 | super # placeholder so that I can add some doc 154 | end 155 | 156 | def domain_update_xml(domain) #:nodoc: 157 | ret = super 158 | 159 | if domain.key?(:secDNS) 160 | sd = domain[:secDNS] 161 | ext = extension do |xml| 162 | xml.update(sd[:urgent] == true ? {:urgent => true}: {}, {:xmlns => EPPClient::SCHEMAS_URL['secDNS']}) do 163 | if sd.key?(:rem) 164 | xml.rem do 165 | if sd[:rem].key?(:all) && sd[:rem][:all] == true 166 | xml.all true 167 | elsif sd[:rem].key?(:dsData) 168 | sd[:rem][:dsData].each do |ds| 169 | make_ds_data(xml, ds) 170 | end 171 | elsif sd[:rem].key?(:keyData) 172 | sd[:rem][:keyData].each do |key| 173 | make_key_data(xml, key) 174 | end 175 | end 176 | end 177 | end 178 | if sd.key?(:add) 179 | xml.add do 180 | if sd[:add].key?(:dsData) 181 | sd[:add][:dsData].each do |ds| 182 | make_ds_data(xml, ds) 183 | end 184 | elsif sd[:add].key?(:keyData) 185 | sd[:add][:keyData].each do |key| 186 | make_key_data(xml, key) 187 | end 188 | end 189 | end 190 | end 191 | if sd.key?(:chg) && sd[:chg].key?(:maxSigLife) 192 | xml.chg do 193 | xml.maxSigLife sd[:chg][:maxSigLife] 194 | end 195 | end 196 | end 197 | end 198 | return insert_extension(ret, ext) 199 | else 200 | return ret 201 | end 202 | end 203 | 204 | private 205 | def make_key_data(xml, key) 206 | xml.keyData do 207 | xml.flags key[:flags] 208 | xml.protocol key[:protocol] 209 | xml.alg key[:alg] 210 | xml.pubKey key[:pubKey] 211 | end 212 | end 213 | def make_ds_data(xml, ds) 214 | xml.dsData do 215 | xml.keyTag ds[:keyTag] 216 | xml.alg ds[:alg] 217 | xml.digestType ds[:digestType] 218 | xml.digest ds[:digest] 219 | make_key_data(xml, ds[:keyData]) if ds.key?(:keyData) 220 | end 221 | end 222 | def parse_key_data(xml) 223 | { 224 | :flags => xml.xpath("secDNS:flags", EPPClient::SCHEMAS_URL).text.to_i, 225 | :protocol => xml.xpath("secDNS:protocol", EPPClient::SCHEMAS_URL).text.to_i, 226 | :alg => xml.xpath("secDNS:alg", EPPClient::SCHEMAS_URL).text.to_i, 227 | :pubKey => xml.xpath("secDNS:pubKey", EPPClient::SCHEMAS_URL).text, 228 | } 229 | end 230 | def parse_ds_data(xml) 231 | ret = { 232 | :keyTag => xml.xpath("secDNS:keyTag", EPPClient::SCHEMAS_URL).text.to_i, 233 | :alg => xml.xpath("secDNS:alg", EPPClient::SCHEMAS_URL).text.to_i, 234 | :digestType => xml.xpath("secDNS:digestType", EPPClient::SCHEMAS_URL).text.to_i, 235 | :digest => xml.xpath("secDNS:digest", EPPClient::SCHEMAS_URL).text 236 | } 237 | if (keyData = xml.xpath('secDNS:keyData', EPPClient::SCHEMAS_URL)).size > 0 238 | ret[:keyData] = parse_key_data(keyData) 239 | end 240 | ret 241 | end 242 | 243 | end 244 | end 245 | -------------------------------------------------------------------------------- /vendor/ietf/contact-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | Extensible Provisioning Protocol v1.0 19 | contact provisioning schema. 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 79 | 80 | 81 | 82 | 84 | 86 | 88 | 89 | 90 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 | 101 | 102 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 117 | 118 | 120 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 137 | 139 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 151 | 152 | 153 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 165 | 166 | 167 | 169 | 170 | 171 | 172 | 175 | 176 | 177 | 178 | 180 | 181 | 182 | 183 | 186 | 187 | 188 | 189 | 191 | 193 | 195 | 196 | 197 | 198 | 201 | 202 | 203 | 205 | 206 | 207 | 208 | 211 | 212 | 213 | 215 | 217 | 219 | 221 | 223 | 225 | 226 | 227 | 228 | 229 | 230 | 232 | 234 | 236 | 237 | 239 | 240 | 241 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 253 | 254 | 255 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 273 | 274 | 275 | 276 | 277 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 290 | 291 | 292 | 293 | 294 | 296 | 298 | 300 | 302 | 303 | 304 | 305 | 306 | 308 | 310 | 312 | 314 | 316 | 317 | 318 | 319 | 323 | 324 | 325 | 326 | 328 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 367 | 368 | 369 | 370 | 371 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 388 | 389 | -------------------------------------------------------------------------------- /vendor/ietf/domain-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Extensible Provisioning Protocol v1.0 21 | domain provisioning schema. 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 43 | 45 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 80 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 91 | 93 | 94 | 95 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 126 | 127 | 128 | 129 | 130 | 131 | 134 | 135 | 136 | 138 | 139 | 140 | 143 | 144 | 145 | 146 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 172 | 173 | 174 | 175 | 176 | 178 | 179 | 180 | 181 | 184 | 185 | 186 | 187 | 189 | 191 | 192 | 193 | 194 | 197 | 198 | 199 | 200 | 202 | 204 | 206 | 207 | 208 | 209 | 212 | 213 | 214 | 216 | 218 | 220 | 221 | 222 | 223 | 226 | 227 | 228 | 230 | 232 | 233 | 234 | 235 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 271 | 272 | 273 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 291 | 292 | 293 | 294 | 295 | 298 | 299 | 300 | 301 | 302 | 304 | 305 | 306 | 307 | 310 | 311 | 312 | 313 | 314 | 315 | 317 | 319 | 321 | 323 | 325 | 326 | 328 | 330 | 332 | 334 | 336 | 338 | 340 | 341 | 342 | 343 | 348 | 349 | 350 | 351 | 353 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 397 | 398 | 399 | 400 | 401 | 404 | 405 | 406 | 407 | 409 | 410 | 411 | 412 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 425 | 426 | 427 | 430 | 431 | -------------------------------------------------------------------------------- /vendor/ietf/epp-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | Extensible Provisioning Protocol v1.0 schema. 17 | 18 | 19 | 20 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | 67 | 69 | 71 | 73 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 86 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 114 | 116 | 118 | 120 | 121 | 122 | 123 | 124 | 125 | 127 | 129 | 131 | 133 | 135 | 136 | 137 | 138 | 139 | 140 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 172 | 173 | 174 | 176 | 177 | 178 | 179 | 180 | 181 | 183 | 184 | 185 | 186 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 215 | 217 | 218 | 219 | 220 | 223 | 224 | 225 | 226 | 227 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 252 | 254 | 255 | 256 | 257 | 260 | 261 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 277 | 278 | 279 | 280 | 281 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 324 | 325 | 326 | 328 | 330 | 331 | 333 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 369 | 371 | 372 | 374 | 376 | 377 | 378 | 379 | 380 | 382 | 383 | 385 | 386 | 387 | 390 | 391 | 392 | 393 | 395 | 396 | 397 | 398 | 399 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 444 | 445 | -------------------------------------------------------------------------------- /vendor/afnic/frnic-1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | Extensible Provisioning Protocol v1.0 15 | AFNIC specific extensions. 16 | Draft version 1.0 17 | 18 | 19 | 20 | 23 | 25 | 27 | 29 | 31 | 32 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | -------------------------------------------------------------------------------- /vendor/afnic/frnic-1.1.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | Extensible Provisioning Protocol v1.0 15 | AFNIC specific extensions v1.1 16 | 17 | 18 | 19 | 22 | 24 | 26 | 28 | 30 | 31 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | -------------------------------------------------------------------------------- /lib/epp-client/contact.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | module Contact 3 | EPPClient::Poll::PARSERS['contact:infData'] = :contact_info_process 4 | 5 | def contact_check_xml(*contacts) #:nodoc: 6 | command do |xml| 7 | xml.check do 8 | xml.check('xmlns' => EPPClient::SCHEMAS_URL['contact-1.0']) do 9 | contacts.each do |c| 10 | xml.id(c) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | 17 | # Check the availability of contacts 18 | # 19 | # takes list of contacts as arguments 20 | # 21 | # returns an array of hashes containing three fields : 22 | # [:id] the contact id 23 | # [:avail] wether the contact id can be provisionned. 24 | # [:reason] 25 | # the server-specific text to help explain why the object cannot be 26 | # provisioned. 27 | # 28 | def contact_check(*contacts) 29 | contacts.flatten! 30 | 31 | response = send_request(contact_check_xml(*contacts)) 32 | get_result(:xml => response, :callback => :contact_check_process) 33 | end 34 | 35 | def contact_check_process(xml) #:nodoc: 36 | xml.xpath('epp:resData/contact:chkData/contact:cd', EPPClient::SCHEMAS_URL).map do |dom| 37 | ret = { 38 | :name => dom.xpath('contact:id', EPPClient::SCHEMAS_URL).text, 39 | :avail => dom.xpath('contact:id', EPPClient::SCHEMAS_URL).attr('avail').value == '1', 40 | } 41 | unless (reason = dom.xpath('contact:reason', EPPClient::SCHEMAS_URL).text).empty? 42 | ret[:reason] = reason 43 | end 44 | ret 45 | end 46 | end 47 | 48 | def contact_info_xml(args) #:nodoc: 49 | command do |xml| 50 | xml.info do 51 | xml.info('xmlns' => EPPClient::SCHEMAS_URL['contact-1.0']) do 52 | xml.id(args[:id]) 53 | if args.key?(:authInfo) 54 | xml.authInfo do 55 | xml.pw(args[:authInfo]) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | 63 | # Returns the informations about a contact 64 | # 65 | # Takes either a unique argument, either 66 | # a string, representing the contact id 67 | # or a hash with the following keys : 68 | # [:id] the contact id, and optionnaly 69 | # [:authInfo] an optional authentication information. 70 | # 71 | # Returned is a hash mapping as closely as possible the result expected 72 | # from the command as per Section 3.1.2 of RFC 5733 : 73 | # [:id] 74 | # the server-unique identifier of the contact object. Most of the time, 75 | # the nic handle. 76 | # [:roid] 77 | # the Repository Object IDentifier assigned to the contact object when 78 | # the object was created. 79 | # [:status] the status of the contact object. 80 | # [:postalInfo] 81 | # a hash containing one or two keys, +loc+ and +int+ representing the 82 | # localized and internationalized version of the postal address 83 | # information. The value is a hash with the following keys : 84 | # [:name] 85 | # the name of the individual or role represented by the contact. 86 | # [:org] 87 | # the name of the organization with which the contact is affiliated. 88 | # [:addr] 89 | # a hash with the following keys : 90 | # [:street] 91 | # an array that contains the contact's street address. 92 | # [:city] the contact's city. 93 | # [:sp] the contact's state or province. 94 | # [:pc] the contact's postal code. 95 | # [:cc] the contact's country code. 96 | # [:voice] the contact's optional voice telephone number. 97 | # [:fax] the contact's optional facsimile telephone number. 98 | # [:email] the contact's email address. 99 | # [:clID] the identifier of the sponsoring client. 100 | # [:crID] 101 | # the identifier of the client that created the contact object. 102 | # [:crDate] the date and time of contact-object creation. 103 | # [:upID] 104 | # the optional identifier of the client that last updated the contact 105 | # object. 106 | # [:upDate] 107 | # the optional date and time of the most recent contact-object 108 | # modification. 109 | # [:trDate] 110 | # the optional date and time of the most recent successful contact-object 111 | # transfer. 112 | # [:authInfo] 113 | # authorization information associated with the contact object. 114 | # [:disclose] 115 | # an optional array that identifies elements that require exceptional 116 | # server-operator handling to allow or restrict disclosure to third 117 | # parties. See 118 | # section 2.9[http://tools.ietf.org/html/rfc5733#section-2.9] of RFC 5733 119 | # for details. 120 | def contact_info(args) 121 | if String === args 122 | args = {:id => args} 123 | end 124 | response = send_request(contact_info_xml(args)) 125 | 126 | get_result(:xml => response, :callback => :contact_info_process) 127 | end 128 | 129 | def contact_info_process(xml) #:nodoc: 130 | contact = xml.xpath('epp:resData/contact:infData', EPPClient::SCHEMAS_URL) 131 | ret = { 132 | :id => contact.xpath('contact:id', EPPClient::SCHEMAS_URL).text, 133 | :roid => contact.xpath('contact:roid', EPPClient::SCHEMAS_URL).text, 134 | } 135 | if (status = contact.xpath('contact:status', EPPClient::SCHEMAS_URL)).size > 0 136 | ret[:status] = status.map {|s| s.attr('s')} 137 | end 138 | 139 | if (postalInfo = contact.xpath('contact:postalInfo', EPPClient::SCHEMAS_URL)).size > 0 140 | ret[:postalInfo] = postalInfo.inject({}) do |acc, p| 141 | type = p.attr('type').to_sym 142 | acc[type] = { :name => p.xpath('contact:name', EPPClient::SCHEMAS_URL).text, :addr => {} } 143 | if (org = p.xpath('contact:org', EPPClient::SCHEMAS_URL)).size > 0 144 | acc[type][:org] = org.text 145 | end 146 | addr = p.xpath('contact:addr', EPPClient::SCHEMAS_URL) 147 | 148 | acc[type][:addr][:street] = addr.xpath('contact:street', EPPClient::SCHEMAS_URL).map {|s| s.text} 149 | %w(city cc).each do |val| 150 | acc[type][:addr][val.to_sym] = addr.xpath("contact:#{val}", EPPClient::SCHEMAS_URL).text 151 | end 152 | %w(sp pc).each do |val| 153 | if (r = addr.xpath("contact:#{val}", EPPClient::SCHEMAS_URL)).size > 0 154 | acc[type][:addr][val.to_sym] = r.text 155 | end 156 | end 157 | 158 | acc 159 | end 160 | end 161 | 162 | %w(voice fax email clID crID upID).each do |val| 163 | if (value = contact.xpath("contact:#{val}", EPPClient::SCHEMAS_URL)).size > 0 164 | ret[val.to_sym] = value.text 165 | end 166 | end 167 | %w(crDate upDate trDate).each do |val| 168 | if (date = contact.xpath("contact:#{val}", EPPClient::SCHEMAS_URL)).size > 0 169 | ret[val.to_sym] = DateTime.parse(date.text) 170 | end 171 | end 172 | if (authInfo = contact.xpath('contact:authInfo', EPPClient::SCHEMAS_URL)).size > 0 173 | ret[:authInfo] = authInfo.xpath('contact:pw', EPPClient::SCHEMAS_URL).text 174 | end 175 | if (disclose = contact.xpath('contact:disclose', EPPClient::SCHEMAS_URL)).size > 0 176 | ret[:disclose] = { :flag => disclose.attr('flag').value == '1', :elements => [] } 177 | disclose.children.each do |c| 178 | r = { :name => c.name } 179 | unless (type = c.attr('type').value).nil? 180 | r[:type] == type 181 | end 182 | ret[:disclose][:elements] << r 183 | end 184 | end 185 | ret 186 | end 187 | 188 | def contact_create_xml(contact) #:nodoc: 189 | command do |xml| 190 | xml.create do 191 | xml.create('xmlns' => EPPClient::SCHEMAS_URL['contact-1.0']) do 192 | if contact.key?(:id) 193 | xml.id(contact[:id]) 194 | else 195 | xml.id('invalid') 196 | end 197 | contact[:postalInfo].each do |type,infos| 198 | xml.postalInfo :type => type do 199 | xml.name(infos[:name]) 200 | xml.org(infos[:org]) if infos.key?(:org) 201 | xml.addr do 202 | infos[:addr][:street].each do |street| 203 | xml.street(street) 204 | end 205 | xml.city(infos[:addr][:city]) 206 | [:sp, :pc].each do |val| 207 | xml.__send__(val, infos[:addr][val]) if infos[:addr].key?(val) 208 | end 209 | xml.cc(infos[:addr][:cc]) 210 | end 211 | end 212 | end 213 | [:voice, :fax].each do |val| 214 | xml.__send__(val, contact[val]) if contact.key?(val) 215 | end 216 | xml.email(contact[:email]) 217 | xml.authInfo do 218 | xml.pw(contact[:authInfo]) 219 | end 220 | if contact.key?(:disclose) 221 | xml.disclose do 222 | contact[:disclose].each do |disc| 223 | if disc.key?(:type) 224 | xml.__send__(disc[:name], :type => disc[:type]) 225 | else 226 | xml.__send__(disc[:name]) 227 | end 228 | end 229 | end 230 | end 231 | end 232 | end 233 | end 234 | end 235 | 236 | # Creates a contact 237 | # 238 | # Takes a hash as an argument containing the following keys : 239 | # 240 | # [:id] 241 | # the server-unique identifier of the contact object. Most of the time, 242 | # the nic handle. 243 | # [:postalInfo] 244 | # a hash containing one or two keys, +loc+ and +int+ representing the 245 | # localized and internationalized version of the postal address 246 | # information. The value is a hash with the following keys : 247 | # [:name] 248 | # the name of the individual or role represented by the contact. 249 | # [:org] 250 | # the name of the organization with which the contact is affiliated. 251 | # [:addr] 252 | # a hash with the following keys : 253 | # [:street] 254 | # an array that contains the contact's street address. 255 | # [:city] the contact's city. 256 | # [:sp] the contact's state or province. 257 | # [:pc] the contact's postal code. 258 | # [:cc] the contact's country code. 259 | # [:voice] the contact's optional voice telephone number. 260 | # [:fax] the contact's optional facsimile telephone number. 261 | # [:email] the contact's email address. 262 | # [:authInfo] 263 | # authorization information associated with the contact object. 264 | # [:disclose] 265 | # an optional array that identifies elements that require exceptional 266 | # server-operator handling to allow or restrict disclosure to third 267 | # parties. See 268 | # section 2.9[http://tools.ietf.org/html/rfc5733#section-2.9] of RFC 5733 269 | # for details. 270 | # 271 | # Returns a hash with the following keys : 272 | # 273 | # [:id] the nic handle. 274 | # [:crDate] the date and time of contact-object creation. 275 | def contact_create(contact) 276 | response = send_request(contact_create_xml(contact)) 277 | 278 | get_result(:xml => response, :callback => :contact_create_process) 279 | end 280 | 281 | def contact_create_process(xml) #:nodoc: 282 | contact = xml.xpath('epp:resData/contact:creData', EPPClient::SCHEMAS_URL) 283 | { 284 | :id => contact.xpath('contact:id', EPPClient::SCHEMAS_URL).text, 285 | :crDate => DateTime.parse(contact.xpath('contact:crDate', EPPClient::SCHEMAS_URL).text), 286 | } 287 | end 288 | 289 | def contact_delete_xml(contact) #:nodoc: 290 | command do |xml| 291 | xml.delete do 292 | xml.delete('xmlns' => EPPClient::SCHEMAS_URL['contact-1.0']) do 293 | xml.id(contact) 294 | end 295 | end 296 | end 297 | end 298 | 299 | # Deletes a contact 300 | # 301 | # Takes a single nic handle for argument. 302 | # 303 | # Returns true on success, or raises an exception. 304 | def contact_delete(contact) 305 | response = send_request(contact_delete_xml(contact)) 306 | 307 | get_result(response) 308 | end 309 | 310 | def contact_update_xml(args) #:nodoc: 311 | command do |xml| 312 | xml.update do 313 | xml.update('xmlns' => EPPClient::SCHEMAS_URL['contact-1.0']) do 314 | xml.id args[:id] 315 | if args.key?(:add) && args[:add].key?(:status) 316 | xml.add do 317 | args[:add][:status].each do |s| 318 | xml.status :s => s 319 | end 320 | end 321 | end 322 | if args.key?(:rem) && args[:rem].key?(:status) 323 | xml.rem do 324 | args[:rem][:status].each do |s| 325 | xml.status :s => s 326 | end 327 | end 328 | end 329 | if args.key?(:chg) 330 | contact = args[:chg] 331 | xml.chg do 332 | if contact.key?(:postalInfo) 333 | contact[:postalInfo].each do |type,infos| 334 | xml.postalInfo :type => type do 335 | xml.name(infos[:name]) 336 | xml.org(infos[:org]) if infos.key?(:org) 337 | xml.addr do 338 | infos[:addr][:street].each do |street| 339 | xml.street(street) 340 | end 341 | xml.city(infos[:addr][:city]) 342 | [:sp, :pc].each do |val| 343 | xml.__send__(val, infos[:addr][val]) if infos[:addr].key?(val) 344 | end 345 | xml.cc(infos[:addr][:cc]) 346 | end 347 | end 348 | end 349 | end 350 | [:voice, :fax, :email].each do |val| 351 | xml.__send__(val, contact[val]) if contact.key?(val) 352 | end 353 | if contact.key?(:authInfo) 354 | xml.authInfo do 355 | xml.pw(contact[:authInfo]) 356 | end 357 | end 358 | if contact.key?(:disclose) 359 | xml.disclose do 360 | contact[:disclose].each do |disc| 361 | if disc.key?(:type) 362 | xml.__send__(disc[:name], :type => disc[:type]) 363 | else 364 | xml.__send__(disc[:name]) 365 | end 366 | end 367 | end 368 | end 369 | end 370 | end 371 | end 372 | end 373 | end 374 | end 375 | 376 | # Updates a contact 377 | # 378 | # Takes a hash with the id, and at least one of the following keys : 379 | # [:id] 380 | # the server-unique identifier of the contact object to be updated. 381 | # [:add/:rem] 382 | # adds or removes the following data from the contact object : 383 | # [:status] an array of status to add to/remove from the object. 384 | # [:chg] 385 | # changes the datas of the contact object, takes the same arguments as 386 | # the creation of the contact, except the id, with the small change that 387 | # each first level key is now optional. (Meaning that you don't have to 388 | # supply a :postalInfo if you don't need to, but if you do, all 389 | # it's mandatory fields are mandatory.) 390 | # 391 | # Returns true on success, or raises an exception. 392 | def contact_update(args) 393 | response = send_request(contact_update_xml(args)) 394 | 395 | get_result(response) 396 | end 397 | end 398 | end 399 | -------------------------------------------------------------------------------- /lib/epp-client/domain.rb: -------------------------------------------------------------------------------- 1 | module EPPClient 2 | module Domain 3 | EPPClient::Poll::PARSERS['domain:panData'] = :domain_pending_action_process 4 | EPPClient::Poll::PARSERS['domain:trnData'] = :domain_transfer_response 5 | 6 | def domain_check_xml(*domains) # :nodoc: 7 | command do |xml| 8 | xml.check do 9 | xml.check('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do 10 | domains.each do |dom| 11 | xml.name(dom) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | 18 | # Check the availability of domains 19 | # 20 | # takes a list of domains as arguments 21 | # 22 | # returns an array of hashes containing three fields : 23 | # [:name] The domain name 24 | # [:avail] Wether the domain is available or not. 25 | # [:reason] The reason for non availability, if given. 26 | def domain_check(*domains) 27 | domains.flatten! 28 | response = send_request(domain_check_xml(*domains)) 29 | 30 | get_result(:xml => response, :callback => :domain_check_process) 31 | end 32 | 33 | def domain_check_process(xml) # :nodoc: 34 | xml.xpath('epp:resData/domain:chkData/domain:cd', EPPClient::SCHEMAS_URL).map do |dom| 35 | ret = { 36 | :name => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).text, 37 | :avail => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).attr('avail').value == '1', 38 | } 39 | unless (reason = dom.xpath('domain:reason', EPPClient::SCHEMAS_URL).text).empty? 40 | ret[:reason] = reason 41 | end 42 | ret 43 | end 44 | end 45 | 46 | def domain_info_xml(args) # :nodoc: 47 | command do |xml| 48 | xml.info do 49 | xml.info('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do 50 | xml.name(args[:name]) 51 | if args.key?(:authInfo) 52 | xml.authInfo do 53 | if args.key?(:roid) 54 | xml.pw({:roid => args[:roid]}, args[:authInfo]) 55 | else 56 | xml.pw(args[:authInfo]) 57 | end 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | 65 | # Returns the informations about a domain 66 | # 67 | # Takes either a unique argument, a string, representing the domain, or a 68 | # hash with : :name the domain name, and optionnaly 69 | # :authInfo the authentication information and possibly 70 | # :roid the contact the authInfo is about. 71 | # 72 | # Returned is a hash mapping as closely as possible the result expected 73 | # from the command as per Section 3.1.2 of RFC 5731 : 74 | # [:name] The fully qualified name of the domain object. 75 | # [:roid] 76 | # The Repository Object IDentifier assigned to the domain object when 77 | # the object was created. 78 | # [:status] 79 | # an optionnal array of elements that contain the current status 80 | # descriptors associated with the domain. 81 | # [:registrant] one optionnal registrant nic handle. 82 | # [:contacts] 83 | # an optionnal hash which keys are choosen between +admin+, +billing+ and 84 | # +tech+ and which values are arrays of nic handles for the corresponding 85 | # contact types. 86 | # [:ns] 87 | # an optional array containing nameservers informations, which can either 88 | # be an array of strings containing the the fully qualified name of a 89 | # host, or an array of hashes containing the following keys : 90 | # [:hostName] the fully qualified name of a host. 91 | # [:hostAddrv4] 92 | # an optionnal array of ipv4 addresses to be associated with the host. 93 | # [:hostAddrv6] 94 | # an optionnal array of ipv6 addresses to be associated with the host. 95 | # [:host] 96 | # an optionnal array of fully qualified names of the subordinate host 97 | # objects that exist under this superordinate domain object. 98 | # [:clID] the identifier of the sponsoring client. 99 | # [:crID] 100 | # an optional identifier of the client that created the domain object. 101 | # [:crDate] an optional date and time of domain object creation. 102 | # [:exDate] 103 | # the date and time identifying the end of the domain object's 104 | # registration period. 105 | # [:upID] 106 | # the identifier of the client that last updated the domain object. 107 | # [:upDate] 108 | # the date and time of the most recent domain-object modification. 109 | # [:trDate] 110 | # the date and time of the most recent successful domain-object transfer. 111 | # [:authInfo] 112 | # authorization information associated with the domain object. 113 | def domain_info(args) 114 | if String === args 115 | args = {:name => args} 116 | end 117 | response = send_request(domain_info_xml(args)) 118 | 119 | get_result(:xml => response, :callback => :domain_info_process) 120 | end 121 | 122 | def domain_info_process(xml) # :nodoc: 123 | dom = xml.xpath('epp:resData/domain:infData', EPPClient::SCHEMAS_URL) 124 | ret = { 125 | :name => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).text, 126 | :roid => dom.xpath('domain:roid', EPPClient::SCHEMAS_URL).text, 127 | } 128 | if (status = dom.xpath('domain:status', EPPClient::SCHEMAS_URL)).size > 0 129 | ret[:status] = status.map {|s| s.attr('s')} 130 | end 131 | if (registrant = dom.xpath('domain:registrant', EPPClient::SCHEMAS_URL)).size > 0 132 | ret[:registrant] = registrant.text 133 | end 134 | if (contact = dom.xpath('domain:contact', EPPClient::SCHEMAS_URL)).size > 0 135 | ret[:contacts] = contact.inject({}) do |a,c| 136 | s = c.attr('type').to_sym 137 | a[s] ||= [] 138 | a[s] << c.text 139 | a 140 | end 141 | end 142 | if (ns = dom.xpath('domain:ns', EPPClient::SCHEMAS_URL)).size > 0 143 | if (hostObj = ns.xpath('domain:hostObj', EPPClient::SCHEMAS_URL)).size > 0 144 | ret[:ns] = hostObj.map {|h| h.text} 145 | elsif (hostAttr = ns.xpath('domain:hostAttr', EPPClient::SCHEMAS_URL)).size > 0 146 | ret[:ns] = hostAttr.map do |h| 147 | r = { :hostName => h.xpath('domain:hostName', EPPClient::SCHEMAS_URL).text } 148 | if (v4 = h.xpath('domain:hostAddr[@ip="v4"]', EPPClient::SCHEMAS_URL)).size > 0 149 | r[:hostAddrv4] = v4.map {|v| v.text} 150 | end 151 | if (v6 = h.xpath('domain:hostAddr[@ip="v6"]', EPPClient::SCHEMAS_URL)).size > 0 152 | r[:hostAddrv6] = v6.map {|v| v.text} 153 | end 154 | r 155 | end 156 | end 157 | end 158 | if (host = dom.xpath('domain:host', EPPClient::SCHEMAS_URL)).size > 0 159 | ret[:host] = host.map {|h| h.text} 160 | end 161 | %w(clID upID).each do |val| 162 | if (r = dom.xpath("domain:#{val}", EPPClient::SCHEMAS_URL)).size > 0 163 | ret[val.to_sym] = r.text 164 | end 165 | end 166 | %w(crDate exDate upDate trDate).each do |val| 167 | if (r = dom.xpath("domain:#{val}", EPPClient::SCHEMAS_URL)).size > 0 168 | ret[val.to_sym] = DateTime.parse(r.text) 169 | end 170 | end 171 | if (authInfo = dom.xpath('domain:authInfo', EPPClient::SCHEMAS_URL)).size > 0 172 | ret[:authInfo] = authInfo.xpath('domain:pw', EPPClient::SCHEMAS_URL).text 173 | end 174 | return ret 175 | end 176 | 177 | def domain_nss_xml(xml, nss) #:nodoc: 178 | xml.ns do 179 | if nss.first.is_a?(Hash) 180 | nss.each do |ns| 181 | xml.hostAttr do 182 | xml.hostName ns[:hostName] 183 | if ns.key?(:hostAddrv4) 184 | ns[:hostAddrv4].each do |v4| 185 | xml.hostAddr({:ip => :v4}, v4) 186 | end 187 | end 188 | if ns.key?(:hostAddrv6) 189 | ns[:hostAddrv6].each do |v6| 190 | xml.hostAddr({:ip => :v6}, v6) 191 | end 192 | end 193 | end 194 | end 195 | else 196 | nss.each do |ns| 197 | xml.hostObj ns 198 | end 199 | end 200 | end 201 | end 202 | 203 | def domain_contacts_xml(xml, args) #:nodoc: 204 | args.each do |type,contacts| 205 | contacts.each do |c| 206 | xml.contact({:type => type}, c) 207 | end 208 | end 209 | end 210 | 211 | def domain_create_xml(args) #:nodoc: 212 | command do |xml| 213 | xml.create do 214 | xml.create('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do 215 | xml.name args[:name] 216 | 217 | if args.key?(:period) 218 | xml.period({:unit => args[:period][:unit]}, args[:period][:number]) 219 | end 220 | 221 | if args.key?(:ns) 222 | domain_nss_xml(xml, args[:ns]) 223 | end 224 | 225 | xml.registrant args[:registrant] if args.key?(:registrant) 226 | 227 | if args.key?(:contacts) 228 | domain_contacts_xml(xml, args[:contacts]) 229 | end 230 | 231 | xml.authInfo do 232 | xml.pw args[:authInfo] 233 | end 234 | end 235 | end 236 | end 237 | end 238 | 239 | # Creates a domain 240 | # 241 | # Takes a hash as an argument, containing the following keys : 242 | # 243 | # [:name] the domain name 244 | # [:period] 245 | # an optionnal hash containing the period for withch the domain is 246 | # registered with the following keys : 247 | # [:unit] the unit of time, either "m"onth or "y"ear. 248 | # [:number] the number of unit of time. 249 | # [:ns] 250 | # an optional array containing nameservers informations, which can either 251 | # be an array of strings containing the nameserver's hostname, or an 252 | # array of hashes containing the following keys : 253 | # [:hostName] the hostname of the nameserver. 254 | # [:hostAddrv4] an optionnal array of ipv4 addresses. 255 | # [:hostAddrv6] an optionnal array of ipv6 addresses. 256 | # [:registrant] an optionnal registrant nic handle. 257 | # [:contacts] 258 | # an optionnal hash which keys are choosen between +admin+, +billing+ and 259 | # +tech+ and which values are arrays of nic handles for the corresponding 260 | # contact types. 261 | # [:authInfo] the password associated with the domain. 262 | # 263 | # Returns a hash with the following keys : 264 | # 265 | # [:name] the fully qualified name of the domain object. 266 | # [:crDate] the date and time of domain object creation. 267 | # [:exDate] 268 | # the date and time identifying the end of the domain object's 269 | # registration period. 270 | def domain_create(args) 271 | response = send_request(domain_create_xml(args)) 272 | 273 | get_result(:xml => response, :callback => :domain_create_process) 274 | end 275 | 276 | def domain_create_process(xml) #:nodoc: 277 | dom = xml.xpath('epp:resData/domain:creData', EPPClient::SCHEMAS_URL) 278 | ret = { 279 | :name => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).text, 280 | :crDate => DateTime.parse(dom.xpath('domain:crDate', EPPClient::SCHEMAS_URL).text), 281 | :upDate => DateTime.parse(dom.xpath('domain:crDate', EPPClient::SCHEMAS_URL).text), 282 | } 283 | end 284 | 285 | def domain_delete_xml(domain) #:nodoc: 286 | command do |xml| 287 | xml.delete do 288 | xml.delete('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do 289 | xml.name domain 290 | end 291 | end 292 | end 293 | end 294 | 295 | # Deletes a domain 296 | # 297 | # Takes a single fully qualified domain name for argument. 298 | # 299 | # Returns true on success, or raises an exception. 300 | def domain_delete(domain) 301 | response = send_request(domain_delete_xml(domain)) 302 | 303 | get_result(response) 304 | end 305 | 306 | def domain_update_xml(args) #:nodoc: 307 | command do |xml| 308 | xml.update do 309 | xml.update('xmlns' => EPPClient::SCHEMAS_URL['domain-1.0']) do 310 | xml.name args[:name] 311 | [:add, :rem].each do |ar| 312 | if args.key?(ar) && (args[ar].key?(:ns) || args[ar].key?(:contacts) || args[ar].key?(:status)) 313 | xml.__send__(ar) do 314 | if args[ar].key?(:ns) 315 | domain_nss_xml(xml, args[ar][:ns]) 316 | end 317 | if args[ar].key?(:contacts) 318 | domain_contacts_xml(xml, args[ar][:contacts]) 319 | end 320 | if args[ar].key?(:status) 321 | args[ar][:status].each do |st,text| 322 | if text.nil? 323 | xml.status(:s => st) 324 | else 325 | xml.status({:s => st}, text) 326 | end 327 | end 328 | end 329 | end 330 | end 331 | end 332 | if args.key?(:chg) && (args[:chg].key?(:registrant) || args[:chg].key?(:authInfo)) 333 | xml.chg do 334 | if args[:chg].key?(:registrant) 335 | xml.registrant args[:chg][:registrant] 336 | end 337 | if args[:chg].key?(:authInfo) 338 | xml.authInfo do 339 | xml.pw args[:chg][:authInfo] 340 | end 341 | end 342 | end 343 | end 344 | end 345 | end 346 | end 347 | end 348 | 349 | # Updates a domain 350 | # 351 | # Takes a hash with the name, and at least one of the following keys : 352 | # [:name] 353 | # the fully qualified name of the domain object to be updated. 354 | # [:add/:rem] 355 | # adds / removes the following data to/from the domain object : 356 | # [:ns] 357 | # an optional array containing nameservers informations, which can either 358 | # be an array of strings containing the nameserver's hostname, or an 359 | # array of hashes containing the following keys : 360 | # [:hostName] the hostname of the nameserver. 361 | # [:hostAddrv4] an optionnal array of ipv4 addresses. 362 | # [:hostAddrv6] an optionnal array of ipv6 addresses. 363 | # [:contacts] 364 | # an optionnal hash which keys are choosen between +admin+, +billing+ and 365 | # +tech+ and which values are arrays of nic handles for the corresponding 366 | # contact types. 367 | # [:status] 368 | # an optional hash with status values to be applied to or removed from 369 | # the object. When specifying a value to be removed, only the attribute 370 | # value is significant; element text is not required to match a value 371 | # for removal. 372 | # [:chg] 373 | # changes the following in the domain object. 374 | # [:registrant] an optionnal registrant nic handle. 375 | # [:authInfo] an optional password associated with the domain. 376 | # 377 | # Returns true on success, or raises an exception. 378 | def domain_update(args) 379 | response = send_request(domain_update_xml(args)) 380 | 381 | get_result(response) 382 | end 383 | 384 | 385 | def domain_pending_action_process(xml) #:nodoc: 386 | dom = xml.xpath('epp:resData/domain:panData', EPPClient::SCHEMAS_URL) 387 | ret = { 388 | :name => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).text, 389 | :paResult => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).attribute('paResult').value, 390 | :paTRID => get_trid(dom.xpath('domain:paTRID', EPPClient::SCHEMAS_URL)), 391 | :paDate => DateTime.parse(dom.xpath('domain:paDate', EPPClient::SCHEMAS_URL).text), 392 | } 393 | end 394 | 395 | def domain_transfer_response(xml) #:nodoc: 396 | dom = xml.xpath('epp:resData/domain:trnData', EPPClient::SCHEMAS_URL) 397 | ret = { 398 | :name => dom.xpath('domain:name', EPPClient::SCHEMAS_URL).text, 399 | :trStatus => dom.xpath('domain:trStatus', EPPClient::SCHEMAS_URL).text, 400 | :reID => dom.xpath('domain:reID', EPPClient::SCHEMAS_URL).text, 401 | :reDate => DateTime.parse(dom.xpath('domain:reDate', EPPClient::SCHEMAS_URL).text), 402 | :acID => dom.xpath('domain:acID', EPPClient::SCHEMAS_URL).text, 403 | :acDate => DateTime.parse(dom.xpath('domain:acDate', EPPClient::SCHEMAS_URL).text) 404 | } 405 | if (exDate = dom.xpath('domain:exDate', EPPClient::SCHEMAS_URL)).size > 0 406 | ret[:exDate] = DateTime.parse(exDate) 407 | end 408 | ret 409 | end 410 | end 411 | end 412 | -------------------------------------------------------------------------------- /vendor/afnic/frnic-1.2.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | Extensible Provisioning Protocol v1.0 15 | AFNIC specific extensions v1.1 16 | 17 | 18 | 19 | 22 | 24 | 26 | 28 | 30 | 31 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | -------------------------------------------------------------------------------- /lib/epp-client/afnic.rb: -------------------------------------------------------------------------------- 1 | require 'epp-client/base' 2 | require 'epp-client/rgp' 3 | require 'epp-client/secdns' 4 | 5 | module EPPClient 6 | class AFNIC < Base 7 | SCHEMAS_AFNIC = %w[ 8 | frnic-1.2 9 | ] 10 | 11 | EPPClient::SCHEMAS_URL.merge!(SCHEMAS_AFNIC.inject({}) do |a,s| 12 | a[s.sub(/-1\.2$/, '')] = "http://www.afnic.fr/xml/epp/#{s}" if s =~ /-1\.2$/ 13 | a[s] = "http://www.afnic.fr/xml/epp/#{s}" 14 | a 15 | end) 16 | 17 | # Sets the default for AFNIC, that is, server and port, according to 18 | # AFNIC's documentation. 19 | # http://www.afnic.fr/doc/interface/epp 20 | # 21 | # ==== Optional Attributes 22 | # [:test] sets the server to be the test server. 23 | def initialize(args) 24 | if args.delete(:test) == true 25 | args[:server] ||= 'epp.sandbox.nic.fr' 26 | else 27 | args[:server] ||= 'epp.nic.fr' 28 | end 29 | @services = EPPClient::SCHEMAS_URL.values_at('domain', 'contact') 30 | args[:port] ||= 700 31 | super(args) 32 | @extensions << EPPClient::SCHEMAS_URL['frnic'] 33 | end 34 | 35 | # Extends the EPPClient::Domain#domain_check so that the specific AFNIC 36 | # check informations are processed, the additionnal informations are : 37 | # 38 | # [:reserved] the domain is a reserved name. 39 | # [:rsvReason] the optional reason why the domain is reserved. 40 | # [:forbidden] the domain is a forbidden name. 41 | # [:fbdReason] the optional reason why the domain is forbidden. 42 | def domain_check(*domains) 43 | super # placeholder so that I can add some doc 44 | end 45 | 46 | def domain_check_process(xml) # :nodoc: 47 | ret = super 48 | xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:chkData/frnic:domain/frnic:cd', EPPClient::SCHEMAS_URL).each do |dom| 49 | name = dom.xpath('frnic:name', EPPClient::SCHEMAS_URL) 50 | hash = ret.select {|d| d[:name] == name.text}.first 51 | hash[:reserved] = name.attr('reserved').value == "1" 52 | unless (reason = dom.xpath('frnic:rsvReason', EPPClient::SCHEMAS_URL).text).empty? 53 | hash[:rsvReason] = reason 54 | end 55 | hash[:forbidden] = name.attr('forbidden').value == "1" 56 | unless (reason = dom.xpath('frnic:fbdReason', EPPClient::SCHEMAS_URL).text).empty? 57 | hash[:fbdReason] = reason 58 | end 59 | end 60 | return ret 61 | end 62 | 63 | # Extends the EPPClient::Domain#domain_info so that the specific AFNIC 64 | # :status can be added. 65 | def domain_info(domain) 66 | super # placeholder so that I can add some doc 67 | end 68 | 69 | def domain_info_process(xml) #:nodoc: 70 | ret = super 71 | if (frnic_status = xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:infData/frnic:domain/frnic:status', EPPClient::SCHEMAS_URL)).size > 0 72 | ret[:status] ||= [] # The status is optional, there may be none at this point. 73 | ret[:status] += frnic_status.map {|s| s.attr('s')} 74 | end 75 | ret 76 | end 77 | 78 | # parse legalEntityInfos content. 79 | def legalEntityInfos(leI) #:nodoc: 80 | ret = {} 81 | ret[:legalStatus] = leI.xpath('frnic:legalStatus', EPPClient::SCHEMAS_URL).attr('s').value 82 | if (r = leI.xpath("frnic:idStatus", EPPClient::SCHEMAS_URL)).size > 0 83 | ret[:idStatus] = {:value => r.text} 84 | ret[:idStatus][:when] = r.attr('when').value if r.attr('when') 85 | ret[:idStatus][:source] = r.attr('source').value if r.attr('source') 86 | end 87 | %w(siren VAT trademark DUNS local).each do |val| 88 | if (r = leI.xpath("frnic:#{val}", EPPClient::SCHEMAS_URL)).size > 0 89 | ret[val.to_sym] = r.text 90 | end 91 | end 92 | if (asso = leI.xpath("frnic:asso", EPPClient::SCHEMAS_URL)).size > 0 93 | ret[:asso] = {} 94 | if (r = asso.xpath("frnic:waldec", EPPClient::SCHEMAS_URL)).size > 0 95 | ret[:asso][:waldec] = r.text 96 | else 97 | if (decl = asso.xpath('frnic:decl', EPPClient::SCHEMAS_URL)).size > 0 98 | ret[:asso][:decl] = Date.parse(decl.text) 99 | end 100 | publ = asso.xpath('frnic:publ', EPPClient::SCHEMAS_URL) 101 | ret[:asso][:publ] = { 102 | :date => Date.parse(publ.text), 103 | :page => publ.attr('page').value, 104 | } 105 | if (announce = publ.attr('announce')) && announce.value != '0' 106 | ret[:asso][:publ][:announce] = announce.value 107 | end 108 | end 109 | end 110 | ret 111 | end 112 | private :legalEntityInfos 113 | 114 | # Extends the EPPClient::Contact#contact_info so that the specific AFNIC 115 | # check informations are processed, the additionnal informations are : 116 | # 117 | # either : 118 | # [:legalEntityInfos] 119 | # indicating that the contact is an organisation with the following 120 | # informations : 121 | # [:legalStatus] 122 | # should be either +company+, +association+ or +other+. 123 | # [:idStatus] 124 | # indicates the identification process status. Has optional 125 | # :when and :source attributes. 126 | # [:siren] contains the SIREN number of the organisation. 127 | # [:VAT] 128 | # is optional and contains the VAT number of the organisation. 129 | # [:trademark] 130 | # is optional and contains the trademark number of the organisation. 131 | # [:DUNS] 132 | # is optional and contains the Data Universal Numbering System number of 133 | # the organisation. 134 | # [:local] 135 | # is optional and contains an identifier local to the eligible country. 136 | # [:asso] 137 | # indicates the organisation is an association and contains either a 138 | # +waldec+ or a +decl+ and a +publ+ : 139 | # [:waldec] contains the waldec id of the association. 140 | # [:decl] 141 | # optionally indicate the date of the association was declared at the 142 | # prefecture. 143 | # [:publ] 144 | # contains informations regarding the publication in the "Journal 145 | # Officiel" : 146 | # [:date] the date of publication. 147 | # [:page] the page the announce is on. 148 | # [:announce] the announce number on the page (optional). 149 | # [:individualInfos] 150 | # indicating that the contact is a person with the following 151 | # informations : 152 | # [:idStatus] 153 | # indicates the identification process status. Has optional 154 | # :when and :source attributes. 155 | # [:birthDate] the date of birth of the contact. 156 | # [:birthCity] the city of birth of the contact. 157 | # [:birthPc] the postal code of the city of birth. 158 | # [:birthCc] the country code of the place of birth. 159 | # 160 | # Additionnaly, when the contact is a person, there can be the following 161 | # informations : 162 | # [:firstName] 163 | # the first name of the person. (The last name being stored in the +name+ 164 | # field in the +postalInfo+.) 165 | # [:list] 166 | # with the value of +restrictedPublication+ mean that the element 167 | # diffusion should be restricted. 168 | # 169 | # Optionnaly, there can be : 170 | # [:obsoleted] 171 | # the contact info is obsolete since/from the optional date :when. 172 | # [:reachable] 173 | # the contact is reachable through the optional :media since/from 174 | # the optional date :when. The info having been specified by the 175 | # :source. 176 | def contact_info(contact) 177 | super # placeholder so that I can add some doc 178 | end 179 | 180 | def contact_info_process(xml) #:nodoc: 181 | ret = super 182 | if (contact = xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:infData/frnic:contact', EPPClient::SCHEMAS_URL)).size > 0 183 | if (list = contact.xpath('frnic:list', EPPClient::SCHEMAS_URL)).size > 0 184 | ret[:list] = list.map {|l| l.text} 185 | end 186 | if (firstName = contact.xpath('frnic:firstName', EPPClient::SCHEMAS_URL)).size > 0 187 | ret[:firstName] = firstName.text 188 | end 189 | if (iI = contact.xpath('frnic:individualInfos', EPPClient::SCHEMAS_URL)).size > 0 190 | ret[:individualInfos] = {} 191 | ret[:individualInfos][:birthDate] = Date.parse(iI.xpath('frnic:birthDate', EPPClient::SCHEMAS_URL).text) 192 | if (r = iI.xpath("frnic:idStatus", EPPClient::SCHEMAS_URL)).size > 0 193 | ret[:individualInfos][:idStatus] = {:value => r.text} 194 | ret[:individualInfos][:idStatus][:when] = r.attr('when').value if r.attr('when') 195 | ret[:individualInfos][:idStatus][:source] = r.attr('source').value if r.attr('source') 196 | end 197 | %w(birthCity birthPc birthCc).each do |val| 198 | if (r = iI.xpath("frnic:#{val}", EPPClient::SCHEMAS_URL)).size > 0 199 | ret[:individualInfos][val.to_sym] = r.text 200 | end 201 | end 202 | end 203 | if (leI = contact.xpath('frnic:legalEntityInfos', EPPClient::SCHEMAS_URL)).size > 0 204 | ret[:legalEntityInfos] = legalEntityInfos(leI) 205 | end 206 | if (obsoleted = contact.xpath('frnic:obsoleted', EPPClient::SCHEMAS_URL)).size > 0 207 | if obsoleted.text != '0' 208 | ret[:obsoleted] = {} 209 | if v_when = obsoleted.attr('when') 210 | ret[:obsoleted][:when] = DateTime.parse(v_when.value) 211 | end 212 | end 213 | end 214 | if (reachable = contact.xpath('frnic:reachable', EPPClient::SCHEMAS_URL)).size > 0 215 | if reachable.text != '0' 216 | ret[:reachable] = {} 217 | if v_when = reachable.attr('when') 218 | ret[:reachable][:when] = DateTime.parse(v_when.value) 219 | end 220 | if media = reachable.attr('media') 221 | ret[:reachable][:media] = media.value 222 | end 223 | if source = reachable.attr('source') 224 | ret[:reachable][:source] = source.value 225 | end 226 | end 227 | end 228 | end 229 | ret 230 | end 231 | 232 | def contact_create_xml(contact) #:nodoc: 233 | ret = super 234 | 235 | ext = extension do |xml| 236 | xml.ext( :xmlns => EPPClient::SCHEMAS_URL['frnic']) do 237 | xml.create do 238 | xml.contact do 239 | if contact.key?(:legalEntityInfos) 240 | lEI = contact[:legalEntityInfos] 241 | xml.legalEntityInfos do 242 | xml.idStatus(lEI[:idStatus]) if lEI.key?(:idStatus) 243 | xml.legalStatus(:s => lEI[:legalStatus]) 244 | [:siren, :VAT, :trademark, :DUNS, :local].each do |val| 245 | if lEI.key?(val) 246 | xml.__send__(val, lEI[val]) 247 | end 248 | end 249 | if lEI.key?(:asso) 250 | asso = lEI[:asso] 251 | xml.asso do 252 | if asso.key?(:waldec) 253 | xml.waldec(asso[:waldec]) 254 | else 255 | xml.decl(asso[:decl]) if asso.key?(:decl) 256 | attrs = {:page => asso[:publ][:page]} 257 | attrs[:announce] = asso[:publ][:announce] if asso[:publ].key?(:announce) 258 | xml.publ(attrs, asso[:publ][:date]) 259 | end 260 | end 261 | end 262 | end 263 | else 264 | if contact.key?(:list) 265 | xml.list(contact[:list]) 266 | end 267 | if contact.key?(:individualInfos) 268 | iI = contact[:individualInfos] 269 | xml.individualInfos do 270 | xml.idStatus(iI[:idStatus]) if iI.key?(:idStatus) 271 | xml.birthDate(iI[:birthDate]) 272 | if iI.key?(:birthCity) 273 | xml.birthCity(iI[:birthCity]) 274 | end 275 | if iI.key?(:birthPc) 276 | xml.birthPc(iI[:birthPc]) 277 | end 278 | xml.birthCc(iI[:birthCc]) 279 | end 280 | end 281 | if contact.key?(:firstName) 282 | xml.firstName(contact[:firstName]) 283 | end 284 | end 285 | if contact.key?(:reachable) 286 | if Hash === (reachable = contact[:reachable]) 287 | xml.reachable(reachable, 1) 288 | else 289 | raise ArgumentError, "reachable has to be a Hash" 290 | end 291 | end 292 | end 293 | end 294 | end 295 | end 296 | 297 | insert_extension(ret, ext) 298 | end 299 | 300 | # Extends the EPPClient::Contact#contact_create so that the specific AFNIC 301 | # create informations can be sent, the additionnal informations are : 302 | # 303 | # either : 304 | # [:legalEntityInfos] 305 | # indicating that the contact is an organisation with the following 306 | # informations : 307 | # [:idStatus] 308 | # indicates the identification process status. 309 | # [:legalStatus] 310 | # should be either +company+, +association+ or +other+. 311 | # [:siren] contains the SIREN number of the organisation. 312 | # [:VAT] 313 | # is optional and contains the VAT number of the organisation. 314 | # [:trademark] 315 | # is optional and contains the trademark number of the organisation. 316 | # [:DUNS] 317 | # is optional and contains the Data Universal Numbering System number of 318 | # the organisation. 319 | # [:local] 320 | # is optional and contains an identifier local to the eligible country. 321 | # [:asso] 322 | # indicates the organisation is an association and contains either a 323 | # +waldec+ or a +decl+ and a +publ+ : 324 | # [:waldec] contains the waldec id of the association. 325 | # [:decl] 326 | # optionally indicate the date of the association was declared at the 327 | # prefecture. 328 | # [:publ] 329 | # contains informations regarding the publication in the "Journal 330 | # Officiel" : 331 | # [:date] the date of publication. 332 | # [:page] the page the announce is on. 333 | # [:announce] the announce number on the page (optional). 334 | # [:individualInfos] 335 | # indicating that the contact is a person with the following 336 | # informations : 337 | # [:idStatus] 338 | # indicates the identification process status. 339 | # [:birthDate] the date of birth of the contact. 340 | # [:birthCity] the city of birth of the contact. 341 | # [:birthPc] the postal code of the city of birth. 342 | # [:birthCc] the country code of the place of birth. 343 | # 344 | # Additionnaly, when the contact is a person, there can be the following 345 | # informations : 346 | # [:firstName] 347 | # the first name of the person. (The last name being stored in the +name+ 348 | # field in the +postalInfo+.) 349 | # [:list] 350 | # with the value of +restrictedPublication+ mean that the element 351 | # diffusion should be restricted. 352 | # 353 | # Optionnaly, there can be : 354 | # [:reachable] 355 | # the contact is reachable through the optional :media. 356 | # 357 | # The returned information contains new keys : 358 | # [:idStatus] 359 | # indicates the identification process status. It's only present when the 360 | # created contact was created with the +:individualInfos+ or 361 | # +:legalEntityInfos+ extensions. 362 | # [:nhStatus] 363 | # is a boolean indicating wether the contact is really new, or if there 364 | # was already a contact with the exact same informations in the database, 365 | # in which case, it has been returned. 366 | def contact_create(contact) 367 | super # placeholder so that I can add some doc 368 | end 369 | 370 | def contact_create_process(xml) #:nodoc: 371 | ret = super 372 | if (creData = xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:creData', EPPClient::SCHEMAS_URL)).size > 0 373 | ret[:nhStatus] = creData.xpath('frnic:nhStatus', EPPClient::SCHEMAS_URL).attr('new').value == '1' 374 | ret[:idStatus] = creData.xpath('frnic:idStatus', EPPClient::SCHEMAS_URL).text 375 | end 376 | ret 377 | end 378 | 379 | # Extends the EPPClient::Domain#domain_create to make sure there's no 380 | # :ns, :dsData or :keyData records, AFNIC's 381 | # servers sends quite a strange error when there is. 382 | def domain_create(args) 383 | raise ArgumentError, "You can't create a domain with ns records, you must do an update afterwards" if args.key?(:ns) 384 | raise ArgumentError, "You can't create a domain with ds or key records, you must do an update afterwards" if args.key?(:dsData) || args.key?(:keyData) 385 | super 386 | end 387 | 388 | # Raises an exception, as contacts are deleted with a garbage collector. 389 | def contact_delete(args) 390 | raise NotImplementedError, "Contacts are deleted with a garbage collector" 391 | end 392 | 393 | def contact_update_xml(args) #:nodoc: 394 | ret = super 395 | 396 | if [:add, :rem].any? {|c| args.key?(c) && [:list, :reachable, :idStatus].any? {|k| args[c].key?(k)}} 397 | ext = extension do |xml| 398 | xml.ext( :xmlns => EPPClient::SCHEMAS_URL['frnic']) do 399 | xml.update do 400 | xml.contact do 401 | [:add, :rem].each do |c| 402 | if args.key?(c) && [:list, :reachable, :idStatus].any? {|k| args[c].key?(k)} 403 | xml.__send__(c) do 404 | if args[c].key?(:list) 405 | xml.list(args[c][:list]) 406 | end 407 | if args[c].key?(:idStatus) 408 | xml.idStatus(args[c][:idStatus]) 409 | end 410 | if args[c].key?(:reachable) 411 | if Hash === (reachable = args[c][:reachable]) 412 | xml.reachable(reachable, 1) 413 | else 414 | raise ArgumentError, "reachable has to be a Hash" 415 | end 416 | end 417 | end 418 | end 419 | end 420 | end 421 | end 422 | end 423 | end 424 | 425 | return insert_extension(ret, ext) 426 | else 427 | return ret 428 | end 429 | end 430 | 431 | # Extends the EPPClient::Contact#contact_update so that the specific AFNIC 432 | # update informations can be sent, the additionnal informations are : 433 | # 434 | # [:add/:rem] 435 | # adds or removes the following datas : 436 | # [:list] 437 | # with the value of +restrictedPublication+ mean that the element 438 | # diffusion should/should not be restricted. 439 | # [:idStatus] 440 | # indicates the identification process status. 441 | # [:reachable] 442 | # the contact is reachable through the optional :media. 443 | def contact_update(args) 444 | super # placeholder so that I can add some doc 445 | end 446 | 447 | # Extends the EPPClient::Domain#domain_update so that AFNIC's weirdnesses 448 | # can be taken into account. 449 | # 450 | # AFNIC does not support ns/hostObj, only ns/hostAttr/Host*, so, take care 451 | # of this here. 452 | # Also, you can only do one of the following at a time : 453 | # * update contacts 454 | # * update name servers 455 | # * update status & authInfo 456 | def domain_update(args) 457 | if args.key?(:chg) && args[:chg].key?(:registrant) 458 | raise ArgumentError, "You need to do a trade or recover operation to change the registrant" 459 | end 460 | has_contacts = args.key?(:add) && args[:add].key?(:contacts) || args.key?(:add) && args[:add].key?(:contacts) 461 | has_ns = args.key?(:add) && args[:add].key?(:ns) || args.key?(:add) && args[:add].key?(:ns) 462 | has_other = args.key?(:add) && args[:add].key?(:status) || args.key?(:add) && args[:add].key?(:status) || args.key?(:chg) && args[:chg].key?(:authInfo) 463 | if [has_contacts, has_ns, has_other].select {|v| v}.size > 1 464 | raise ArgumentError, "You can't update all that at one time" 465 | end 466 | [:add, :rem].each do |ar| 467 | if args.key?(ar) && args[ar].key?(:ns) && String === args[ar][:ns].first 468 | args[ar][:ns] = args[ar][:ns].map {|ns| {:hostName => ns}} 469 | end 470 | end 471 | super 472 | end 473 | 474 | # Extends the EPPClient::Poll#poll_req to be able to parse quallification 475 | # response extension. 476 | def poll_req 477 | super # placeholder so that I can add some doc 478 | end 479 | 480 | EPPClient::Poll::PARSERS['frnic:ext/frnic:resData/frnic:quaData/frnic:contact'] = :contact_afnic_qualification 481 | 482 | def contact_afnic_qualification(xml) #:nodoc: 483 | contact = xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:quaData/frnic:contact', EPPClient::SCHEMAS_URL) 484 | ret = {:id => contact.xpath('frnic:id', EPPClient::SCHEMAS_URL).text} 485 | qP = contact.xpath('frnic:qualificationProcess', EPPClient::SCHEMAS_URL) 486 | ret[:qualificationProcess] = {:s => qP.attr('s').value} 487 | ret[:qualificationProcess][:lang] = qP.attr('lang').value if qP.attr('lang') 488 | if (leI = contact.xpath('frnic:legalEntityInfos', EPPClient::SCHEMAS_URL)).size > 0 489 | ret[:legalEntityInfos] = legalEntityInfos(leI) 490 | end 491 | reach = contact.xpath('frnic:reachability', EPPClient::SCHEMAS_URL) 492 | ret[:reachability] = {:reStatus => reach.xpath('frnic:reStatus', EPPClient::SCHEMAS_URL).text} 493 | if (voice = reach.xpath('frnic:voice', EPPClient::SCHEMAS_URL)).size > 0 494 | ret[:reachability][:voice] = voice.text 495 | end 496 | if (email = reach.xpath('frnic:email', EPPClient::SCHEMAS_URL)).size > 0 497 | ret[:reachability][:email] = email.text 498 | end 499 | ret 500 | end 501 | 502 | EPPClient::Poll::PARSERS['frnic:ext/frnic:resData/frnic:trdData/frnic:domain'] = :domain_afnic_trade_response 503 | 504 | def domain_afnic_trade_response(xml) #:nodoc: 505 | dom = xml.xpath('epp:extension/frnic:ext/frnic:resData/frnic:trdData/frnic:domain', EPPClient::SCHEMAS_URL) 506 | ret = { 507 | :name => dom.xpath('frnic:name', EPPClient::SCHEMAS_URL).text, 508 | :trStatus => dom.xpath('frnic:trStatus', EPPClient::SCHEMAS_URL).text, 509 | :reID => dom.xpath('frnic:reID', EPPClient::SCHEMAS_URL).text, 510 | :reDate => DateTime.parse(dom.xpath('frnic:reDate', EPPClient::SCHEMAS_URL).text), 511 | :acID => dom.xpath('frnic:acID', EPPClient::SCHEMAS_URL).text, 512 | } 513 | 514 | # FIXME: there are discrepencies between the 1.2 xmlschema, the documentation and the reality, I'm trying to stick to reality here. 515 | %w(reHldID acHldID).each do |f| 516 | if (field = dom.xpath("frnic:#{f}", EPPClient::SCHEMAS_URL)).size > 0 517 | ret[f.to_sym] = field.text 518 | end 519 | end 520 | %w(rhDate ahDate).each do |f| 521 | if (field = dom.xpath("frnic:#{f}", EPPClient::SCHEMAS_URL)).size > 0 522 | ret[f.to_sym] = DateTime.parse(field.text) 523 | end 524 | end 525 | ret 526 | end 527 | 528 | # keep that at the end. 529 | include EPPClient::RGP 530 | include EPPClient::SecDNS 531 | end 532 | end 533 | --------------------------------------------------------------------------------