├── .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 |
--------------------------------------------------------------------------------