DDNS With GoDaddy

If you have a domain with GoDaddy.com and want to host a server at home on a sub-domain of your domain, but you have a dynamic IP like with Comcast Residential, and no dynamic DNS service to take of it. Well, this script, when used with chron and run from your server, can automatically, periodically query your public IP address, and when a change has been detected it will update your GoDaddy DNS records to map the new IP to your subdomain.

Here is how I did it on Ubuntu Linux:

In terminal:

sudo apt-get install python python-pip
sudo pip install pif
sudo pip install pygodaddy

From here, I continued the idea represented here: GoDaddy DynDNS for the Poor

The original script accesses GoDaddy every iteration, that’s no good. I made mine store the current_ip in a file, and only when the public_ip is different does the script access GoDaddy to update the A record. So my situation is a bit unique. I want to update only one subdomain with the DynDNS-like service. So my script goes like this:

godaddy.py

#!/usr/bin/env python
#import sys
import os
import logging
import pif
import pygodaddy

# GLOBALS
GODADDY_USERNAME="USERNAME"
GODADDY_PASSWORD="PASSWORD"
# The files will be created automatically
LOGFILE='/SOME/PLACE/godaddy/godaddy.log'
IPFILE='/SOME/PLACE/godaddy/current_ip'

#
# check locally if IP has changed
#
def check_ip_file(public_ip):
  if os.path.exists(IPFILE):
    # Open a file
    fo = open(IPFILE, "r")
    old_ip = fo.read(50)
    fo.close()
    #print "Read String is : ", str
    # Close opend file
    if old_ip == public_ip:
      print "ip is the same.. not doing anything"
      return 1
  # return if no file exists, or the IP is new
  return

def write_ip_file():
  if not os.path.exists(IPFILE):
    fo = open(IPFILE, "w")
    fo.write(public_ip)
    fo.close()
  else:
    fo = open(IPFILE, "r+")
    fo.seek(0)
    fo.write(public_ip)
    fo.truncate()
    fo.close()
  return

def update_dns(public_ip):
  client = pygodaddy.GoDaddyClient()
  client.login(GODADDY_USERNAME, GODADDY_PASSWORD)

  for domain in client.find_domains():
    for dns_record in client.find_dns_records(domain):
      logging.debug("Domain '{0}' DNS records: {1}".format(domain, client.find_dns_records(domain)))
      # only update the bluewolf subdomain
      if dns_record.hostname == 'bluewolf':
        if public_ip != dns_record.value:
            if client.update_dns_record(dns_record.hostname+"."+domain, public_ip):
              logging.info("Host '{0}' public IP set to '{1}'".format(dns_record.hostname, public_ip))
              # update our local copy of IP
              write_ip_file()
              break
            else:
              logging.info("Failed to update Host '{0}' IP to '{1}'".format(dns_record.hostname, public_ip))
        else:
            logging.info("Nothing was changed")
            # We are 90% only here because there is no current_ip file. So, we write it now.
            write_ip_file()
      else:
        logging.info("Not Bluewolf: '{0}', skipping".format(dns_record.hostname))
  return

def constrain_logfile():
  # Don't allow the log file to become greater than 10MB
  if os.path.exists(LOGFILE):
    statinfo = os.stat(LOGFILE)
    if statinfo.st_size >= 10485760:
      print "removing log file"
      os.remove(LOGFILE)
  return

def setup_logfile():
  constrain_logfile()
  logging.basicConfig(filename=LOGFILE, format='%(asctime)s %(message)s', level=logging.INFO)
  return

### BEGIN MAIN PROCEDURE
setup_logfile()
public_ip = pif.get_public_ip()

if check_ip_file(public_ip) != 1:
  update_dns(public_ip)

Running Regularly with Cron

I have mine running for a local user at 5 minute intervals. The user does not have to be logged in for the job to occur.
to edit your “crontab,” just do this at terminal

crontab -e

Then you get an editor window, probably with a lot of comments, just throw this at the bottom:

*/5 * * * * /PATH/TO/GoDaddy/godaddy.py

Here is a tutorial for running at other intervals (like once every hour):

Notes

I am still an IPv4 guy, so I’m not sure how this would work for an IPv6 implementation. Perhaps someone could comment on this.

Reference

PyGoddady Manual: https://pygodaddy.readthedocs.org/en/latest/

