Adding reverse DNS record may be a time consuming task, especially
for IPv6 addresses. Having a way to automatically create a reverse
record when a forward record is created could speed up the process.
host-add command already has this possibility.

This patch takes advantage of the new per-type API and adds new
options for A/AAAA record types: --a-create-reverse and
--aaaa-create-reverse. These commands can be used to automatically
create reverse records for new A/AAAA addresses (both forward
and reverse zones need to be managed by FreeIPA server):

ipa dnsrecord-add example.com foo --a-rec=10.0.0.1 --a-create-reverse

This command would add a new A record to record foo in zone
example.com and a PTR record to appropriate reverse zone for
IP address 10.0.0.1 (for example PTR record 1 in zone
0.0.10.in-addr.arpa. pointing to foo.example.com.).

Few modification were done to new DNS API to support this feature:
 - Refactor --ip-address option handling from host-add and place it
   to dns.py to be used by both modules
 - Add support for "extra" per-type options
 - Hide DNS record part options in dnsrecord_find command as they
   have no effect for this command

https://fedorahosted.org/freeipa/ticket/2009

>From 739d4a781590492056eb9161aebaa9e959b52c54 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Mon, 23 Jan 2012 15:40:23 +0100
Subject: [PATCH] Add reverse DNS record when forward is created

Adding reverse DNS record may be a time consuming task, especially
for IPv6 addresses. Having a way to automatically create a reverse
record when a forward record is created could speed up the process.
host-add command already has this possibility.

This patch takes advantage of the new per-type API and adds new
options for A/AAAA record types: --a-create-reverse and
--aaaa-create-reverse. These commands can be used to automatically
create reverse records for new A/AAAA addresses (both forward
and reverse zones need to be managed by FreeIPA server):

ipa dnsrecord-add example.com foo --a-rec=10.0.0.1 --a-create-reverse

This command would add a new A record to record foo in zone
example.com and a PTR record to appropriate reverse zone for
IP address 10.0.0.1 (for example PTR record 1 in zone
0.0.10.in-addr.arpa. pointing to foo.example.com.).

Few modification were done to new DNS API to support this feature:
 - Refactor --ip-address option handling from host-add and place it
   to dns.py to be used by both modules
 - Add support for "extra" per-type options
 - Hide DNS record part options in dnsrecord_find command as they
   have no effect for this command

https://fedorahosted.org/freeipa/ticket/2009
---
 API.txt                              |  144 +++++-------------
 VERSION                              |    2 +-
 ipalib/plugins/dns.py                |  285 +++++++++++++++++++++++++++++-----
 ipalib/plugins/host.py               |  104 ++-----------
 tests/test_xmlrpc/test_dns_plugin.py |   42 +++++
 5 files changed, 344 insertions(+), 233 deletions(-)

diff --git a/API.txt b/API.txt
index 2937c24f4d6aa53b1028f430a05ef6453544ea7c..569d716ffe4c0da8fc09333f5f4ada739565f1fb 100644
--- a/API.txt
+++ b/API.txt
@@ -610,15 +610,17 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('result', <type 'bool'>, None)
 output: Output('value', <type 'unicode'>, None)
 command: dnsrecord_add
-args: 2,113,3
+args: 2,115,3
 arg: Str('dnszoneidnsname', cli_name='dnszone', query=True, required=True)
 arg: Str('idnsname', attribute=True, cli_name='name', multivalue=False, primary_key=True, required=True)
 option: Int('dnsttl', attribute=True, cli_name='ttl', multivalue=False, required=False)
 option: StrEnum('dnsclass', attribute=True, cli_name='class', multivalue=False, required=False, values=(u'IN', u'CS', u'CH', u'HS'))
 option: ARecord('arecord', attribute=True, cli_name='a_rec', csv=True, multivalue=True, option_group=u'A Record', required=False)
 option: Str('a_part_ip_address', attribute=False, cli_name='a_ip_address', multivalue=False, option_group=u'A Record', required=False)
+option: Flag('a_extra_create_reverse', attribute=False, autofill=True, cli_name='a_create_reverse', default=False, multivalue=False, option_group=u'A Record', required=False)
 option: AAAARecord('aaaarecord', attribute=True, cli_name='aaaa_rec', csv=True, multivalue=True, option_group=u'AAAA Record', required=False)
 option: Str('aaaa_part_ip_address', attribute=False, cli_name='aaaa_ip_address', multivalue=False, option_group=u'AAAA Record', required=False)
+option: Flag('aaaa_extra_create_reverse', attribute=False, autofill=True, cli_name='aaaa_create_reverse', default=False, multivalue=False, option_group=u'AAAA Record', required=False)
 option: A6Record('a6record', attribute=True, cli_name='a6_rec', csv=True, multivalue=True, option_group=u'A6 Record', required=False)
 option: AFSDBRecord('afsdbrecord', attribute=True, cli_name='afsdb_rec', csv=True, multivalue=True, option_group=u'AFSDB Record', required=False)
 option: Int('afsdb_part_subtype', attribute=False, cli_name='afsdb_subtype', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'AFSDB Record', required=False)
@@ -786,116 +788,46 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('result', <type 'dict'>, None)
 output: Output('value', <type 'unicode'>, None)
 command: dnsrecord_find
-args: 2,114,4
+args: 2,44,4
 arg: Str('dnszoneidnsname', cli_name='dnszone', query=True, required=True)
 arg: Str('criteria?', noextrawhitespace=False)
 option: Str('idnsname', attribute=True, autofill=False, cli_name='name', multivalue=False, primary_key=True, query=True, required=False)
 option: Int('dnsttl', attribute=True, autofill=False, cli_name='ttl', multivalue=False, query=True, required=False)
 option: StrEnum('dnsclass', attribute=True, autofill=False, cli_name='class', multivalue=False, query=True, required=False, values=(u'IN', u'CS', u'CH', u'HS'))
