<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Bennyland &#187; whm</title>
	<atom:link href="http://blog.bennyland.com/tag/whm/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.bennyland.com</link>
	<description>this server is running in my bedroom - benny’s learning how to run a linux server</description>
	<lastBuildDate>Thu, 06 Jan 2011 16:41:43 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Dynamic DNS support for WHM using ddclient</title>
		<link>http://blog.bennyland.com/2010/01/20/dynamic-dns-support-for-whm-using-ddclient/</link>
		<comments>http://blog.bennyland.com/2010/01/20/dynamic-dns-support-for-whm-using-ddclient/#comments</comments>
		<pubDate>Wed, 20 Jan 2010 23:11:01 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[dynamic dns]]></category>
		<category><![CDATA[guide]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[whm]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=36</guid>
		<description><![CDATA[The server which I use to use to host my websites is now primarily used as my DNS and also a backup ftp space for this server to store backups (that way I don't have to buy some sort of tape backup thing). This is great except for the fact that my IP changes a [...]]]></description>
			<content:encoded><![CDATA[<p>The server which I use to use to host my websites is now primarily used as my DNS and also a backup ftp space for this server to store backups (that way I don't have to buy some sort of tape backup thing).  This is great except for the fact that my IP changes a couple of times a year.  Up until now I was pushing requests from the webhost's DNS to <a title="dyndns" href="http://dyndns.org" target="_blank">dyndns</a> and then to my home server running <a title="ddclient" href="http://ddclient.sourceforge.net/" target="_blank">ddclient</a> to update dyndns.  Of course I could have paid around $30 a year per domain but I'm cheap.</p>
<p>So I modified ddclient so that it would update my host's DNS server.  My host uses <a title="cpanel and whm" href="http://www.cpanel.net/" target="_blank">cpanel/WHM</a> which luckily has a JSON API!</p>
<p>Here's how you can make the same modifications:<span id="more-36"></span></p>
<p>Install ddclient on your server - this is easy</p>
<pre class="brush: bash; title: ;">yum install ddclient</pre>
<p>You're also going to need some perl mods:</p>
<pre class="brush: bash; title: ;">cpan</pre>
<p>note: don't just copy and paste these 4 lines at once, some of the installs may have you type "yes" at some point.  In the case of WWW::Mechanize, it forced me to type "yes" at least 4 times.</p>
<pre class="brush: plain; title: ;">
install JSON
install JSON::XS
install WWW::Mechanize
exit
</pre>
<p>Now, make sure you have version 3.7.3 of ddclient and then patch it with this <a href='http://blog.bennyland.com/wp-content/uploads/2010/01/ddclient.tar.gz'>ddclient 3.7.3 patch file</a></p>
<pre class="brush: bash; title: ;">
cd /tmp
wget http://blog.bennyland.com/wp-content/uploads/2010/01/ddclient.tar.gz
tar -zxvf ddclient.tar.gz
cd ddclient patch -p0 /usr/sbin/ddclient &lt; ddclient.patch
</pre>
<p>Alternatively you can follow these directions:</p>
<pre class="brush: bash; title: ;">vim /usr/sbin/ddclient</pre>
<div style="border-top: black 2px dotted">
replace this:</p>
<pre class="brush: perl; first-line: 14; title: ;">
require 5.004;
use strict;
use Getopt::Long;
use Sys::Hostname;
use IO::Socket;
</pre>
<p>with:</p>
<pre class="brush: perl; first-line: 14; highlight: [19]; title: ;">
require 5.004;
use strict;
use Getopt::Long;
use Sys::Hostname;
use IO::Socket;
use WWW::Mechanize;
use JSON -support_by_pp;
</pre>
</div>
<div style="border-top: black 2px dotted">
replace this:</p>
<pre class="brush: perl; first-line: 474; title: ;">
    'sitelutions' =&gt; {
        'updateable' =&gt; undef,
        'update'     =&gt; \&amp;nic_sitelutions_update,
        'examples'   =&gt; \&amp;nic_sitelutions_examples,
        'variables'  =&gt; merge(
                          { 'server'       =&gt; setv(T_FQDNP,  1, 0, 1, 'www.sitelutions.com',   undef)    },
                          { 'min-interval' =&gt; setv(T_DELAY,  0, 0, 1, 0, interval('5m')),},
                          $variables{'service-common-defaults'},
                        ),
    },
</pre>
<p>with:</p>
<pre class="brush: perl; first-line: 474; highlight: [484]; title: ;">
    'sitelutions' =&gt; {
        'updateable' =&gt; undef,
        'update'     =&gt; \&amp;nic_sitelutions_update,
        'examples'   =&gt; \&amp;nic_sitelutions_examples,
        'variables'  =&gt; merge(
                          { 'server'       =&gt; setv(T_FQDNP,  1, 0, 1, 'www.sitelutions.com',   undef)    },
                          { 'min-interval' =&gt; setv(T_DELAY,  0, 0, 1, 0, interval('5m')),},
                          $variables{'service-common-defaults'},
                        ),
    },
    'whm' =&gt; {
        'updateable' =&gt; undef,
        'update'     =&gt; \&amp;nic_whm_update,
        'examples'   =&gt; \&amp;nic_whm_examples,
        'variables'  =&gt; merge(
                          { 'min-interval' =&gt; setv(T_DELAY,  0, 0, 1, interval('5m'), 0),},
                          $variables{'service-common-defaults'},
                        ),
    },
</pre>
</div>
<div style="border-top: black 2px dotted">
replace this:</p>
<pre class="brush: perl; first-line: 1410; title: ;">
sub split_by_comma {
    my $string = shift;

    return split /\s*[, ]\s*/, $string if defined $string;
    return ();
}
</pre>
<p>with:</p>
<pre class="brush: perl; first-line: 1410; highlight: [1416]; title: ;">
sub split_by_comma {
    my $string = shift;

    return split /\s*[, ]\s*/, $string if defined $string;
    return ();
}
sub split_by_semi {
    my $string = shift;

    return split /\s*[; ]\s*/, $string if defined $string;
    return ();
}
</pre>
</div>
<div style="border-top: black 2px dotted">
and at the very bottom of the file replace this:</p>
<pre class="brush: perl; first-line: 3185; title: ;">
######################################################################
# vim: ai ts=4 sw=4 tw=78 :
</pre>
<p>with:</p>
<pre class="brush: perl; first-line: 3185; highlight: [3185,3354]; title: ;">
######################################################################

######################################################################
## nic_whm_examples
######################################################################
sub nic_whm_examples {
    return &lt;&lt;EoEXAMPLE;

o 'whm'

The 'whm' protocol is an API provided by the cpanel/WHM system.

Configuration variables applicable to the 'whm' protocol are:
  protocol=whm                                   ##
  server=ip.of.your.whm.login  ## defaults to sitelutions.com
  login=service-login          ## login name and password  registered with the service
  password=service-password    ## whm hash used for API (not your whm password)
  host.to.update;host.to.update. #

Example ${program}.conf file entries:
  ## single host update
  protocol=whm,                                                                                                                                 \\
  login=my-whm-login,                                                                                                           \\
  password=a-really-long-hash-string                                            \\
  myhost.com;myhost.com.;subdomain1.myhost.com.;subdomain2.myhost.com.,myotherhost.com;subdomain2.myotherhost.com.

EoEXAMPLE
}
######################################################################
## nic_whm_update
##
## written by Benny Raymond
##
######################################################################
sub nic_whm_update {
    debug(&quot;\nnic_whm_update -------------------&quot;);
                my $has_failed = 0;
    ## update each configured host
    foreach my $h (@_) {
        my @host_and_names = split_by_semi($h);
        my $host_to_update = $host_and_names[0];
        my %hosts_names;
        undef %hosts_names;
        my $i = 0;
                for (@host_and_names) {
                        if ($i==0) {
                                $i++;
                        } else {
                                $hosts_names{$_} = 1;
                        }
                }
        info(&quot;setting IP address to %s for %s&quot;, $ip, $host_to_update);
        verbose(&quot;UPDATE:&quot;,&quot;updating %s&quot;, $host_to_update);

        my $url_lookup;
        $url_lookup     = &quot;http://$config{$h}{'server'}/json-api/dumpzone&quot;;
        $url_lookup .= &quot;?domain=$host_to_update&quot;;

        my $api_output = json_whm_fetch($config{$h}{'login'},$config{$h}{'password'},$url_lookup);
        if (!defined($api_output) || !$api_output) {
            failed(&quot;updating %s: Could not connect to %s.&quot;, $h, $url_lookup);
            last;
        }
        if (json_whm_status($api_output) != 1)
        {
                failed(&quot;updating %s: %s - %s&quot;, $h, json_whm_status_msg($api_output),  $url_lookup);
        }

        # loop through names and update
        # get the A records
        my(@a_records) = @{(json_whm_a_records($api_output))};
        # and update the DNS table
        foreach (@a_records)
        {
                my(%record) = %{($_)};
                if ($hosts_names{$record{name}})
                {
                    if ($record{address} eq $ip)
                    {
                        success(&quot;updating '%s' - '%s': good: IP already set to %s&quot;, $host_to_update, $record{name}, $ip);
                    }
                    else
                    {
                                # update this line
                    #    print $record{Line} . &quot;&gt;&quot; . $record{name};
                        $url_lookup     = &quot;http://$config{$h}{'server'}/json-api/editzonerecord&quot;;
                        $url_lookup .= &quot;?domain=$host_to_update&quot;;
                        $url_lookup .= &quot;&amp;Line=$record{Line}&quot;;
                        $url_lookup .= &quot;&amp;address=$ip&quot;;
                        my $api_update_output = json_whm_fetch($config{$h}{'login'},$config{$h}{'password'},$url_lookup);

                        if (json_whm_status($api_update_output) != 1)
                        {
                                failed(&quot;updating '%s' - '%s': %s %s&quot;, $host_to_update, $record{name}, json_whm_status_msg($api_update_output), $url_lookup);
                                $has_failed = 1;
                                $config{$h}{'status'} = 'failed';
                        } else {
                                success(&quot;updating '%s' - '%s': good: IP address set to %s&quot;, $host_to_update, $record{name}, $ip);
                        }
                     }
                } else {
                        # warning(&quot;name '%s' not found for host '%s'&quot;, $record{name}, $host_to_update);
                }
        }

        if ($has_failed == 0)
        {
          $config{$h}{'ip'}     = $ip;
          $config{$h}{'mtime'}  = $now;
          $config{$h}{'status'} = 'good';
        }
    }
}
sub json_whm_a_records
{
        my(%json) = %{(shift)};
        my(@records) = @{($json{result}[0]{record})};
        my(@a_records);
        foreach (@records)
        {
                my(%record) = %{($_)};
                if ($record{type} eq &quot;A&quot;)
                {
                        push(@a_records, \%record);
                }
        }
        return \@a_records;
}
sub json_whm_status
{
        my(%json) = %{(shift)};
        my $status = $json{result}[0]{status};
        return $status;
}
sub json_whm_status_msg
{
        my(%json) = %{(shift)};
        my $status = $json{result}[0]{statusmsg};
        return $status;
}
sub json_whm_fetch
{
        my ($whm_api_login) = $_[0];
        my ($whm_api_key) = $_[1];
        my ($json_url) = $_[2];
        my $browser = WWW::Mechanize-&gt;new();
        my @args = (
                Authorization =&gt; &quot;WHM &quot; . $whm_api_login . &quot;:&quot; . $whm_api_key
        );
        my $json_text = &quot;&quot;;
        eval{
                # download the json page:
                # print &quot;Getting json $json_url\n&quot;;
                $browser-&gt;get( $json_url, @args );
                my $content = $browser-&gt;content();
                my $json = new JSON;

                # these are some nice json options to relax restrictions a bit:
                $json_text = $json-&gt;allow_nonref-&gt;utf8-&gt;relaxed-&gt;escape_slash-&gt;loose-&gt;allow_singlequote-&gt;allow_barekey-&gt;decode($content);
        };
        # catch crashes:
        if($@){
                print STDERR &quot;[[JSON ERROR]] JSON parser crashed! $@\n&quot;;
                return &quot;&quot;;
        } else {
                return $json_text;
        }
}

######################################################################
# vim: ai ts=4 sw=4 tw=78 :
</pre>
</div>
<p>Save and close ddclient</p>
<pre class="brush: plain; title: ;">:wq</pre>
<p>Now, edit ddclient.conf.</p>
<pre class="brush: bash; title: ;">vim /etc/ddclient/ddclient.conf</pre>
<p>Here's a simple version (note: you need to log into WHM and get your API key or none of this is going to work.  You'll be entering your API key as one line - in WHM it is copied as several lines, just remove the carriage returns and enter it in one huge line as your password).  Notice how my domain looks, it's my domain name, followed by a colon, followed by colon seperated hostnames to update - they all have periods after them!</p>
<pre class="brush: perl; title: ;">
daemon=300                              # check every 300 seconds
syslog=yes                              # log update msgs to syslog
mail=root                               # mail all msgs to root
mail-failure=root                       # mail failed update msgs to root
pid=/var/run/ddclient.pid               # record PID in file.
ssl=yes                                 # use ssl-support.  Works with
                                        # ssl-library
## To obtain an IP address from Web status page (using the proxy if defined)
use=web, web=checkip.dyndns.org/, web-skip='IP Address' # found after IP Address

##
## WHM
##
server=host.or.ip.of.your.whm.maybe.with.port,       \
protocol=whm,                   \
login=your-whm-username,                 \
password=crazy-long-api-key-as-one-long-line               \
hostname.com;hostname.com.;subdomain.hostname.com.
</pre>
<p>Save and close ddclient.conf</p>
<pre class="brush: plain; title: ;">:wq</pre>
<p>And finally run it and tell it to run every time the server reboots:</p>
<pre class="brush: bash; title: ;">
/sbin/chkconfig ddclient on
/sbin/service ddclient start
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2010/01/20/dynamic-dns-support-for-whm-using-ddclient/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

