Dynamic DNS in Hostmonster with Ruby(easily extensible to any cpanel)

It’s been awhile since I wrote something. Now I wanna show you how to make changes into DNS records managed by CPanel with Ruby and Mechanize.

Everyone remembers services like dyndns and no-ip, but when we got a hosting and a domain name at some place like Hostmonster, we still want to point a subdomain to our home server. But it’s pretty annoying to log into Cpanel and change the records manually. So I wrote this little tool.

This specific code works for Hostmonster, but it’s easily extensible to any(I guess) Cpanel based hosting.

First you need to install ruby with mechanize and json libraries. In a Debian/Mint/Ubuntu way you can do typing the next on console:

sudo apt-get install ruby ruby-mechanize ruby-json

Next you need the following code:

#!/usr/bin/env ruby

require 'mechanize'
require 'json'

USERNAME	= 'MY_USERNAME'
PASSWORD	= 'MY_PASSWD'
DOMAIN		= 'positrones.net'
SUBDOMAIN	= 'my_subdomain'
ADDRESS		= '0.0.0.0' # If nil then try to get automagicaly

URLS = {
			'login'		=> 'https://login.hostmonster.com/?',
			'drecords'	=> 'https://my.hostmonster.com/cgi/dm/zoneedit/ajax'
		}

USER_AGENT = 'Windows IE 9'

m = Mechanize.new do |a|
	a.verify_mode = OpenSSL::SSL::VERIFY_NONE
	a.user_agent_alias = USER_AGENT
end

def get_ip
  r = Net::HTTP.get('myip.positrones.net', '/')
  ip = r.match(/\d{1,4}\.\d{1,4}\.\d{1,4}\.\d{1,4}/)

  ip[0].to_s
end

# Do the login stuff
print "Checking user and password... "
page = m.get( URLS['login'] );
form = page.form_with( :name => 'theform' )
form['login'] = USERNAME
form['password'] = PASSWORD
send_button = form.button_with(:value => 'Login')
form.click_button(send_button)
page = page.links[0].click
puts "done!"

# Edit the DNS Zone
page = page.link_with(:text => 'DNS Zone Editor').click
print "Getting old zone records... "
json = JSON.parse( m.post( URLS['drecords'], {'op' => 'getzonerecords', 'domain' => DOMAIN} ).body )
puts "done!"

print "Trying to get subdomain old info... "
json['data'].each do |r|
	if r['name'] == SUBDOMAIN
		print "done!\nSaving new address[#{r['address']} => #{ADDRESS||get_ip}]... "
		json = m.post( URLS['drecords'], {'op'		=> 'editzonerecord',
									'domain'		=> DOMAIN,
									'name'			=> SUBDOMAIN,
									'orig__name'	=> SUBDOMAIN,
									'address'		=> ADDRESS||get_ip,
									'orig__adress'	=> r['address'],
									'ttl'			=> r['ttl'],
									'orig__ttl'		=> r['ttl'],
									'Line'			=> r['Line'],
									'type'			=> r['type']}
			)
		json = JSON.parse(json.body)

		if( json['result'] == 1 )
			puts "done!\nAddress changed succesfully!"
			Kernel::exit(0)
		else
			puts "\nAn error has ocurred trying to save new address :("
			Kernel::exit(1)
		end
	end
end

puts "The subdomain #{SUBDOMAIN} cannot be found!"
Kernel::exit(1)

The first lines just load the libraries «mechanize» and «json».
Then we stablish the data of the hosting provider, the names are self explanatory, just few points:

  1. The domain must not have the www
  2. The subdomain must be alone. If you wanna change «code.positrones.net» just type «code»
  3. You can specify the new IP address of the A record. If you wanna use your public IP address to be detected, change to nil

The URLS hash contains the information of the login page and the ajax DNS editor of the Cpanel, change it to fit your needs 😉

The USER_AGENT it’s an id defined in the Mechanize library, in this case we want to identify as IE 9.

The login process usually takes place through an encrypted connection(HTTPS), then, for reasons not exposed here, we deactivate the verification of the SSL certificate and then set the USER_AGENT.

Then we define a method called get_ip, this method connects to a site which only returns the IP address of the client.

The rest of code log in into Cpanel, go to DNS Zone Editor and change the information of the given subdomain. The use Mechanize library it’s exposed in other post, you can read it if you wanna know the details.

The program returns 0 if everything goes fine, 1 in other case. By the way, I didn’t write some error management sentences, so be careful when use this code.

You can download from here.

Enjoy. 🙂

Chalupas

