#!/usr/bin/ruby require 'digest/md5' require 'socket' # BEGIN ===== User setup section ===== # Account information ID = 'your_id' PASSWD = 'your_password' HOST = 'example.com' # How to get my current global IP ? # If you can't understand what you should do, # please comment out following paragraph, # and simply write down, IP_CURRENT = nil. IP_CURRENT = Proc.new{ /(?:\d{1,3}\.){3}\d{1,3}/ =~ `ifconfig ppp0` $& } # Program type setting # auto background update(IS_DAEMON = true) / user manual update(IS_DAEMON = false) ? IS_DAEMON = true UPDATE_PEROID = 60 * 60 #[sec] LOG_FILE = 'ip-updater.log' # END ===== User setup section ===== # Target Servers, SRV_UPDATE means the update server, and SRV_CHECK means the ip check server. SRV_UPDATE_HOST = 'update.3domain.hk' SRV_UPDATE_PORT = 9120 SRV_CHECK_HOST = 'myip.3domain.hk' SRV_CHECK_PORT = 9121 # About this program AGENT_NAME = 'Ruby-Updater' AGENT_VERSION = '0.2' AGENT_MODE = 'MD5' # main program begins from the next line. def err? res return !(res and !(res =~ /ERR/)) end def update(ip_previous = nil) print("#{Time.now.to_s}, Trying...\n") ip_current = nil if IP_CURRENT ip_current = IP_CURRENT.call else soc = UDPSocket.open soc.send("\n", 0, SRV_CHECK_HOST, SRV_CHECK_PORT) begin if /(?:\d{1,3}\.){3}\d{1,3}/ =~ soc.recv(32) then ip_current = $& end rescue print "WARNING: the IP reporting server is now out of service.\n" end end # check IP whether the DDNS registered IP equals my current global IP. if ip_previous and ip_previous == ip_current then print "MESSAGE: No need to update, now DDNS.IP == IP_CURRENT\n" return ip_current end soc = TCPSocket.open(SRV_UPDATE_HOST, SRV_UPDATE_PORT) begin if err?(soc.gets) then raise Exception.new("ERROR: Fail to open socket!!") end soc.write("AGENT #{AGENT_NAME}/#{AGENT_VERSION}\n") # send about this program if err?(soc.gets) then raise Exception.new("ERROR: From the update server; #{$_}") end case AGENT_MODE when 'MD5' soc.write("LOGIN #{ID} digest-md5\n") # send login cmd if !(soc.gets =~ /CHALLENGE\s([0-9a-fA-F]*)/) then raise Exception.new("ERROR: From the update server; #{$_}") end md5_passwd = Digest::MD5.digest(PASSWD) response = Digest::MD5.hexdigest(md5_passwd + [$1].pack('H*')) soc.write("RESPONSE #{response}\n") if err?(soc.gets) then raise Exception.new("ERROR: From the update server; #{$_}") end else soc.write("LOGIN #{ID} plain\n") # send login cmd soc.write("#{PASSWD}") # send passwd if err?(soc.gets) then raise Exception.new("ERROR: From the update server; #{$_}") end end soc.write("A_UPDATE online #{HOST}#{ip_current ? (' ' + ip_current) : ''}\n") if err?(soc.gets) then raise Exception.new("ERROR: From the update server; #{$_}") end print "MESSAGE: IP updated successfully; #{$_}" /(?:\d{1,3}\.){3}\d{1,3}/ =~ $_ ip_current = $& rescue print ($!) ensure soc.write("EXIT\n") soc.close end return ip_current end if IS_DAEMON then exit! if fork # fork # log file setting log_file = nil if LOG_FILE then log_file = File.open(LOG_FILE, 'w+') STDOUT.reopen(log_file) STDOUT.sync = true STDERR.reopen(log_file) STDERR.sync = true else print("WARNING: No logging, pid is #{Process.pid}\n") def print(s) end end Signal.trap('HUP'){exit} Signal.trap('TERM'){exit} END{if log_file then log_file.close end} ObjectSpace.each_object(IO){|io| io.close unless io.closed? || io == log_file || io == STDOUT || io == STDERR } Process.setsid print("PID: #{Process.pid}\n") print("Start #{AGENT_NAME} at #{Time.now.to_s}\n") ip = nil loop{ ip = update(ip) sleep(UPDATE_PEROID) } else update end