Tagged with: , , , ,
Posted in ddns, godaddy, python
43 comments on “DDNS With GoDaddy
  1. curious george says:

    Do you have it for Windows?

    • michaelbazzinott001 says:

      Python is multi-platform, figure out how to install Python on Windows and then you may configure and run this script.

  2. Richard says:

    Hi Bazza

    Found your script and was very excited. :-) I am getting an error and being a Linux Admin with very little knowledge on Python I was hoping you could enlighten me? :-( Debian reports crontab error as below. Rechecked the required apt packages installed ok. My script is included below that if you have time to help it would be gladly appreciated.

    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/pygodaddy.py”, line 6, in
    import pygodaddy
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/pygodaddy.py”, line 88, in
    update_dns(public_ip)
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/pygodaddy.py”, line 46, in update_dns
    client = pygodaddy.GoDaddyClient()
    AttributeError: ‘module’ object has no attribute ‘GoDaddyClient’

    #!/usr/bin/env python
    #import sys
    import os
    import logging
    import pif
    import pygodaddy

    # GLOBALS
    GODADDY_USERNAME=”xx”
    GODADDY_PASSWORD=”yy”
    # The files will be created automatically
    LOGFILE=’/usr/local/lib/python2.7/dist-packages/pygodaddy/godaddy.log’
    IPFILE=’/usr/local/lib/python2.7/dist-packages/pygodaddy/current_ip’

    #
    # check locally if IP has changed
    #
    def check_ip_file(public_ip):
    if os.path.exists(IPFILE):
    # Open a file
    fo = open(IPFILE, “r”)
    old_ip = fo.read(50)
    fo.close()
    #print “Read String is : “, str
    # Close opend file
    if old_ip == public_ip:
    print “ip is the same.. not doing anything”
    return 1
    # return if no file exists, or the IP is new
    return

    def write_ip_file():
    if not os.path.exists(IPFILE):
    fo = open(IPFILE, “w”)
    fo.write(public_ip)
    fo.close()
    else:
    fo = open(IPFILE, “r+”)
    fo.seek(0)
    fo.write(public_ip)
    fo.truncate()
    fo.close()
    return

    def update_dns(public_ip):
    client = pygodaddy.GoDaddyClient()
    client.login(GODADDY_USERNAME, GODADDY_PASSWORD)

    for domain in client.find_domains():
    for dns_record in client.find_dns_records(domain):
    logging.debug(“Domain ‘{0}’ DNS records: {1}”.format(domain, client.find_dns_records(domain)))
    # only update the bluewolf subdomain
    if dns_record.hostname == ‘zionprivatesavingsandtrust’:
    if public_ip != dns_record.value:
    if client.update_dns_record(dns_record.hostname+”.”+domain, public_ip):
    logging.info(“Host ‘{0}’ public IP set to ‘{1}'”.format(dns_record.hostname, public_ip))
    # update our local copy of IP
    write_ip_file()
    else:
    logging.info(“Failed to update Host ‘{0}’ IP to ‘{1}'”.format(dns_record.hostname, public_ip))
    else:
    logging.info(“Nothing was changed”)
    # We are 90% only here because there is no current_ip file. So, we write it now.
    write_ip_file()
    else:
    logging.info(“Not zionprivatesavingsandtrust: ‘{0}’, skipping”.format(dns_record.hostname))
    return

    def constrain_logfile():
    # Don’t allow the log file to become greater than 10MB
    if os.path.exists(LOGFILE):
    statinfo = os.stat(LOGFILE)
    if statinfo.st_size >= 10485760:
    print “removing log file”
    os.remove(LOGFILE)
    return

    def setup_logfile():
    constrain_logfile()
    logging.basicConfig(filename=LOGFILE, format=’%(asctime)s %(message)s’, level=logging.INFO)
    return

    ### BEGIN MAIN PROCEDURE
    setup_logfile()
    public_ip = pif.get_public_ip()

    if check_ip_file(public_ip) != 1:
    update_dns(public_ip)

  3. ddreamnop says:

    Help me please ?

    I want to update domain name
    but i can’t change this script

    What should I do?

    Thank you.

    • michaelbazzinott001 says:

      You did not provide enough information. I can’t help anyways, I’m doing other things

      • dreamnop says:

        I have two domain name

        example
        a. domain = test1.com
        subdomain = my.test1.com
        b. domain = test2.com
        subdomain = my2.test2.com

        I want to update test1.com not my.test1.com on this script

        Thank you .

  4. Richard says:

    Thank you for your reply.

    Ok I renamed the script to as suggested to “dnsdaddy.py” and I am now getting the error below?

    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/dnsdaddy.py”, line 6, in
    import pygodaddy
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/pygodaddy.py”, line 88, in
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/pygodaddy.py”, line 46, in update_dns
    AttributeError: ‘module’ object has no attribute ‘GoDaddyClient’

  5. Richard says:

    That did it!

    Thank you so much especially because you are so busy. I really appreciate you taking the time to help me out.

    As you advised I renamed the pygodaddy.py script and deleted pygodaddy.py & pygodaddy.pyc

    If I may impose a little more on your time are we also able to add the main domain name to be changed?

    Please only reply if you have a moment as I am sure you have more pressing and technically challenging issues than my newbie questions.

    Thank you very much again for your your help.
    Richard…

    • michaelbazzinott001 says:

      lol wow. I really appreciate your flattering response ^___^
      Main domain name?? Hmmm…. let me see, even I could use this too ^__^

      OK after the following line:
      for domain in client.find_domains():

      add:
      if domain == ‘DOMAIN_NAME’:

      and indent everything afterwards until and not including ‘return’

      IIRC Python is critical about the tabs, you will need to indent everything properly, good luck! and let me know when you get it working!! Tell me about it and I will update the blog post :)

  6. Richard says:

    Michael, Thank you for the instruction. It made it easy for me to add to your script and make the required changes to the indents. I went throught it one line at a time until the indent errors stoped. Wow I did not relise how differant python is. Hard!

    I still must not have the indents right as the script has stoped working but I am not getting errors any more so I dont know how to proceed. Log below. When it was working it was saying “Not zionprivatesavingsandtrust: ‘@’, skipping” that was correct as that name was not in the sub domain.

    If you have a moment can you look at my newbie efforts and point me in the right direction. If your too busy it can really wait so please dont put yourself out. I feel bad enough asking you to have a look at this again.

    Have a great weekend!

    2015-02-20 20:30:01,167 Starting new HTTP connection (1): v4.ident.me
    2015-02-20 20:30:02,169 Starting new HTTPS connection (1): dns.godaddy.com
    2015-02-20 20:30:03,799 Starting new HTTPS connection (1): idp.godaddy.com

    for domain in client.find_domains():
    if domain == ‘zionprivatesavingsandtrust’:
    for dns_record in client.find_dns_records(domain):
    logging.debug(“Domain ‘{0}’ DNS records: {1}”.format(domain, client.find_dns_records(domain)))
    # only update the bluewolf subdomain
    if dns_record.hostname == ‘zionprivatesavingsandtrust’:
    if public_ip != dns_record.value:
    if client.update_dns_record(dns_record.hostname+”.”+domain, public_ip):
    logging.info(“Host ‘{0}’ public IP set to ‘{1}'”.format(dns_record.hostname, public_ip))
    # update our local copy of IP
    write_ip_file()
    else:
    logging.info(“Failed to update Host ‘{0}’ IP to ‘{1}'”.format(dns_record.hostname, public_ip))
    else:
    logging.info(“Nothing was changed”)
    # We are 90% only here because there is no current_ip file. So, we write it now.
    write_ip_file()
    else:
    logging.info(“Not zionprivatesavingsandtrust: ‘{0}’, skipping”.format(dns_record.hostname))
    return

  7. Rich says:

    This was working for me, but recently stopped. I am getting the following error in the log file:

    Failed to update Host ‘ftp’ IP to ‘99.999.999.999’

    Not terribly helpful. Did GoDaddy change something and break this fantastic tool?

  8. humberto says:

    hi, I have and error /usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
    InsecurePlatformWarning
    any help about this please?

  9. Richard says:

    Hi Michael

    Would you please be able to remove the Login & Password from the scrip I submitted above?

    Thank you!

  10. dean says:

    Has anyone try to login with the code? I trace the code a bit and tried step-by-step to see what went wrong.
    Then I came to the login part where I input my GoDaddy username(a series of numbers) and password.

    >>> client = pygodaddy.GoDaddyClient()
    >>> a = client.login(‘somenumber’,’password’)
    >>> a
    false

    I checked my username and password many times. But it always return false.

    Is this “pygodaddy” library still working?

    Thanks for any kind guidance,

    Dean

    • michaelbazzinott001 says:

      Works fine for me. I just tested it right now. The script works, and I also tried the following

      >>> import pygodaddy
      >>> client = pygodaddy.GoDaddyClient()
      >>> a = client.login('sdfdf','sdfsdf')
      >>> a
      True
      
      • Jon says:

        is this still working for you as of tday, just tried it myself but no luck either, login routine broken error gets spit out.

      • Illia says:

        Not working, actually.

        ERROR:pygodaddy.client:Login routine broken, godaddy may have updated their login mechanism
        Traceback (most recent call last):
        File “/usr/local/lib/python2.7/dist-packages/pygodaddy/client.py”, line 101, in login
        viewstate = re.compile(r’id=”__VIEWSTATE” value=”([^”]+)”‘).search(r.text).group(1)
        AttributeError: ‘NoneType’ object has no attribute ‘group’

        pi@raspberrypi ~ $ pip show pygodaddy

        Name: pygodaddy
        Version: 0.2.2
        Location: /usr/local/lib/python2.7/dist-packages
        Requires: requests, tldextract

  11. greg says:

    Hi,
    it seems that godaddy changed something on their side breaking the script (see below)
    do you if it could be fixed ?
    thanks

    2015-08-24 14:33:04,456 Login routine broken, godaddy may have updated their login mechanism
    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/client.py”, line 101, in login
    viewstate = re.compile(r’id=”__VIEWSTATE” value=”([^”]+)”‘).search(r.text).group(1)
    AttributeError: ‘NoneType’ object has no attribute ‘group’
    2015-08-24 14:33:04,456 ERROR: Failed to log in to godaddy.com with username:

  12. gregory says:

    Hello, is the script still working ?
    Thanks

  13. Sam says:

    I’m getting the following errors:

    /usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
    InsecurePlatformWarning
    /usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
    InsecurePlatformWarning
    /usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:90: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
    InsecurePlatformWarning

    My log file reads:
    2015-08-30 07:35:08,936 Starting new HTTP connection (1): v4.ident.me
    2015-08-30 07:35:09,631 Starting new HTTPS connection (1): dns.godaddy.com
    2015-08-30 07:35:10,465 Starting new HTTPS connection (1): idp.godaddy.com
    2015-08-30 07:35:11,072 Starting new HTTPS connection (1): sso.godaddy.com
    2015-08-30 07:35:11,967 Login routine broken, godaddy may have updated their login mechanism
    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/client.py”, line 101, in login
    viewstate = re.compile(r’id=”__VIEWSTATE” value=”([^”]+)”‘).search(r.text).group(1)
    AttributeError: ‘NoneType’ object has no attribute ‘group’

    I suspect it may be a problem with Python2.7 vs Python3? The only thing I changed for your script was the username/password and BlueWolf.

    Also, I own two domains, one for .com and one for .net. I’d like to only change the .com domain and I have no subdomain concerns.

    Any help would be much appreciated.

  14. denys says:

    hi can i get some assistance with running the script.

    ive got it all working now except the login part, has go daddy updated their logins ?

    trying the above script returns false
    line 101 in login
    viewstate = recompile(r’id=_viewstate” …etc

    AttributeError:’NoneType’ object has no attribute ‘group’

  15. jvblackxj says:

    Hello, With godaddy updating their site with single sign on, I found that pygodaddy didn’t work. I spent some time last night working some things out, and it isn’t pretty, but it works for me now. I am not sure if you see my email address, but I would be happy to share with you what I did to the client.py script.

    Jason V

  16. John Lindley says:

    Thanks for this script – It is great! Couple of things if you’re interested.

    1) For python3 I had to update the __init__.py file from https://github.com/BryceGough/pygodaddy/tree/master/pygodaddy
    2) The same site also had an updated client.py file that helped me get past:

    Login routine broken, godaddy may have updated their login mechanism
    Traceback (most recent call last):
    File “/usr/local/lib/python3.4/dist-packages/pygodaddy/client.py”, line 101, in login
    viewstate = re.compile(r’id=”__VIEWSTATE” value=”([^”]+)”‘).search(r.text).group(1)

    • John Lindley says:

      Sorry for the SPAM, but I forgot one other small hurdle. I updated line 53 to: if dns_record.hostname == ‘@’:

  17. Serge says:

    Is it possible that it does not work anymore?
    Here is what my log shows:

    2015-12-13 19:00:57,783 Starting new HTTP connection (1): v4.ident.me
    2015-12-13 19:00:58,191 Starting new HTTPS connection (1): dns.godaddy.com
    2015-12-13 19:00:59,411 Starting new HTTPS connection (1): idp.godaddy.com
    2015-12-13 19:01:00,761 Starting new HTTPS connection (1): sso.godaddy.com
    2015-12-13 19:01:01,972 Login routine broken, godaddy may have updated their login mechanism
    Traceback (most recent call last):
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/client.py”, line 101, in login
    viewstate = re.compile(r’id=”__VIEWSTATE” value=”([^”]+)”‘).search(r.text).group(1)
    AttributeError: ‘NoneType’ object has no attribute ‘group’

    And thanks for that grat work!

  18. eric gandt says:

    First as of 12/2015 you need a different location to fix the login issue:

    pip install -U git+git://github.com/claneys/pygodaddy#egg=pygodaddy

    Second the following script is updated to handle multiple domains and hosts additional checks and finally not to access godaddy unless the dns needs an update.

    #!/usr/bin/env python

    import logging
    import pif
    import pygodaddy
    import socket

    # Original Source:
    # https://saschpe.wordpress.com/2013/11/12/godaddy-dyndns-for-the-poor/
    # https://github.com/observerss/pygodaddy
    #
    # Modified by Jeremy Sears (https://stackoverflow.com/users/1240482/jsears)
    # Modified by Eric Gandt to support mutiple names per domain and multiple domains plus short circute when records match

    # defined all variables here
    U=”@USERNAME@”
    P=”@PASSWORD@”
    D=”@DOMAIN@”
    H=”@HOSTNAME@”
    L=”@LOG LOCATION@”

    logging.basicConfig(filename=L, format=’%(asctime)s %(message)s’, level=logging.INFO)

    # the “requests” library logs noisily, so turn that off
    logging.getLogger(“requests”).setLevel(logging.DEBUG)

    logging.debug(“DEBUG: Running godaddy_ddns.py”);

    client = pygodaddy.GoDaddyClient()
    success = client.login(U,P)
    if success:
    logging.info(“INFO: Successfully logged in.”)
    else:
    logging.error(“ERROR: Failed to log in to godaddy.com with username: ‘{0}’.”.format(U))

    public_ip = pif.get_public_ip()
    logging.debug(“DEBUG: Current Public IP ‘{0}’.”.format(public_ip))

    dns = socket.gethostbyname(H+”.”+D)
    logging.debug(“DEBUG: current DNS record for {0}.{1} is {2}.”.format(H, D, dns))
    if public_ip != dns:
    # if the DNS and public IP differ then we may want to update the dns records
    for domain in client.find_domains():
    # since we may have mutliple domain only retrun data for one of them
    if domain == D:
    # get the domain records
    logging.debug(“DEBUG: Looking up DNS Records for {0}.”.format(domain))
    dns_records = client.find_dns_records(domain)

    # display what we actually got it is a list of 1 or more entries
    logging.debug(“DEBUG: Domain ‘{0}’ DNS records: {1}”.format(domain, dns_records))

    if len(dns_records) == 0:
    logging.debug(“DEBUG: No existing DNS records found for domain {0}.”.format(domain))
    else:
    # we have DNS records, but we need to look at which record we actually want
    for dns_record in dns_records:
    logging.debug(“DEBUG: Found existing record in DNS for {0} is ‘{1}’.”.format(dns_record.hostname, dns_record.value))

    # compare the hostname to what we want to update
    if dns_record.hostname == H:
    logging.debug(“DEBUG: Found desired record for {0} with an address of {1}.”.format(H, dns_record.value))

    # we do not need this check as tha was the first one executed, but to be safe do it again
    if dns_record.value == public_ip:
    # names match nothing needs to be done
    logging.info(“INFO: Public IP A record DNS record for host ‘{0}’ is up to date, and does not need to be updated.”.format(H))
    else:
    # update is needed for the address
    logging.info(“INFO: Updating A record for {0} from address {1} to {2}.”.format(dns_record.hostname, dns_record.value, public_ip))
    # finally call the function to make teh update to the DNS record, only update and include host and domain as provided by user
    success = client.update_dns_record(H+”.”+D, public_ip, ‘A’, False)
    if success:
    logging.info(“INFO: Domain ‘{0}’: Successfully set public IP to ‘{1}’.”.format(domain, public_ip))
    else:
    logging.error(“ERROR: Domain ‘{0}’: Unable to update public IP to ‘{1}’.”.format(domain, public_ip))
    # skip any remaining hosts
    break
    else:
    logging.debug(“DEBUG: Skipping unwanted domain entry {0}.”.format(dns_record.hostname))
    # skip any remaining domains
    break
    else:
    logging.debug(“DEBUG: Skipping unwanted Domain {0}.”.format(domain))
    else:
    # short circuted exit as there was nothing we needed to do, and we did not have to quey godaddy
    logging.info(“INFO: current DNS record for {0}.{1} is {2} which matches the discovered address of {3}, nothing to do.”.format(H, D, dns, public_ip))

  19. David says:

    Do you know if GoDaddy has since made api changes?
    I get the following when typing your last reply

    >>> import pygodaddy
    >>> client = pygodaddy.GoDaddyClient()
    >>> a = client.login(‘uname*******’, ‘passwd********’)
    No handlers could be found for logger “pygodaddy.client”

    >>> client

    >>>

    Thank you for your time,
    Any thoughts?

  20. jvblackxj says:

    Here is my updated script. I can guarantee it works, but I did just run it without any problems. I don’t use it anymore due to obtaining an ISP which gives a single free static IP.

    ############################################################################
    # Copyright 2013 observerss
    #
    # Licensed under the Apache License, Version 2.0 (the “License”);
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    # http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an “AS IS” BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    ############################################################################
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    from collections import defaultdict, namedtuple

    import requests
    import logging
    import re
    import time
    import tldextract

    __all__ = [‘LoginError’, ‘GoDaddyClient’, ‘GoDaddyAccount’]

    logger = logging.getLogger(__name__)

    DNSRecord = namedtuple(‘DNSRecord’, ‘index, hostname, value, ttl, host_td, points_to, rec_modified’)

    class LoginError(Exception):
    pass

    class GoDaddyAccount(object):
    “”” This is a context manager for GoDaddyClient

    Usage:

    >>> from pygodaddy import GoDaddyAccount
    >>> with GoDaddyAccount(username, password) as client:
    … client.update_dns_record(‘sub1.exmaple.com’, ‘1.2.3.4’)
    … client.update_dns_record(‘sub2.exmaple.com’, ‘1.2.3.5’)
    “””
    def __init__(self, username, password):
    self.username = username
    self.password = password
    self.client = None

    def __enter__(self):
    self.client = GoDaddyClient()
    if not self.client.login(self.username, self.password):
    # set client to `None`, force context manager to fail quickly
    self.client = None
    return self.client

    def __exit__(self, exec_type, exc_value, traceback):
    # suppress exceptions
    return True

    class GoDaddyClient(object):
    “”” GoDaddy Client Library

    Typical Usage:

    >>> from pygodaddy import GoDaddyClient
    >>> client = GoDaddyClient()
    >>> if client.login(username, password):
    … client.update_dns_record(‘sub.example.com’, ‘1.2.3.4’)
    “””
    def __init__(self):
    self.logged_in = False
    self.default_url = ‘https://dns.godaddy.com/default.aspx’
    self.login_post_url = ‘https://sso.godaddy.com/v1/?path=%2Flogin.aspx%3FSPKey%3DGDDNS-M1-001&app=idp’
    #self.default_url = ‘https://sso.godaddy.com/?app=idp&path=%2flogin.aspx%3fSPKey%3dGDDNS-M1-001’
    self.zonefile_url = ‘https://dns.godaddy.com/ZoneFile.aspx?zoneType=0&sa=&zone={domain}’
    self.zonefile_ws_url = ‘https://dns.godaddy.com/ZoneFile_WS.asmx’
    self.session = requests.Session()
    self.sec=””
    logging.captureWarnings(True)

    def is_loggedin(self, html=None):
    “”” Test login according to returned html, then set value to self.logged_in

    :param html: the html content returned by self.session.get/post
    :returns: `True` if there’s welcome message, else `False`
    “””
    if html is None:
    html = self.session.get(self.default_url).text
    self.logged_in = bool(re.compile(r’Welcome: (.*)’).search(html))
    lgi = str(self.logged_in).upper()
    logger.info(‘Is Logged in: ‘ + lgi)
    return self.logged_in

    def login(self, username, password):
    “”” Login to a godaddy account

    :param username: godaddy username
    :param password: godaddy password
    :returns: `True` if login is successful, else `False`
    “””
    logging.info(‘Start Login’)
    #r = self.session.get(self.login_post_url)
    r = self.session.get(self.default_url)
    #try:
    #viewstate = re.compile(r’id=”__VIEWSTATE” value=”([^”]+)”‘).search(r.text).group(1)
    #except:
    # logger.exception(‘Login routine broken, godaddy may have updated their login mechanism’)
    # return False
    #data = {
    # ‘Login$userEntryPanel2$LoginImageButton.x’ : 0,
    # ‘Login$userEntryPanel2$LoginImageButton.y’ : 0,
    # ‘Login$userEntryPanel2$UsernameTextBox’ : username,
    # ‘Login$userEntryPanel2$PasswordTextBox’ : password,
    # ‘__VIEWSTATE’: viewstate,
    #}
    data = {
    ‘name’ : username,
    ‘password’ : password,
    ‘app’ : ‘idp’,
    ‘realm’ : ‘idp’
    }
    logger.info(‘Sending Data to login url: ‘ + r.url)
    logger.info(‘Data: ‘ + str(data))
    r = self.session.post(r.url, data=data)
    logger.info(‘Status Code: ‘ + str(r.status_code))
    #time.sleep(1)
    #r = self.session.get(self.default_url)
    #logging.info(r.text)
    return self.is_loggedin(r.text)

    def find_domains(self):
    “”” return all domains of user “””
    html = self.session.get(self.default_url).text
    return re.compile(r”’GoToZoneEdit\(‘([^’]+)”’).findall(html)

    def find_dns_records(self, domain, record_type=’A’):
    “”” find all dns reocrds of a given domain

    :param domain: a typical domain name, e.g. “example.com”
    :returns: a dict of (hostname -> DNSRecord)
    “””
    html = self.session.get(self.zonefile_url.format(domain=domain)).text

    #Update the security token while we’re at it.
    sec_pattern = ‘nonce=\”([0-9A-Za-z]+)\”‘
    self.sec = re.compile(sec_pattern).findall(html)[0]

    pattern = “Undo{rt}Edit\\(‘tbl{rt}Records_([0-9]+)’, ‘([^’]+)’, ‘([^’]+)’, ‘([^’]+)’, ‘([^’]+)’, ‘([^’]+)’, ‘([^’]+)’\\)”.format(rt=record_type)
    try:
    results = map(DNSRecord._make, re.compile(pattern).findall(html))
    except:
    logger.exception(‘find domains broken, maybe godaddy has changed its web structure’)
    return []
    return results

    def update_dns_record(self, hostname, value, record_type=’A’, new=True):
    “”” Update a dns record

    :param hostname: hostname to update
    :param value: value for hostname to point to, for A record, it’s like ‘XX.XX.XX.XX’
    :param record_type: only ‘A’ is implemented for now
    :param new: if `True`, will create a new record if necessary, default to `True`
    :returns: `True` if update is successful, else `False`
    “””
    if record_type != ‘A’:
    raise NotImplementedError(‘Only A Record Update is supported for now’)

    prefix, domain = self._split_hostname(hostname)

    records = list(self.find_dns_records(domain, record_type))
    for record in records:
    if record.hostname == prefix:
    if record.value != value:
    if not self._edit_record(value=value, index=record.index, record_type=record_type):
    return False
    time.sleep(1) # godaddy seems to reject the save if there isn’t a pause here
    if not self._save_records(domain=domain, index=record.index, record_type=record_type):
    return False
    return True
    logger.info(‘Update was not required.’)
    break
    else:
    # record.hostname == prefix not found
    if new:
    # let’s create a new record
    index = int(records[-1].index)+1 if records else 0

    if not self._add_record(prefix=prefix, value=value, index=index, record_type=record_type):
    return False
    time.sleep(1)# godaddy seems to reject the save if there isn’t a pause here
    if not self._save_records(domain=domain, index=index, record_type=record_type):
    return False
    return True

    return False

    def delete_dns_record(self, hostname, record_type=’A’):
    “”” delete hostname in accounts

    :param hostname: hostname to be deleted
    :param record_type: only ‘A’ is implemented for now
    :returns: `True` if successful, else `False`
    “””
    if record_type != ‘A’:
    raise NotImplementedError(‘Only A Record Update is supported for now’)

    prefix, domain = self._split_hostname(hostname)

    for record in self.find_dns_records(domain, record_type):
    if record.hostname == prefix:
    if not self._delete_record(record.index, record_type=record_type):
    return False
    time.sleep(1)# godaddy seems to reject the save if there isn’t a pause here
    if not self._save_records(domain=domain, index=record.index, record_type=record_type):
    return False
    return True

    return False

    def _split_hostname(self, hostname):
    “”” split hostname into prefix + domain “””
    ext = tldextract.extract(hostname)
    prefix = ext.subdomain
    domain = ext.registered_domain
    if not prefix:
    prefix = ‘@’
    return prefix, domain

    def _delete_record(self, index, record_type=’A’):
    “”” delete old record, return `True` if successful “””
    data = {‘sInput’:”{index}|true”.format(index=index)}
    r = self.session.post(self.zonefile_ws_url + ‘/Flag{rt}RecForDeletion’.format(rt=record_type), data=data)
    if not ‘SUCCESS’ in r.text:
    logger.error(‘Failed to delete record, has the website changed?’)
    return False
    return True

    def _add_record(self, prefix, value, index, record_type=’A’):
    “”” add new record, return `True` if successful “””
    data = {‘sInput’:”.format(index=index, prefix=prefix, value=value)}
    r = self.session.post(self.zonefile_ws_url + ‘/AddNew{rt}Record’.format(rt=record_type), data=data)
    if not ‘SUCCESS’ in r.text:
    logger.error(‘Failed to add record, has the website changed?’)
    return False
    return True

    def _edit_record(self, index, value, record_type=’A’):
    “”” set value of record on `index` to `value`, return `True` if successful “””
    data = {‘sInput’ : ”.format(value=value, index=index, rt=record_type.lower())}
    r = self.session.post(self.zonefile_ws_url + ‘/EditRecordField’, data=data)
    if not ‘SUCCESS’ in r.text:
    logger.error(‘Failed to edit record, has the website changed?’)
    return False
    return True

    def _save_records(self, domain, index, record_type=’A’):
    “”” save edit of `index` of `domain` “””
    string = (”





    ”)

    string=string.format(domain=domain, index=index,nonce=self.sec)
    data = {‘sInput’ : string}
    r = self.session.post(self.zonefile_ws_url + ‘/SaveRecords’, data=data)
    if not ‘SUCCESS’ in r.text:
    logger.error(‘Failed to save records, has the website changed?’)
    return False
    return True

    • jvblackxj says:

      The file above is was located here (raspbian): ./usr/local/lib/python2.7/dist-packages/pygodaddy/client.py

  21. humberto says:

    any help ? 3 days I can not login anymore
    Name: pygodaddy
    Version: 0.2.2
    Summary: 3rd Party Client Library for Manipulating Go Daddy DNS Records.
    Home-page: https://github.com/observerss/pygodaddy
    Error :

    Logging into GoDaddy…
    Traceback (most recent call last):
    File “new_3_.py”, line 30, in
    if client.login(username, password):
    File “/usr/local/lib/python2.7/dist-packages/pygodaddy/client.py”, line 99, in login
    r = self.session.get(self.default_url)
    File “/usr/local/lib/python2.7/dist-packages/requests/sessions.py”, line 487, in get
    return self.request(‘GET’, url, **kwargs)
    File “/usr/local/lib/python2.7/dist-packages/requests/sessions.py”, line 475, in request
    resp = self.send(prep, **send_kwargs)
    File “/usr/local/lib/python2.7/dist-packages/requests/sessions.py”, line 585, in send
    r = adapter.send(request, **kwargs)
    File “/usr/local/lib/python2.7/dist-packages/requests/adapters.py”, line 467, in send
    raise ConnectionError(e, request=request)
    requests.exceptions.ConnectionError: HTTPSConnectionPool(host=’dns.godaddy.com’, port=443): Max retries exceeded with url: /default.aspx (Caused by NewConnectionError(‘: Failed to establish a new connection: [Errno -2] Name or service not known’,))

  22. jjordan0 says:

    It looks like there’s a pull request for a fix. I have not tried it yet though.
    https://github.com/observerss/pygodaddy/pull/18/files?diff=split

  23. jjordan0 says:

    Hi humberto et al

    I just upgraded to godaddypy (alternative to pygodaddy) and got the DNS update working.
    See the following page for help: https://github.com/observerss/pygodaddy/issues/22

    Good luck

  24. mr` says:

    Traceback (most recent call last):
    File “/usr/local/lib/python3.2/dist-packages/requests/packages/urllib3/connectionpool.py”, line 544, in urlopen
    body=body, headers=headers)
    File “/usr/local/lib/python3.2/dist-packages/requests/packages/urllib3/connectionpool.py”, line 341, in _make_request
    self._validate_conn(conn)
    File “/usr/local/lib/python3.2/dist-packages/requests/packages/urllib3/connectionpool.py”, line 761, in _validate_conn
    conn.connect()
    File “/usr/local/lib/python3.2/dist-packages/requests/packages/urllib3/connection.py”, line 238, in connect
    ssl_version=resolved_ssl_version)
    File “/usr/local/lib/python3.2/dist-packages/requests/packages/urllib3/util/ssl_.py”, line 279, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
    File “/usr/lib/python3.2/ssl.py”, line 189, in wrap_socket
    _context=self)
    File “/usr/lib/python3.2/ssl.py”, line 276, in __init__
    raise x
    File “/usr/lib/python3.2/ssl.py”, line 272, in __init__
    self.do_handshake()
    File “/usr/lib/python3.2/ssl.py”, line 451, in do_handshake
    self._sslobj.do_handshake()
    ssl.SSLError: [Errno 1] _ssl.c:392: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

    During handling of the above exception, another exception occurred:

    Seems to have broke several months ago. It is related to certificate validation. Any fixes?

    Traceback (most recent call last):
    File “/usr/local/lib/python3.2/dist-packages/requests/adapters.py”, line 370, in send
    timeout=timeout
    File “/usr/local/lib/python3.2/dist-packages/requests/packages/urllib3/connectionpool.py”, line 574, in urlopen
    raise SSLError(e)
    requests.packages.urllib3.exceptions.SSLError: [Errno 1] _ssl.c:392: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

  25. Gino says:

    IP just changed on 6/21 and discovered my script isn’t working anymore either. It last worked on 5/10 when my IP changed last. I’m getting a different error though than humberto is above. Do I need to use some sort of SSL certificate with this now?

    Traceback (most recent call last):
    File “C:\GoDaddy.DynDNS.Updater\godaddy.py”, line 94, in
    update_dns(public_ip)
    File “C:\GoDaddy.DynDNS.Updater\godaddy.py”, line 51, in update_dns
    client.login(GODADDY_USERNAME, GODADDY_PASSWORD)
    File “C:\Python27\lib\site-packages\pygodaddy\client.py”, line 100, in login
    r = self.session.get(self.default_url)
    File “C:\Python27\lib\site-packages\requests\sessions.py”, line 480, in get
    return self.request(‘GET’, url, **kwargs)
    File “C:\Python27\lib\site-packages\requests\sessions.py”, line 468, in request
    resp = self.send(prep, **send_kwargs)
    File “C:\Python27\lib\site-packages\requests\sessions.py”, line 576, in send
    r = adapter.send(request, **kwargs)
    File “C:\Python27\lib\site-packages\requests\adapters.py”, line 447, in send
    raise SSLError(e, request=request)
    requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)

  26. Geremia says:

    Determing IPv6 can be done with:

    import netifaces
    public_ip = netifaces.ifaddresses(‘eth1’)[10][0][‘addr’]

    (You might need to change the indices and eth1 for your specific setup.)

  27. Jason Frazier says:

    None of these scripts are working, so I updated to use godaddypy module and GoDaddy Developer API key/secret, rather than the broken user/password login used by the older pygodaddy.

    Here is my new script. I’ve also put it on Pastebin to retain proper formatting (these blog comments break the formatting and quotes).
    http://pastebin.com/M9X462qm

    #!/usr/bin/env python
    # install these python modules (pip install pif godaddypy)
    import logging
    import pif
    from godaddypy import Client, Account
    import socket

    # Original Source:
    # https://saschpe.wordpress.com/2013/11/12/godaddy-dyndns-for-the-poor/
    # http://blogs.umb.edu/michaelbazzinott001/2014/09/14/ddns-with-godaddy/
    # https://github.com/eXamadeus/godaddypy
    #
    # Modified by Jeremy Sears (https://stackoverflow.com/users/1240482/jsears)
    # Modified by Eric Gandt to support multiple names per domain and multiple domains plus short circuit when records match
    # Modified by Jason Frazier to use newest godaddypi and API key authentication instead of non-working pygodaddy U/P logins
    #
    # ensure you have already created yoursubdomainprefix.yourdomain.example as a new ‘A’ record in your GoDaddy DNS Management portal
    # ensure the time-to-live is reasonably short for DDNS-like operation, 600 seconds is enough without causing issues with GoDaddy
    # save this script in a location on your host, mark as executable and use crontab to execute every five minutes
    # */5 * * * * /PATH/TO/godaddy.py
    #
    # You must obtain a Production (not Test) GoDaddy API Key and secret for your account
    # https://developer.godaddy.com/keys
    #
    # you must define these one-letter-named variables and remove the @ signs
    K=”@YOUR-GODADDY-API-KEY@”
    S=”@YOUR-GODADDY-API-SECRET@”
    H=”@yoursubdomainprefix@”
    D=”@yourdomain.example@”
    L=”@/PATH/TO/godaddy.log@”

    # nothing below here should need modification
    logging.basicConfig(filename=L, format=’%(asctime)s %(message)s’, level=logging.INFO)
    # the “requests” library logs noisily, so turn that off
    logging.getLogger(“requests”).setLevel(logging.DEBUG)
    logging.debug(“DEBUG: Running godaddy_ddns.py”);
    my_acct = Account(api_key=K, api_secret=S)
    client = Client(my_acct)

    public_ip = pif.get_public_ip()
    logging.debug(“DEBUG: Current Public IP ‘{0}’.”.format(public_ip))
    dns = socket.gethostbyname(H+”.”+D)
    logging.debug(“DEBUG: current DNS record for {0}.{1} is {2}.”.format(H, D, dns))
    if public_ip != dns:
    # if the DNS and public IP differ then we may want to update the dns records
    for domain in client.get_domains():
    # since we may have multiple domain only return data for one of them
    if domain == D:
    # get the domain records
    logging.debug(“DEBUG: Looking up DNS Records for {0}.”.format(domain))
    dns_records = client.get_records(domain, record_type=’A’)
    # display what we actually got it is a list of 1 or more entries
    logging.debug(“DEBUG: Domain ‘{0}’ DNS records: {1}”.format(domain, dns_records))
    if len(dns_records) == 0:
    logging.debug(“DEBUG: No existing DNS records found for domain {0}.”.format(domain))
    else:
    # we have DNS records, but we need to look at which record we actually want
    for dns_record in dns_records:
    logging.debug(“DEBUG: Found existing record in DNS for {0} is ‘{1}’.”.format(dns_record[‘name’], dns_record[‘data’]))
    # compare the hostname to what we want to update
    if dns_record[‘name’] == H:
    logging.debug(“DEBUG: Found desired record for {0} with an address of {1}.”.format(H, dns_record[‘data’]))
    # we do not need this check as tha was the first one executed, but to be safe do it again
    if dns_record[‘data’] == public_ip:
    # names match nothing needs to be done
    logging.info(“INFO: Public IP A record DNS record for host ‘{0}’ is up to date, and does not need to be updated.”.format(H))
    else:
    # update is needed for the address
    logging.info(“INFO: Updating A record for {0} ({1}) from address {2} to {3}.”.format(dns_record[‘name’], H+”.”+D, dns_record[‘data’], public_ip))
    # finally call the function to make teh update to the DNS record, only update and include host and domain as provided by user
    success = client.update_record_ip(public_ip, D, H, ‘A’)
    if success:
    logging.info(“INFO: Domain ‘{0}’: Successfully set public IP to ‘{1}’.”.format(domain, public_ip))
    else:
    logging.error(“ERROR: Domain ‘{0}’: Unable to update public IP to ‘{1}’.”.format(domain, public_ip))
    # skip any remaining hosts
    break
    else:
    logging.debug(“DEBUG: Skipping unwanted domain entry {0}.”.format(dns_record[‘name’]))
    # skip any remaining domains
    break
    else:
    logging.debug(“DEBUG: Skipping unwanted Domain {0}.”.format(domain))
    else:
    # short circuited exit as there was nothing we needed to do, and we did not have to query godaddy
    logging.info(“INFO: current DNS record for {0}.{1} is {2} which matches the discovered address of {3}, nothing to do.”.format(H, D, dns, public_ip))

  28. JD says:

    This is a nice script from which to learn website interactivity using Python, but as a means to update a GoDaddy DNS record, it is no longer the best choice since GoDaddy now has an API for that. See https://developer.godaddy.com/doc/endpoint/domains and specifically “PUT
    ​/v1​/domains​/{domain}​/records​/{type}​/{name}”.

Leave a Reply to Rich Cancel reply

Your email address will not be published. Required fields are marked *

*

Skip to toolbar