La comida de hoy. Riquisimas chalupas estilo Actopan. Con la asesoria, ayuda y supervisión de Lupita.

Me comí poco más de veinte.

Deliciosas.

El hijo de Stalin

1578470506<
El libro me lo regaló mi hermanita en mi cumple y me entretuvo un par de semanas. No sé si la traducción es la mala o el libro es malo. Lo que si puedo afirmar es que está bastante entretenido.

Digo para 10 pesos que dijo que le costó está genial.

Ahora no sé si seguir con la ecuación de Dante(otro regalo) o 20 años después(patrocinado por el Gnaro).

Ya veremos…

Watching files with ruby and fssm

On GNU/Linux exists a library called inotify which does that, notifies for changes(change, create, delete) of files into a directory. Over Mac there is FSEvents, which essentially do the same as inotify, with some exceptions of course.

Now, what happened if I have 2 servers which I want to monitor for file changes?. You could say: «write one app and run on 2 servers». Yeah!, nice answer, but 1 of them is running GNU/Linux and the other one is running MacOS, so we need something like Ruby to do so.

First we install fssm(File System State Monitor) from rubygems:

gem install fssm

Maybe you need do the above as root. Next go to your favorite editor, I use Geany.

Of course, the first lines will be:

#!/usr/bin/env ruby

require 'rubygems'
require 'fssm'

The first line calls for the ruby interpreter and the next 2 loads rubygems and fssm ruby extensions.

Now go to the magic code, it’s pretty simple and easy to undestand.


FSSM.monitor("/tmp/test") do
	update do |b, r|
		puts "Someone changes the file '#{r}' into '#{b}'"
	end

	create do |b, r|
		puts "Someone creates the file '#{r}' into '#{b}'"
	end

	delete do |b, r|
		puts "Someone deletes the file '#{r}' into '#{b}'"
	end
end

The first line enters to a loop that watch for filesystem changes only into /tmp/test directory.

The second line states which actions gonna take the program if the some file inside the /tmp/test directory has been updated and passes the variables b and r to the block(in this case just line number 3). r is the relative path to the updated, b is the absolute path(/tmp/test in this case); then a message is printed on the screen.

The code for the created and deleted files are the same.

Now, the FSSM.monitor has 3 possible arguments, we just use 1 of them in the above example.

The second parameter is a glob pattern(or array of glob patterns) that the files must match to be watched by fssm, if this parameter is not specified, the default is **/*.

The third parameter is for watching directories. By default, fssm watch just for file changes, if we want to watch directories too we need to pass the option here, doing this :directories => true, and additionally, the blocks for update, delete and create must have another block variable:

delete do |b, r, t|
	puts "Someone delete #{r} into #{b} which is a #{t == :directory ? 'directory' : 'file'}"
end

Where t is the type, it means if is a directory or a file, we check for this in a «short» if statement. t variable can be the symbols :directory or :file. AFAIK this will not work as expected on MacOS, I appreciate if someone can confirm, I’m a poor guy and I don’t own a Mac. :P. Anyway it don’t watch for created, deleted or updated directories, but if you create a directory inside /tmp/test and then create a file inside, it will be detected.

So, the complete code with checking for directories is:

#!/usr/bin/env ruby

require 'rubygems'
require 'fssm'

FSSM.monitor("/tmp/test", '**/*', :directories => true) do
 update do |b, r, t|
 puts "Someone changes #{r} into #{b} which is a #{t == :directory ? 'directory' : 'file'}"
 end

 create do |b, r, t|
 puts "Someone create #{r} into #{b} which is a #{t == :directory ? 'directory' : 'file'}"
 end

 delete do |b, r, t|
 puts "Someone delete #{r} into #{b} which is a #{t == :directory ? 'directory' : 'file'}"
 end
end

That’s it!. Now we can watch for filesystem changes in many different systems. It’s so nice and useful. Of course we need to develop and work a little more to do something nicer.

I hope this will help someone, works for me.

See you soon.

Changing IP Address of a Thomson TG585-V8 Router

My old ADSL internet modem HG2701was burned for some reason and I requested for a replacement to my ISP and it gave me a Thomson TG585-V8 modem which makes useles a little python script that I found around to change the IP and keep downloading… backup copies of something.

As a coincidence some days ago I read about a library called mechanize and of course using one of my favorite languages(ruby) I decided to write a little program to change my IP address.

Here, the description of mechanize library stolen from it’s website:

«The Mechanize library is used for automating interaction with websites. Mechanize automatically stores and sends cookies, follows redirects, can follow links, and submit forms. Form fields can be populated and submitted. Mechanize also keeps track of the sites that you have visited as a history.»