-option: ARecord('arecord', attribute=True, autofill=False, cli_name='a_rec', csv=True, multivalue=True, option_group=u'A Record', query=True, required=False)
-option: Str('a_part_ip_address', attribute=False, autofill=False, cli_name='a_ip_address', multivalue=False, option_group=u'A Record', query=True, required=False)
-option: AAAARecord('aaaarecord', attribute=True, autofill=False, cli_name='aaaa_rec', csv=True, multivalue=True, option_group=u'AAAA Record', query=True, required=False)
-option: Str('aaaa_part_ip_address', attribute=False, autofill=False, cli_name='aaaa_ip_address', multivalue=False, option_group=u'AAAA Record', query=True, required=False)
-option: A6Record('a6record', attribute=True, autofill=False, cli_name='a6_rec', csv=True, multivalue=True, option_group=u'A6 Record', query=True, required=False)
-option: AFSDBRecord('afsdbrecord', attribute=True, autofill=False, cli_name='afsdb_rec', csv=True, multivalue=True, option_group=u'AFSDB Record', query=True, required=False)
-option: Int('afsdb_part_subtype', attribute=False, autofill=False, cli_name='afsdb_subtype', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'AFSDB Record', query=True, required=False)
-option: Str('afsdb_part_hostname', attribute=False, autofill=False, cli_name='afsdb_hostname', multivalue=False, option_group=u'AFSDB Record', query=True, required=False)
-option: APLRecord('aplrecord', attribute=True, autofill=False, cli_name='apl_rec', csv=True, multivalue=True, option_group=u'APL Record', query=True, required=False)
-option: CERTRecord('certrecord', attribute=True, autofill=False, cli_name='cert_rec', csv=True, multivalue=True, option_group=u'CERT Record', query=True, required=False)
-option: Int('cert_part_type', attribute=False, autofill=False, cli_name='cert_type', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'CERT Record', query=True, required=False)
-option: Int('cert_part_key_tag', attribute=False, autofill=False, cli_name='cert_key_tag', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'CERT Record', query=True, required=False)
-option: Int('cert_part_algorithm', attribute=False, autofill=False, cli_name='cert_algorithm', maxvalue=255, minvalue=0, multivalue=False, option_group=u'CERT Record', query=True, required=False)
-option: Str('cert_part_certificate_or_crl', attribute=False, autofill=False, cli_name='cert_certificate_or_crl', multivalue=False, option_group=u'CERT Record', query=True, required=False)
-option: CNAMERecord('cnamerecord', attribute=True, autofill=False, cli_name='cname_rec', csv=True, multivalue=True, option_group=u'CNAME Record', query=True, required=False)
-option: Str('cname_part_hostname', attribute=False, autofill=False, cli_name='cname_hostname', multivalue=False, option_group=u'CNAME Record', query=True, required=False)
-option: DHCIDRecord('dhcidrecord', attribute=True, autofill=False, cli_name='dhcid_rec', csv=True, multivalue=True, option_group=u'DHCID Record', query=True, required=False)
-option: DLVRecord('dlvrecord', attribute=True, autofill=False, cli_name='dlv_rec', csv=True, multivalue=True, option_group=u'DLV Record', query=True, required=False)
-option: DNAMERecord('dnamerecord', attribute=True, autofill=False, cli_name='dname_rec', csv=True, multivalue=True, option_group=u'DNAME Record', query=True, required=False)
-option: Str('dname_part_target', attribute=False, autofill=False, cli_name='dname_target', multivalue=False, option_group=u'DNAME Record', query=True, required=False)
-option: DNSKEYRecord('dnskeyrecord', attribute=True, autofill=False, cli_name='dnskey_rec', csv=True, multivalue=True, option_group=u'DNSKEY Record', query=True, required=False)
-option: DSRecord('dsrecord', attribute=True, autofill=False, cli_name='ds_rec', csv=True, multivalue=True, option_group=u'DS Record', query=True, required=False)
-option: Int('ds_part_key_tag', attribute=False, autofill=False, cli_name='ds_key_tag', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'DS Record', query=True, required=False)
-option: Int('ds_part_algorithm', attribute=False, autofill=False, cli_name='ds_algorithm', maxvalue=255, minvalue=0, multivalue=False, option_group=u'DS Record', query=True, required=False)
-option: Int('ds_part_digest_type', attribute=False, autofill=False, cli_name='ds_digest_type', maxvalue=255, minvalue=0, multivalue=False, option_group=u'DS Record', query=True, required=False)
-option: Str('ds_part_digest', attribute=False, autofill=False, cli_name='ds_digest', multivalue=False, option_group=u'DS Record', query=True, required=False)
-option: HIPRecord('hiprecord', attribute=True, autofill=False, cli_name='hip_rec', csv=True, multivalue=True, option_group=u'HIP Record', query=True, required=False)
-option: IPSECKEYRecord('ipseckeyrecord', attribute=True, autofill=False, cli_name='ipseckey_rec', csv=True, multivalue=True, option_group=u'IPSECKEY Record', query=True, required=False)
-option: KEYRecord('keyrecord', attribute=True, autofill=False, cli_name='key_rec', csv=True, multivalue=True, option_group=u'KEY Record', query=True, required=False)
-option: Int('key_part_flags', attribute=False, autofill=False, cli_name='key_flags', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'KEY Record', query=True, required=False)
-option: Int('key_part_protocol', attribute=False, autofill=False, cli_name='key_protocol', maxvalue=255, minvalue=0, multivalue=False, option_group=u'KEY Record', query=True, required=False)
-option: Int('key_part_algorithm', attribute=False, autofill=False, cli_name='key_algorithm', maxvalue=255, minvalue=0, multivalue=False, option_group=u'KEY Record', query=True, required=False)
-option: Str('key_part_public_key', attribute=False, autofill=False, cli_name='key_public_key', multivalue=False, option_group=u'KEY Record', query=True, required=False)
-option: KXRecord('kxrecord', attribute=True, autofill=False, cli_name='kx_rec', csv=True, multivalue=True, option_group=u'KX Record', query=True, required=False)
-option: Int('kx_part_preference', attribute=False, autofill=False, cli_name='kx_preference', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'KX Record', query=True, required=False)
-option: Str('kx_part_exchanger', attribute=False, autofill=False, cli_name='kx_exchanger', multivalue=False, option_group=u'KX Record', query=True, required=False)
-option: LOCRecord('locrecord', attribute=True, autofill=False, cli_name='loc_rec', csv=True, multivalue=True, option_group=u'LOC Record', query=True, required=False)
-option: Int('loc_part_lat_deg', attribute=False, autofill=False, cli_name='loc_lat_deg', maxvalue=90, minvalue=0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
-option: Int('loc_part_lat_min', attribute=False, autofill=False, cli_name='loc_lat_min', maxvalue=59, minvalue=0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
-option: Decimal('loc_part_lat_sec', attribute=False, autofill=False, cli_name='loc_lat_sec', maxvalue=Decimal('59.999'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=3, query=True, required=False)
-option: StrEnum('loc_part_lat_dir', attribute=False, autofill=False, cli_name='loc_lat_dir', multivalue=False, option_group=u'LOC Record', query=True, required=False, values=(u'N', u'S'))
-option: Int('loc_part_lon_deg', attribute=False, autofill=False, cli_name='loc_lon_deg', maxvalue=180, minvalue=0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
-option: Int('loc_part_lon_min', attribute=False, autofill=False, cli_name='loc_lon_min', maxvalue=59, minvalue=0, multivalue=False, option_group=u'LOC Record', query=True, required=False)
-option: Decimal('loc_part_lon_sec', attribute=False, autofill=False, cli_name='loc_lon_sec', maxvalue=Decimal('59.999'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=3, query=True, required=False)
-option: StrEnum('loc_part_lon_dir', attribute=False, autofill=False, cli_name='loc_lon_dir', multivalue=False, option_group=u'LOC Record', query=True, required=False, values=(u'E', u'W'))
-option: Decimal('loc_part_altitude', attribute=False, autofill=False, cli_name='loc_altitude', maxvalue=Decimal('42849672.95'), minvalue=Decimal('-100000.00'), multivalue=False, option_group=u'LOC Record', precision=2, query=True, required=False)
-option: Decimal('loc_part_size', attribute=False, autofill=False, cli_name='loc_size', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, query=True, required=False)
-option: Decimal('loc_part_h_precision', attribute=False, autofill=False, cli_name='loc_h_precision', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, query=True, required=False)
-option: Decimal('loc_part_v_precision', attribute=False, autofill=False, cli_name='loc_v_precision', maxvalue=Decimal('90000000.00'), minvalue=Decimal('0.0'), multivalue=False, option_group=u'LOC Record', precision=2, query=True, required=False)
-option: MXRecord('mxrecord', attribute=True, autofill=False, cli_name='mx_rec', csv=True, multivalue=True, option_group=u'MX Record', query=True, required=False)
-option: Int('mx_part_preference', attribute=False, autofill=False, cli_name='mx_preference', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'MX Record', query=True, required=False)
-option: Str('mx_part_exchanger', attribute=False, autofill=False, cli_name='mx_exchanger', multivalue=False, option_group=u'MX Record', query=True, required=False)
-option: NAPTRRecord('naptrrecord', attribute=True, autofill=False, cli_name='naptr_rec', csv=True, multivalue=True, option_group=u'NAPTR Record', query=True, required=False)
-option: Int('naptr_part_order', attribute=False, autofill=False, cli_name='naptr_order', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'NAPTR Record', query=True, required=False)
-option: Int('naptr_part_preference', attribute=False, autofill=False, cli_name='naptr_preference', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'NAPTR Record', query=True, required=False)
-option: Str('naptr_part_flags', attribute=False, autofill=False, cli_name='naptr_flags', multivalue=False, option_group=u'NAPTR Record', query=True, required=False)
-option: Str('naptr_part_service', attribute=False, autofill=False, cli_name='naptr_service', multivalue=False, option_group=u'NAPTR Record', query=True, required=False)
-option: Str('naptr_part_regexp', attribute=False, autofill=False, cli_name='naptr_regexp', multivalue=False, option_group=u'NAPTR Record', query=True, required=False)
-option: Str('naptr_part_replacement', attribute=False, autofill=False, cli_name='naptr_replacement', multivalue=False, option_group=u'NAPTR Record', query=True, required=False)
-option: NSRecord('nsrecord', attribute=True, autofill=False, cli_name='ns_rec', csv=True, multivalue=True, option_group=u'NS Record', query=True, required=False)
-option: Str('ns_part_hostname', attribute=False, autofill=False, cli_name='ns_hostname', multivalue=False, option_group=u'NS Record', query=True, required=False)
-option: NSECRecord('nsecrecord', attribute=True, autofill=False, cli_name='nsec_rec', csv=True, multivalue=True, option_group=u'NSEC Record', query=True, required=False)
-option: Str('nsec_part_next', attribute=False, autofill=False, cli_name='nsec_next', multivalue=False, option_group=u'NSEC Record', query=True, required=False)
-option: StrEnum('nsec_part_types', attribute=False, autofill=False, cli_name='nsec_types', multivalue=True, option_group=u'NSEC Record', query=True, required=False, values=(u'SOA', u'A', u'AAAA', u'A6', u'AFSDB', u'APL', u'CERT', u'CNAME', u'DHCID', u'DLV', u'DNAME', u'DNSKEY', u'DS', u'HIP', u'IPSECKEY', u'KEY', u'KX', u'LOC', u'MX', u'NAPTR', u'NS', u'NSEC', u'NSEC3', u'NSEC3PARAM', u'PTR', u'RRSIG', u'RP', u'SIG', u'SPF', u'SRV', u'SSHFP', u'TA', u'TKEY', u'TSIG', u'TXT'))
-option: NSEC3Record('nsec3record', attribute=True, autofill=False, cli_name='nsec3_rec', csv=True, multivalue=True, option_group=u'NSEC3 Record', query=True, required=False)
-option: NSEC3PARAMRecord('nsec3paramrecord', attribute=True, autofill=False, cli_name='nsec3param_rec', csv=True, multivalue=True, option_group=u'NSEC3PARAM Record', query=True, required=False)
-option: PTRRecord('ptrrecord', attribute=True, autofill=False, cli_name='ptr_rec', csv=True, multivalue=True, option_group=u'PTR Record', query=True, required=False)
-option: Str('ptr_part_hostname', attribute=False, autofill=False, cli_name='ptr_hostname', multivalue=False, option_group=u'PTR Record', query=True, required=False)
-option: RRSIGRecord('rrsigrecord', attribute=True, autofill=False, cli_name='rrsig_rec', csv=True, multivalue=True, option_group=u'RRSIG Record', query=True, required=False)
-option: StrEnum('rrsig_part_type_covered', attribute=False, autofill=False, cli_name='rrsig_type_covered', multivalue=False, option_group=u'RRSIG Record', query=True, required=False, values=(u'SOA', u'A', u'AAAA', u'A6', u'AFSDB', u'APL', u'CERT', u'CNAME', u'DHCID', u'DLV', u'DNAME', u'DNSKEY', u'DS', u'HIP', u'IPSECKEY', u'KEY', u'KX', u'LOC', u'MX', u'NAPTR', u'NS', u'NSEC', u'NSEC3', u'NSEC3PARAM', u'PTR', u'RRSIG', u'RP', u'SPF', u'SRV', u'SSHFP', u'TA', u'TKEY', u'TSIG', u'TXT'))
-option: Int('rrsig_part_algorithm', attribute=False, autofill=False, cli_name='rrsig_algorithm', maxvalue=255, minvalue=0, multivalue=False, option_group=u'RRSIG Record', query=True, required=False)
-option: Int('rrsig_part_labels', attribute=False, autofill=False, cli_name='rrsig_labels', maxvalue=255, minvalue=0, multivalue=False, option_group=u'RRSIG Record', query=True, required=False)
-option: Int('rrsig_part_original_ttl', attribute=False, autofill=False, cli_name='rrsig_original_ttl', minvalue=0, multivalue=False, option_group=u'RRSIG Record', query=True, required=False)
-option: Str('rrsig_part_signature_expiration', attribute=False, autofill=False, cli_name='rrsig_signature_expiration', multivalue=False, option_group=u'RRSIG Record', query=True, required=False)
-option: Str('rrsig_part_signature_inception', attribute=False, autofill=False, cli_name='rrsig_signature_inception', multivalue=False, option_group=u'RRSIG Record', query=True, required=False)
-option: Int('rrsig_part_key_tag', attribute=False, autofill=False, cli_name='rrsig_key_tag', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'RRSIG Record', query=True, required=False)
-option: Str('rrsig_part_signers_name', attribute=False, autofill=False, cli_name='rrsig_signers_name', multivalue=False, option_group=u'RRSIG Record', query=True, required=False)
-option: Str('rrsig_part_signature', attribute=False, autofill=False, cli_name='rrsig_signature', multivalue=False, option_group=u'RRSIG Record', query=True, required=False)
-option: RPRecord('rprecord', attribute=True, autofill=False, cli_name='rp_rec', csv=True, multivalue=True, option_group=u'RP Record', query=True, required=False)
-option: SIGRecord('sigrecord', attribute=True, autofill=False, cli_name='sig_rec', csv=True, multivalue=True, option_group=u'SIG Record', query=True, required=False)
-option: StrEnum('sig_part_type_covered', attribute=False, autofill=False, cli_name='sig_type_covered', multivalue=False, option_group=u'SIG Record', query=True, required=False, values=(u'SOA', u'A', u'AAAA', u'A6', u'AFSDB', u'APL', u'CERT', u'CNAME', u'DHCID', u'DLV', u'DNAME', u'DNSKEY', u'DS', u'HIP', u'IPSECKEY', u'KEY', u'KX', u'LOC', u'MX', u'NAPTR', u'NS', u'NSEC', u'NSEC3', u'NSEC3PARAM', u'PTR', u'RRSIG', u'RP', u'SPF', u'SRV', u'SSHFP', u'TA', u'TKEY', u'TSIG', u'TXT'))
-option: Int('sig_part_algorithm', attribute=False, autofill=False, cli_name='sig_algorithm', maxvalue=255, minvalue=0, multivalue=False, option_group=u'SIG Record', query=True, required=False)
-option: Int('sig_part_labels', attribute=False, autofill=False, cli_name='sig_labels', maxvalue=255, minvalue=0, multivalue=False, option_group=u'SIG Record', query=True, required=False)
-option: Int('sig_part_original_ttl', attribute=False, autofill=False, cli_name='sig_original_ttl', minvalue=0, multivalue=False, option_group=u'SIG Record', query=True, required=False)
-option: Str('sig_part_signature_expiration', attribute=False, autofill=False, cli_name='sig_signature_expiration', multivalue=False, option_group=u'SIG Record', query=True, required=False)
-option: Str('sig_part_signature_inception', attribute=False, autofill=False, cli_name='sig_signature_inception', multivalue=False, option_group=u'SIG Record', query=True, required=False)
-option: Int('sig_part_key_tag', attribute=False, autofill=False, cli_name='sig_key_tag', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'SIG Record', query=True, required=False)
-option: Str('sig_part_signers_name', attribute=False, autofill=False, cli_name='sig_signers_name', multivalue=False, option_group=u'SIG Record', query=True, required=False)
-option: Str('sig_part_signature', attribute=False, autofill=False, cli_name='sig_signature', multivalue=False, option_group=u'SIG Record', query=True, required=False)
-option: SPFRecord('spfrecord', attribute=True, autofill=False, cli_name='spf_rec', csv=True, multivalue=True, option_group=u'SPF Record', query=True, required=False)
-option: SRVRecord('srvrecord', attribute=True, autofill=False, cli_name='srv_rec', csv=True, multivalue=True, option_group=u'SRV Record', query=True, required=False)
-option: Int('srv_part_priority', attribute=False, autofill=False, cli_name='srv_priority', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'SRV Record', query=True, required=False)
-option: Int('srv_part_weight', attribute=False, autofill=False, cli_name='srv_weight', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'SRV Record', query=True, required=False)
-option: Int('srv_part_port', attribute=False, autofill=False, cli_name='srv_port', maxvalue=65535, minvalue=0, multivalue=False, option_group=u'SRV Record', query=True, required=False)
-option: Str('srv_part_target', attribute=False, autofill=False, cli_name='srv_target', multivalue=False, option_group=u'SRV Record', query=True, required=False)
-option: SSHFPRecord('sshfprecord', attribute=True, autofill=False, cli_name='sshfp_rec', csv=True, multivalue=True, option_group=u'SSHFP Record', query=True, required=False)
-option: Int('sshfp_part_algorithm', attribute=False, autofill=False, cli_name='sshfp_algorithm', maxvalue=255, minvalue=0, multivalue=False, option_group=u'SSHFP Record', query=True, required=False)
-option: Int('sshfp_part_fp_type', attribute=False, autofill=False, cli_name='sshfp_fp_type', maxvalue=255, minvalue=0, multivalue=False, option_group=u'SSHFP Record', query=True, required=False)
-option: Str('sshfp_part_fingerprint', attribute=False, autofill=False, cli_name='sshfp_fingerprint', multivalue=False, option_group=u'SSHFP Record', query=True, required=False)
-option: TARecord('tarecord', attribute=True, autofill=False, cli_name='ta_rec', csv=True, multivalue=True, option_group=u'TA Record', query=True, required=False)
-option: TKEYRecord('tkeyrecord', attribute=True, autofill=False, cli_name='tkey_rec', csv=True, multivalue=True, option_group=u'TKEY Record', query=True, required=False)
-option: TSIGRecord('tsigrecord', attribute=True, autofill=False, cli_name='tsig_rec', csv=True, multivalue=True, option_group=u'TSIG Record', query=True, required=False)
-option: TXTRecord('txtrecord', attribute=True, autofill=False, cli_name='txt_rec', csv=True, multivalue=True, option_group=u'TXT Record', query=True, required=False)
-option: Str('txt_part_data', attribute=False, autofill=False, cli_name='txt_data', multivalue=False, option_group=u'TXT Record', query=True, required=False)
+option: ARecord('arecord', attribute=True, autofill=False, cli_name='a_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: AAAARecord('aaaarecord', attribute=True, autofill=False, cli_name='aaaa_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: A6Record('a6record', attribute=True, autofill=False, cli_name='a6_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: AFSDBRecord('afsdbrecord', attribute=True, autofill=False, cli_name='afsdb_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: APLRecord('aplrecord', attribute=True, autofill=False, cli_name='apl_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: CERTRecord('certrecord', attribute=True, autofill=False, cli_name='cert_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: CNAMERecord('cnamerecord', attribute=True, autofill=False, cli_name='cname_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: DHCIDRecord('dhcidrecord', attribute=True, autofill=False, cli_name='dhcid_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: DLVRecord('dlvrecord', attribute=True, autofill=False, cli_name='dlv_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: DNAMERecord('dnamerecord', attribute=True, autofill=False, cli_name='dname_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: DNSKEYRecord('dnskeyrecord', attribute=True, autofill=False, cli_name='dnskey_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: DSRecord('dsrecord', attribute=True, autofill=False, cli_name='ds_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: HIPRecord('hiprecord', attribute=True, autofill=False, cli_name='hip_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: IPSECKEYRecord('ipseckeyrecord', attribute=True, autofill=False, cli_name='ipseckey_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: KEYRecord('keyrecord', attribute=True, autofill=False, cli_name='key_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: KXRecord('kxrecord', attribute=True, autofill=False, cli_name='kx_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: LOCRecord('locrecord', attribute=True, autofill=False, cli_name='loc_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: MXRecord('mxrecord', attribute=True, autofill=False, cli_name='mx_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: NAPTRRecord('naptrrecord', attribute=True, autofill=False, cli_name='naptr_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: NSRecord('nsrecord', attribute=True, autofill=False, cli_name='ns_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: NSECRecord('nsecrecord', attribute=True, autofill=False, cli_name='nsec_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: NSEC3Record('nsec3record', attribute=True, autofill=False, cli_name='nsec3_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: NSEC3PARAMRecord('nsec3paramrecord', attribute=True, autofill=False, cli_name='nsec3param_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: PTRRecord('ptrrecord', attribute=True, autofill=False, cli_name='ptr_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: RRSIGRecord('rrsigrecord', attribute=True, autofill=False, cli_name='rrsig_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: RPRecord('rprecord', attribute=True, autofill=False, cli_name='rp_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: SIGRecord('sigrecord', attribute=True, autofill=False, cli_name='sig_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: SPFRecord('spfrecord', attribute=True, autofill=False, cli_name='spf_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: SRVRecord('srvrecord', attribute=True, autofill=False, cli_name='srv_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: SSHFPRecord('sshfprecord', attribute=True, autofill=False, cli_name='sshfp_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: TARecord('tarecord', attribute=True, autofill=False, cli_name='ta_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: TKEYRecord('tkeyrecord', attribute=True, autofill=False, cli_name='tkey_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: TSIGRecord('tsigrecord', attribute=True, autofill=False, cli_name='tsig_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
+option: TXTRecord('txtrecord', attribute=True, autofill=False, cli_name='txt_rec', csv=True, multivalue=True, option_group=None, query=True, required=False)
 option: Int('timelimit?', autofill=False, minvalue=0)
 option: Int('sizelimit?', autofill=False, minvalue=0)
 option: Flag('structured', autofill=True, default=False)
diff --git a/VERSION b/VERSION
index ff245b559f67f80f83119d15a154c30185ad0f55..2afbba27df3864db51223e9c7dfa9b185a466853 100644
--- a/VERSION
+++ b/VERSION
@@ -79,4 +79,4 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=21
+IPA_API_VERSION_MINOR=22
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index da4934fc971cb1cb8b2b930cad55b341b692cb5c..a643a54c30f7cc496450b670956b9ff442846c12 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -30,7 +30,7 @@ from ipalib.plugins.baseldap import *
 from ipalib import _, ngettext
 from ipalib.util import validate_zonemgr, normalize_zonemgr, validate_hostname
 from ipapython import dnsclient
-from ipapython.ipautil import valid_ip
+from ipapython.ipautil import valid_ip, CheckedIPAddress
 from ldap import explode_dn
 
 __doc__ = _("""
@@ -80,8 +80,10 @@ EXAMPLES:
  Add LOC record for example.com:
    ipa dnsrecord-add example.com @ --loc-rec="49 11 42.4 N 16 36 29.6 E 227.64m"
 
- Add new A record for www.example.com: (random IP)
-   ipa dnsrecord-add example.com www --a-rec=80.142.15.2
+ Add new A record for www.example.com. Create a reverse record in appropriate
+ reverse zone as well. In this case a PTR record "2" pointing to www.example.com.
+ will be created in zone 15.142.80.in-addr.arpa.
+   ipa dnsrecord-add example.com www --a-rec=80.142.15.2 --a-create-reverse
 
  Add new PTR record for www.example.com
    ipa dnsrecord-add 15.142.80.in-addr.arpa. 2 --ptr-rec=www.example.com.
@@ -246,8 +248,129 @@ def _normalize_hostname(domain_name):
     else:
         return domain_name
 
+def is_forward_record(zone, str_address):
+    addr = netaddr.IPAddress(str_address)
+    if addr.version == 4:
+        result = api.Command['dnsrecord_find'](zone, arecord=str_address)
+    elif addr.version == 6:
+        result = api.Command['dnsrecord_find'](zone, aaaarecord=str_address)
+    else:
+        raise ValueError('Invalid address family')
+
+    return result['count'] > 0
+
+def add_forward_record(zone, name, str_address):
+    addr = netaddr.IPAddress(str_address)
+    try:
+        if addr.version == 4:
+            api.Command['dnsrecord_add'](zone, name, arecord=str_address)
+        elif addr.version == 6:
+            api.Command['dnsrecord_add'](zone, name, aaaarecord=str_address)
+        else:
+            raise ValueError('Invalid address family')
+    except errors.EmptyModlist:
+        pass # the entry already exists and matches
+
+def get_reverse_zone(ipaddr, prefixlen=None):
+    ip = netaddr.IPAddress(ipaddr)
+    revdns = unicode(ip.reverse_dns)
+
+    if prefixlen is None:
+        revzone = u''
+
+        result = api.Command['dnszone_find']()['result']
+        for zone in result:
+            zonename = zone['idnsname'][0]
+            if revdns.endswith(zonename) and len(zonename) > len(revzone):
+                revzone = zonename
+    else:
+        if ip.version == 4:
+            pos = 4 - prefixlen / 8
+        elif ip.version == 6:
+            pos = 32 - prefixlen / 4
+        items = ip.reverse_dns.split('.')
+        revzone = u'.'.join(items[pos:])
+
+        try:
+            api.Command['dnszone_show'](revzone)
+        except errors.NotFound:
+            revzone = u''
+
+    if len(revzone) == 0:
+        raise errors.NotFound(
+            reason=_('DNS reverse zone for IP address %(addr)s not found') % dict(addr=ipaddr)
+        )
+
+    revname = revdns[:-len(revzone)-1]
+
+    return revzone, revname
+
+def add_records_for_host_precheck(host, domain, ip_addresses, check_forward=True, check_reverse=True):
+    result = api.Command['dnszone_find']()['result']
+    match = False
+    for zone in result:
+        if domain == zone['idnsname'][0]:
+            match = True
+            break
+    if not match:
+        raise errors.NotFound(
+            reason=_('DNS zone %(zone)s not found') % dict(zone=domain)
+        )
+    if not isinstance(ip_addresses, (tuple, list)):
+        ip_addresses = [ip_addresses]
+
+    for ip_address in ip_addresses:
+        ip = CheckedIPAddress(ip_address, match_local=False)
+
+        if check_forward:
+            if is_forward_record(domain, unicode(ip)):
+                raise errors.DuplicateEntry(
+                        message=_(u'IP address %(ip)s is already assigned in domain %(domain)s.')\
+                            % dict(ip=str(ip), domain=domain))
+
+        if check_reverse:
+            try:
+                prefixlen = None
+                if not ip.defaultnet:
+                    prefixlen = ip.prefixlen
+                # we prefer lookup of the IP through the reverse zone
+                revzone, revname = get_reverse_zone(ip, prefixlen)
+                reverse = api.Command['dnsrecord_find'](revzone, idnsname=revname)
+                if reverse['count'] > 0:
+                    raise errors.DuplicateEntry(
+                            message=_(u'Reverse record for IP address %(ip)s already exists in reverse zone %(zone)s.')\
+                            % dict(ip=str(ip), zone=revzone))
+            except errors.NotFound:
+                pass
+
+
+def add_records_for_host(host, domain, ip_addresses, add_forward=True, add_reverse=True):
+    if not isinstance(ip_addresses, (tuple, list)):
+        ip_addresses = [ip_addresses]
+
+    for ip_address in ip_addresses:
+        ip = CheckedIPAddress(ip_address, match_local=False)
+
+        if add_forward:
+            add_forward_record(domain, host, unicode(ip))
+
+        if add_reverse:
+            try:
+                prefixlen = None
+                if not ip.defaultnet:
+                    prefixlen = ip.prefixlen
+                revzone, revname = get_reverse_zone(ip, prefixlen)
+                addkw = { 'ptrrecord' : host + "." + domain }
+                api.Command['dnsrecord_add'](revzone, revname, **addkw)
+            except errors.EmptyModlist:
+                # the entry already exists and matches
+                pass
+
 class DNSRecord(Str):
+    # a list of parts that create the actual raw DNS record
     parts = None
+    # an optional list of parameters used in record-specific operations
+    extra = None
     supported = True
     # supported RR types: https://fedorahosted.org/bind-dyndb-ldap/browser/doc/schema
 
@@ -257,6 +380,7 @@ class DNSRecord(Str):
     option_group_format = _('%s Record')
     see_rfc_msg = _("(see RFC %s for details)")
     part_name_format = "%s_part_%s"
+    extra_name_format = "%s_extra_%s"
     cli_name_format = "%s_%s"
     format_error_msg = None
 
@@ -398,30 +522,57 @@ class DNSRecord(Str):
             part.validate(val)
         return None
 
+    def _convert_dnsrecord_part(self, part):
+        """
+        All parts of DNSRecord need to be processed and modified before they
+        can be added to global DNS API. For example a prefix need to be added
+        before part name so that the name is unique in the global namespace.
+        """
+        name = self.part_name_format % (self.rrtype.lower(), part.name)
+        cli_name = self.cli_name_format % (self.rrtype.lower(), part.name)
+        label = self.part_label_format % (self.rrtype, unicode(part.label))
+        option_group = self.option_group_format % self.rrtype
+        flags = list(part.flags) + ['dnsrecord_part', 'virtual_attribute',]
+
+        if not part.required:
+            flags.append('dnsrecord_optional')
+
+        return part.clone_rename(name,
+                     cli_name=cli_name,
+                     label=label,
+                     required=False,
+                     option_group=option_group,
+                     flags=flags)
+
+    def _convert_dnsrecord_extra(self, extra):
+        """
+        Parameters for special per-type behavior need to be processed in the
+        same way as record parts in _convert_dnsrecord_part().
+        """
+        name = self.extra_name_format % (self.rrtype.lower(), extra.name)
+        cli_name = self.cli_name_format % (self.rrtype.lower(), extra.name)
+        label = self.part_label_format % (self.rrtype, unicode(extra.label))
+        option_group = self.option_group_format % self.rrtype
+        flags = list(extra.flags) + ['dnsrecord_extra', 'virtual_attribute',]
+
+        return extra.clone_rename(name,
+                     cli_name=cli_name,
+                     label=label,
+                     required=False,
+                     option_group=option_group,
+                     flags=flags)
+
     def get_parts(self):
         if self.parts is None:
             return tuple()
 
-        parts = []
+        return tuple(self._convert_dnsrecord_part(part) for part in self.parts)
 
-        for part in self.parts:
-            name = self.part_name_format % (self.rrtype.lower(), part.name)
-            cli_name = self.cli_name_format % (self.rrtype.lower(), part.name)
-            label = self.part_label_format % (self.rrtype, unicode(part.label))
-            option_group = self.option_group_format % self.rrtype
-            flags = list(part.flags) + ['dnsrecord_part', 'virtual_attribute',]
+    def get_extra(self):
+        if self.extra is None:
+            return tuple()
 
-            if not part.required:
-                flags.append('dnsrecord_optional')
-
-            parts.append(part.clone_rename(name,
-                         cli_name=cli_name,
-                         label=label,
-                         required=False,
-                         option_group=option_group,
-                         flags=flags))
-
-        return tuple(parts)
+        return tuple(self._convert_dnsrecord_extra(extra) for extra in self.extra)
 
     def prompt_parts(self, backend, mod_dnsvalue=None):
         mod_parts = None
@@ -451,7 +602,47 @@ class DNSRecord(Str):
 
         return user_options
 
-class ARecord(DNSRecord):
+    # callbacks for per-type special record behavior
+    def dnsrecord_add_pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        pass
+
+    def dnsrecord_add_post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        pass
+
+class ForwardRecord(DNSRecord):
+    extra = (
+        Flag('create_reverse?',
+            label=_('Create reverse'),
+            doc=_('Create reverse record for this IP Address'),
+            flags=['no_update']
+        ),
+    )
+
+    def dnsrecord_add_pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        reverse_option = self._convert_dnsrecord_extra(self.extra[0])
+        if options.get(reverse_option.name):
+            records = entry_attrs.get(self.name, [])
+
+            for record in records:
+                add_records_for_host_precheck(keys[-1], keys[-2], records,
+                        check_forward=False,
+                        check_reverse=True)
+
+            setattr(context, '%s_reverse' % self.name, entry_attrs.get(self.name))
+
+    def dnsrecord_add_post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        records = getattr(context, '%s_reverse' % self.name, [])
+
+        for record in records:
+            try:
+                add_records_for_host(keys[-1], keys[-2], record,
+                    add_forward=False, add_reverse=True)
+            except Exception, e:
+                raise errors.NonFatalError(
+                    reason=_('Cannot create reverse record for "%(value)s": %(exc)s') \
+                            % dict(value=record, exc=unicode(e)))
+
+class ARecord(ForwardRecord):
     rrtype = 'A'
     rfc = 1035
     parts = (
@@ -466,7 +657,7 @@ class A6Record(DNSRecord):
     rfc = 3226
     parts = None    # experimental rr type
 
-class AAAARecord(DNSRecord):
+class AAAARecord(ForwardRecord):
     rrtype = 'AAAA'
     rfc = 3596
     parts = (
@@ -1060,6 +1251,9 @@ def __dns_record_options_iter():
         for part in option.get_parts():
             yield part
 
+        for extra in option.get_extra():
+            yield extra
+
 _dns_record_options = tuple(__dns_record_options_iter())
 
 # dictionary of valid reverse zone -> number of address components
@@ -1083,18 +1277,6 @@ def is_ns_rec_resolvable(name):
             reason=_('Nameserver \'%(host)s\' does not have a corresponding A/AAAA record') % {'host': name}
         )
 
-def add_forward_record(zone, name, str_address):
-    addr = netaddr.IPAddress(str_address)
-    try:
-        if addr.version == 4:
-            api.Command['dnsrecord_add'](zone, name, arecord=str_address)
-        elif addr.version == 6:
-            api.Command['dnsrecord_add'](zone, name, aaaarecord=str_address)
-        else:
-            raise ValueError('Invalid address family')
-    except errors.EmptyModlist:
-        pass # the entry already exists and matches
-
 def dns_container_exists(ldap):
     try:
         ldap.get_entry(api.env.container_dns, [])
@@ -1626,6 +1808,22 @@ class dnsrecord_add(LDAPCreate):
                 dnsvalue = [record_option._convert_scalar(parts)]
                 entry_attrs[record_option.name] = dnsvalue
 
+        new_attrs = []
+        for attr in entry_attrs:
+            try:
+                param = self.params[attr]
+            except KeyError:
+                continue
+
+            if not isinstance(param, DNSRecord):
+                continue
+            param.dnsrecord_add_pre_callback(ldap, dn, entry_attrs, attrs_list, *keys, **options)
+            new_attrs.append(attr)
+
+        # Store all new attrs so that DNSRecord post callback is called for
+        # new attributes only and not for all attributes in the LDAP entry
+        setattr(context, 'dnsrecord_add_attrs', new_attrs)
+
         try:
             (dn_, old_entry) = ldap.get_entry(
                         dn, entry_attrs.keys(),
@@ -1656,6 +1854,10 @@ class dnsrecord_add(LDAPCreate):
         raise exc
 
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        for attr in getattr(context, 'dnsrecord_add_attrs', []):
+            param = self.params[attr]
+            param.dnsrecord_add_post_callback(ldap, dn, entry_attrs, *keys, **options)
+
         self.obj.postprocess_record(entry_attrs, **options)
 
         return dn
@@ -1839,7 +2041,8 @@ class dnsrecord_del(LDAPUpdate):
 
     def get_options(self):
         for option in super(dnsrecord_del, self).get_options():
-            if 'dnsrecord_part' in option.flags:
+            if any(flag in option.flags for flag in \
+                    ('dnsrecord_part', 'dnsrecord_extra',)):
                 continue
             elif isinstance(option, DNSRecord):
                 yield option.clone(option_group=None)
@@ -1984,6 +2187,16 @@ class dnsrecord_find(LDAPSearch):
         dnsrecord.structured_flag,
     )
 
+    def get_options(self):
+        for option in super(dnsrecord_find, self).get_options():
+            if any(flag in option.flags for flag in \
+                    ('dnsrecord_part', 'dnsrecord_extra',)):
+                continue
+            elif isinstance(option, DNSRecord):
+                yield option.clone(option_group=None)
+                continue
+            yield option
+
     def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *args, **options):
         # include zone record (root entry) in the search
         return (filter, base_dn, ldap.SCOPE_SUBTREE)
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index fb5f4ea2416abead430487eb71d3a531c80ad80d..e1bbc690c42996c6034da60ae6202067be750c30 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -29,8 +29,8 @@ from ipalib.plugins.baseldap import *
 from ipalib.plugins.service import split_principal
 from ipalib.plugins.service import validate_certificate
 from ipalib.plugins.service import set_certificate_attrs
-from ipalib.plugins.dns import dns_container_exists, _record_types
-from ipalib.plugins.dns import add_forward_record
+from ipalib.plugins.dns import dns_container_exists, _record_types, add_records_for_host_precheck, add_records_for_host
+from ipalib.plugins.dns import get_reverse_zone
 from ipalib import _, ngettext
 from ipalib import x509
 from ipapython.ipautil import ipa_generate_password, CheckedIPAddress
@@ -101,51 +101,6 @@ def validate_host(ugettext, fqdn):
         return _('Fully-qualified hostname required')
     return None
 
-def is_forward_record(zone, str_address):
-    addr = netaddr.IPAddress(str_address)
-    if addr.version == 4:
-        result = api.Command['dnsrecord_find'](zone, arecord=str_address)
-    elif addr.version == 6:
-        result = api.Command['dnsrecord_find'](zone, aaaarecord=str_address)
-    else:
-        raise ValueError('Invalid address family')
-
-    return result['count'] > 0
-
-def get_reverse_zone(ipaddr, prefixlen=None):
-    ip = netaddr.IPAddress(ipaddr)
-    revdns = unicode(ip.reverse_dns)
-
-    if prefixlen is None:
-        revzone = u''
-
-        result = api.Command['dnszone_find']()['result']
-        for zone in result:
-            zonename = zone['idnsname'][0]
-            if revdns.endswith(zonename) and len(zonename) > len(revzone):
-                revzone = zonename
-    else:
-        if ip.version == 4:
-            pos = 4 - prefixlen / 8
-        elif ip.version == 6:
-            pos = 32 - prefixlen / 4
-        items = ip.reverse_dns.split('.')
-        revzone = u'.'.join(items[pos:])
-
-        try:
-            api.Command['dnszone_show'](revzone)
-        except errors.NotFound:
-            revzone = u''
-
-    if len(revzone) == 0:
-        raise errors.NotFound(
-            reason=_('DNS reverse zone for IP address %(addr)s not found') % dict(addr=ipaddr)
-        )
-
-    revname = revdns[:-len(revzone)-1]
-
-    return revzone, revname
-
 def remove_fwd_ptr(ipaddr, host, domain, recordtype):
     api.log.debug('deleting ipaddr %s' % ipaddr)
     try:
@@ -383,35 +338,14 @@ class host_add(LDAPCreate):
     )
 
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
-        if 'ip_address' in options and dns_container_exists(ldap):
+        if options.get('ip_address') and dns_container_exists(ldap):
             parts = keys[-1].split('.')
+            host = parts[0]
             domain = unicode('.'.join(parts[1:]))
-            result = api.Command['dnszone_find']()['result']
-            match = False
-            for zone in result:
-                if domain == zone['idnsname'][0]:
-                    match = True
-                    break
-            if not match:
-                raise errors.NotFound(
-                    reason=_('DNS zone %(zone)s not found') % dict(zone=domain)
-                )
-            ip = CheckedIPAddress(options['ip_address'], match_local=False)
-            if not options.get('no_reverse', False):
-                try:
-                    prefixlen = None
-                    if not ip.defaultnet:
-                        prefixlen = ip.prefixlen
-                    # we prefer lookup of the IP through the reverse zone
-                    revzone, revname = get_reverse_zone(ip, prefixlen)
-                    reverse = api.Command['dnsrecord_find'](revzone, idnsname=revname)
-                    if reverse['count'] > 0:
-                        raise errors.DuplicateEntry(message=u'This IP address is already assigned.')
-                except errors.NotFound:
-                    pass
-            else:
-                if is_forward_record(domain, unicode(ip)):
-                    raise errors.DuplicateEntry(message=u'This IP address is already assigned.')
+            check_reverse = not options.get('no_reverse', False)
+            add_records_for_host_precheck(host, domain, options['ip_address'],
+                    check_forward=True,
+                    check_reverse=check_reverse)
         if not options.get('force', False) and not 'ip_address' in options:
             util.validate_host_dns(self.log, keys[-1])
         if 'locality' in entry_attrs:
@@ -447,25 +381,15 @@ class host_add(LDAPCreate):
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         exc = None
         try:
-            if 'ip_address' in options and dns_container_exists(ldap):
+            if options.get('ip_address') and dns_container_exists(ldap):
                 parts = keys[-1].split('.')
+                host = parts[0]
                 domain = unicode('.'.join(parts[1:]))
+                add_reverse = not options.get('no_reverse', False)
 
-                ip = CheckedIPAddress(options['ip_address'], match_local=False)
-                add_forward_record(domain, parts[0], unicode(ip))
-
-                if not options.get('no_reverse', False):
-                    try:
-                        prefixlen = None
-                        if not ip.defaultnet:
-                            prefixlen = ip.prefixlen
-                        revzone, revname = get_reverse_zone(ip, prefixlen)
-                        addkw = { 'ptrrecord' : keys[-1]+'.' }
-                        api.Command['dnsrecord_add'](revzone, revname, **addkw)
-                    except errors.EmptyModlist:
-                        # the entry already exists and matches
-                        pass
-
+                add_records_for_host(host, domain, options['ip_address'],
+                                     add_forward=True,
+                                     add_reverse=add_reverse)
                 del options['ip_address']
         except Exception, e:
             exc = e
diff --git a/tests/test_xmlrpc/test_dns_plugin.py b/tests/test_xmlrpc/test_dns_plugin.py
index bded2ad42f1813cf101faf9ba5895bdf78f6699c..59f7eafb02c1ab209617a45deee2e5154f3eee6c 100644
--- a/tests/test_xmlrpc/test_dns_plugin.py
+++ b/tests/test_xmlrpc/test_dns_plugin.py
@@ -42,6 +42,8 @@ dnsres1 = u'testdnsres'
 dnsres1_dn = DN(('idnsname',dnsres1), dnszone1_dn)
 dnsrev1 = u'80'
 dnsrev1_dn = DN(('idnsname',dnsrev1), revdnszone1_dn)
+dnsrev2 = u'81'
+dnsrev2_dn = DN(('idnsname',dnsrev2), revdnszone1_dn)
 
 class test_dns(Declarative):
 
@@ -727,6 +729,46 @@ class test_dns(Declarative):
 
 
         dict(
+            desc='Try to create duplicate PTR record for %r with --a-create-reverse' % dnsres1,
+            command=('dnsrecord_add', [dnszone1, dnsres1], {'arecord': u'80.142.15.80',
+                                                            'a_extra_create_reverse' : True}),
+            expected=errors.DuplicateEntry(message=u''),
+        ),
+
+
+        dict(
+            desc='Create A record %r in zone %r with --a-create-reverse' % (dnsres1, dnszone1),
+            command=('dnsrecord_add', [dnszone1, dnsres1], {'arecord': u'80.142.15.81',
+                                                            'a_extra_create_reverse' : True}),
+            expected={
+                'value': dnsres1,
+                'summary': None,
+                'result': {
+                    'dn': unicode(dnsres1_dn),
+                    'idnsname': [dnsres1],
+                    'objectclass': [u'top', u'idnsrecord'],
+                    'arecord': [u'80.142.15.81'],
+                },
+            },
+        ),
+
+
+        dict(
+            desc='Check reverse record for %r created via --a-create-reverse' % dnsres1,
+            command=('dnsrecord_show', [revdnszone1, dnsrev2], {}),
+            expected={
+                'value': dnsrev2,
+                'summary': None,
+                'result': {
+                    'dn': unicode(dnsrev2_dn),
+                    'idnsname': [dnsrev2],
+                    'ptrrecord': [dnsres1 + '.' + dnszone1 + '.'],
+                },
+            },
+        ),
+
+
+        dict(
             desc='Delete zone %r' % dnszone1,
             command=('dnszone_del', [dnszone1], {}),
             expected={
-- 
1.7.7.5

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to