#!/usr/bin/env python
#
# ecpZoneUpdate 0.2
# for more information, visit http://www.ecpsoftware.com
# or contact douglas.savitsky@ecpsoftware.com
#
# ecpZoneUpdate (c) 2003 ecpsoftware, douglas savitsky
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# Tested to run under Windows 2000, FreeBSD 4.7
#
# Last updated 01/28/2002
#
# This software borrows somewhat from zoneclient.py
# available from http://zoneclient.sourceforge.net
# which is (c) kal@users.sourceforge.net
# (as of this writing, an unreachable address)
#
# Thanks to Will Gunadi for a D-Link router patch.

import base64
import httplib
import os
import re
import time
import urllib

_uagent      = 'ecpZoneUpdate'
_version     = '0.2'

# this re finds ip numbers, but also can find incorrect ones!
# the more correct re is much longer, however, and this
# should be sufficient here.
_ip_regex    = "(?:\d{1,3}\.){3}\d{1,3}"

# defines specific router things.  This will expand as more routers
# are added.
_router_options = {
    'Netgear RT311' : {'path' : '/mtenSysStatus.html'},
    'D-Link DI-604' : {'path' : '/st_devic.html'}
        }

# USER OPTIONS

# the INTERNAL ip of the router, this defaults to the, well, to the default.
_router_ip   = '192.168.0.1'

# your router's model.  Options are
# 'Netgear RT311' or 'D-Link DI-604'
# to skip the router and get the IP from the web, use 'zoneedit.com' here
_router_model = 'Netgear RT311'

# where to write logs and store the IP. Change this to suit your OS
_path        = '/usr/local/ecpZoneUpdate'
#_path        = 'C:\\ecpZoneUpdate'

# where to store the last reported ip
_ip_file     = 'ip.txt'

# set to '' to turn off logging
_log_file    = 'ecpZU.log'

# router username
# if you are using 'zoneedit.com' as your _router_model
# you can ignore this option
_runame      = 'admin'

# router password
# if you are using 'zoneedit.com' as your _router_model
# you can ignore this option
_rpassword   = '1234'

# zoneedit username
_zuname      = 'username'

# zoneedit password
_zpassword   = '********'

# zones (must be in a list or tuple)
_zones       = ['MyDomain.com']

# IP's you can't have (filtered out from the page returned by the router)
_bad_ip_list = ['0.0.0.0',
                '255.255.255.0',
                '255.255.255.255',
                _router_ip]

def get_current_zip(file):
    # this is longer than necessary in case there is other crap in the file
    try:
        f = open(file, 'r')
    except:
        # the file does not exist yet so send no ip
        return '0.0.0.0'
    ipd = f.read()
    f.close()
    ip = re.findall(_ip_regex, ipd)
    for i in ip:
        if i not in _bad_ip_list:
            return i
            break
    return '0.0.0.0'

def get_ip(uname, pw):
    if _router_model == 'zoneedit.com':
        ip = _get_zedynamic_ip()
    else:
        # we'll try 5 times to get the ip as my router is spotty
        # i've never seen it take more than 3 tries though ;-)
        tries = 5
        while 1:
            ip = _get_router_ip(uname, pw)
            if tries == 0: break
            else:
                if ip == '0.0.0.0': tries -= 1
                else: break
        # if the router has failed to give the IP, try to get
        # it from zoneedit's site
        if ip == '0.0.0.0':
            ip = _get_zedynamic_ip()
    return ip

def _get_router_ip(uname, pw):
    # more or less lifted from zoneclient.py
    h = httplib.HTTP(_router_ip)
    h.putrequest('GET', _router_options[_router_model]['path'])
    h.putheader('USER-AGENT', '%s/%s' % (_uagent, _version))
    a = base64.encodestring("%s:%s" % (uname, pw))
    h.putheader('AUTHORIZATION', 'Basic %s' % a)
    h.endheaders()
    a,b,c = h.getreply()
    f = h.getfile()
    ipd = f.read()
    f.close()
    l = re.findall(_ip_regex, ipd)
    ip = '0.0.0.0'
    for i in l:
        if i not in _bad_ip_list:
            ip = i
    return ip

def _get_zedynamic_ip():
    try:
        f = urllib.urlopen('http://dynamic.zoneedit.com/checkip.html')
        s = f.read()
        l = re.findall(_ip_regex, s)
        if len(l) == 0: l = ['0.0.0.0']
    except:
        l = ['0.0.0.0']
    return l[0] # this page should only produce one IP number

def set_zoneedit(uname, password, zones, ip):
    # XXX -> this should be changed to use https...,
    # XXX -> and probably to use urllib as well.
    h = httplib.HTTP('www.zoneedit.com')
    h.putrequest('GET', '/auth/dynamic.html?host=%s&dnsto=%s' % (','.join(zones), ip))
    h.putheader('HOST', 'www.zoneedit.com')
    h.putheader('USER-AGENT', '%s/%s' % (_uagent, _version))
    a = base64.encodestring('%s:%s' % (uname, password))
    h.putheader('AUTHORIZATION', 'Basic %s' % a)
    h.endheaders()
    errcode, errmsg, headers = h.getreply()
    # is this next step necessary to do?
    try:
        f = h.getfile()
        dta = f.read()
        f.close()
    except: pass
    return errcode

def set_current_zip(file, ip):
    f = open(file, 'w')
    f.write(ip)
    f.close()

def main(times=75):
    file_ip = get_current_zip(os.path.join(_path, _ip_file))
    if _log_file == '':
        import StringIO
        log = StringIO.StringIO()
    else:
        log = open(os.path.join(_path, _log_file), 'a')
    new_ip = get_ip(uname=_runame, pw=_rpassword)
    if new_ip == '0.0.0.0':
        print >>log, '%s :: Router = 0.0.0.0, sleeping for retry' % time.ctime()
        log.flush()
        log.close()
        times -= 1
        if times > 0:
            time.sleep(30)
            main(times)
    elif file_ip == new_ip:
        print >>log, '%s :: No updates made (%s == %s).' % (time.ctime(time.time()), file_ip, new_ip)
    else:
        print >>log, '%s :: Setting IP [%s >> %s]' % (time.ctime(time.time()), file_ip, new_ip)
        try:
            zu = set_zoneedit(_zuname, _zpassword, _zones, new_ip)
            print >>log, '    Zoneedit return -> %s' % zu
            set_current_zip(os.path.join(_path,_ip_file), new_ip)
            print >>log, '    Update succeeded.'
            print 'IP Updated to %s' % new_ip
        except:
            print >>log, '    Failed'
            print 'IP Update failed'
        log.flush()
        log.close()

if __name__ == '__main__':
    main()