After read some examples and write some testing code I reach to a very simple script that does the magic:

require 'mechanize'

USER = 'USERNAME'
PASSWORD = 'PASSWORD'

URL = 'http://192.168.1.254/cgi/b/is/_pppoe_/ov/?be=0&l0=2&l1=2&name=Internet'

BUTTONS = ['13', '12'] #First is to deconnect, next for reconnect

FORM = 'PPPConn'
SNAME = 'Internet'

m = Mechanize.new

m.user_agent_alias = 'Windows IE 7'
m.auth(USER, PASSWORD)

BUTTONS.each do |bn|
 page = m.get(URL)
 f = page.form_with(FORM)

 f['0'] = bn
 f['1'] = SNAME

 b = f.button_with(:name => bn)
 f.click_button(b)
end

A basic explanation of the above code:

  • We create an instance of the Mechanize class
  • Now we inform to the website which interact to us that we are some kind of webbrowser
  • And then we need to autenticate to the site
  • Then we get the page, the form and fill the correct values
  • Now we click the button to kill the connection
  • So, we repeat the page, form, button, and click the connect button to get a new IP address

Of course, the code has no error checking, it’s a really basic version. But I like it ’cause it’s just a few code.

You can get the code from my bitbucket utils repository and of course I hope to add some features, not so much, it does what suppose to do.

Happy hollidays!.

Generate «secure» passwords

It’s been awhile since I posted something, and even more since I posted computer stuff.

Well, this time I make a little script to generate «secure» passwords from command line with options to modify the number of generated passwords and the number of characters in each password.

The real trick it’s done through a combination of a few shell tools.

  • < : The redirection symbol, it means, send from one side to another.
  • /dev/urandom : «Unblocked» random source
  • tr : Translate or delete characters
  • head : Output the first part of files

So, let’s build the expression which brings to us the passwords. First of all we use

</dev/urandom

to send /dev/urandom output to stdout. The next step is to process the chars dropped by urandom, this can be made with tr command  and the -dc options, d deletes chars and c takes the complement of the given parameter,

</dev/urandom tr -dc '1234567890!@#$%&/()=qwertyuiopQWERTYUIOPasdfghjklASDFGHJKLzxcvbnmZXCVBNM'

, so, tr will take the complement of that ugly string and then delete it, so it removes all the chars that are not inside the long string above, leaving just an «easily» read string.

Now, if we leave the command as is, we get an extremely large string and of course, unusable as password, so we need to cut it, then we pipe to head command with the -cn option, it says «just take the first n characters of anything is given to you». By this time we set n=8, a nice length for a good password.

</dev/urandom tr -dc  '1234567890!@#$%&/()=qwertyuiopQWERTYUIOPasdfghjklASDFGHJKLzxcvbnmZXCVBNM' | head -c8

Now, you can try it!. It gives you a 8 char length password and as you can see, it can be considered(most times) a good password. As an extra, you can put an echo “” at the end of the command to tell bash that puts a new line before the prompt.

</dev/urandom tr -dc   '1234567890!@#$%&/()=qwertyuiopQWERTYUIOPasdfghjklASDFGHJKLzxcvbnmZXCVBNM'  | head -c8; echo ""

And if you want to automatize the generation of passwords and variate the number of generated passwords and the length of it, you can build a little script, I’m not gonna explain to you, but I leave it here, use it and enjoy it.

#!/usr/bin/env bash

genpass()
{
 </dev/urandom tr -dc '1234567890!@#$%&/()=qwertyuiopQWERTYUIOPasdfghjklASDFGHJKLzxcvbnmZXCVBNM' | head -c${1}
}

usage()
{
 echo "${0##*/} c p"
 echo -e "\tc is the number of chars in each generated password"
 echo -e "\tp is the number of password to be generated\n"
 exit 1
}

control()
{
 if [ "$#" = "0" ];then
 npass=1
 cpass=8
 elif [ "$#" = "1" ];then
 if [ "$1" = "-h" ];then
 usage
 elif [ "$1" = "--help" ];then
 usage
 fi

 cpass=$1
 npass=1
 else
 cpass=$1
 npass=$2
 fi

 echo "Chars in each password: ${cpass}"
 echo "Number of passwords to generate: ${npass}"
 echo -e "Generating...\n"

 i=1
 while [ ${i} -le ${npass} ];do
 echo -n -e "\t${i}: "
 genpass ${cpass}
 echo ""
 let i=i+1;
 done

 echo ""
}

control $1 $2

See you soon!.