update.py
5.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/usr/bin/env python3
import http.client
import datetime
import requests
import json
import argparse
import sys
CURRENT_VERSION = "1.1-imanolbarba"
USER_AGENT_STRING = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"
DEBUG = 0
API_URL="https://domains.google.com/nic/update"
TYPE_INFO = 0
TYPE_WARNING = 1
TYPE_ERROR = 2
TYPE_DEBUG = 3
BASH_YLW_TEXT = '\033[33m'
BASH_RED_TEXT = '\033[31m'
BASH_RESET_TEXT = '\033[0m'
# Message logging function
# TODO: log to file
def msgLog(string,type = TYPE_INFO):
time = str(datetime.datetime.now())
if(type == TYPE_ERROR):
print("[{}]: {}ERROR: {}{}".format(time, BASH_RED_TEXT, string, BASH_RESET_TEXT))
elif(type == TYPE_WARNING):
print("[{}]: {}WARNING: {}{}".format(time, BASH_YLW_TEXT, string, BASH_RESET_TEXT))
elif(type == TYPE_INFO):
print("[{}]: INFO: {}".format(time, string))
elif(type == TYPE_DEBUG):
if(DEBUG):
print("[{}]: DEBUG: {}".format(time, string))
else:
print(string)
def parseResponse(body):
# format: response arg
repFields = body.split(" ")
if(len(repFields) > 2):
msgLog("Response exceeded 2 arguments. No currently implemented responses exceed that, so it must be new.\nResponse was:\n\n{}".format(body), TYPE_WARNING)
repVerb = repFields[0]
repArg = repFields[-1] # If response is only 1 argument (the verb), this will also be the verb, saving length logic ;)
if(repVerb == "good"):
msgLog("Successfully updated to {}!".format(repArg), TYPE_INFO)
return True
if(repVerb == "nochg"):
msgLog("Record was already updated to address {}".format(repArg), TYPE_INFO)
return True
if(repVerb == "nohost"):
msgLog("Hostname doesn't exist, or does not have Dynamic DNS enabled", TYPE_ERROR)
return False
if(repVerb == "badauth"):
msgLog("Invalid credentials for specified host", TYPE_ERROR)
return False
if(repVerb == "notfqdn"):
msgLog("Hostname is not a valid FQDN", TYPE_ERROR)
return False
if(repVerb == "badagent"):
msgLog("Invalid request. Ensure the user agent is set in the request", TYPE_ERROR)
return False
if(repVerb == "abuse"):
msgLog("Dynamic DNS access for the hostname has been blocked due to failure to interpret previous responses correctly", TYPE_ERROR)
return False
if(repVerb == "911"):
msgLog("Google Domains failure. Try again later", TYPE_ERROR)
return False
if(repVerb == "conflict"):
msgLog("Conflicting {} record found. Delete the indicated resource record within DNS settings page and try the update again".format(repArg), TYPE_ERROR)
return False
msgLog("Unknown response:\n\n{}".format(body), TYPE_ERROR)
return False
def updateRecord(username, password, hostname, address, offline):
msgLog("Updating {} to {}".format(hostname, ("current IP address" if address == None else address)),TYPE_INFO)
headers = {'User-Agent': USER_AGENT_STRING, 'Content-Type': 'application/x-www-form-urlencoded'}
updateParams = {"hostname" : hostname, "offline": ("yes" if offline == True else "no")}
if(address != None):
updateParams["myip"] = address
req = requests.Request('POST', API_URL, auth=(username,password), headers=headers, data=updateParams)
prep = req.prepare()
msgLog('Request:\n\n{} {}\n{}\n\n{}'.format(prep.method, prep.url,'\n'.join('{}: {}'.format(k, v) for k, v in prep.headers.items()),prep.body), TYPE_DEBUG)
rep = requests.Session().send(prep)
msgLog('Response:\n\n{} {}\n{}\n\n{}'.format(rep.status_code, http.client.responses[rep.status_code],'\n'.join('{}: {}'.format(k, v) for k, v in rep.headers.items()),rep.content.decode()), TYPE_DEBUG)
if(rep.status_code != 200):
msgLog("API returned non-success code: {}\n".format(rep.status_code), TYPE_ERROR)
return False
return parseResponse(rep.content.decode())
parser = argparse.ArgumentParser(description="Update Google Domain Dynamic DNS records")
parser.add_argument('--verbose', action='store_true', dest="verbose", help="Enable debugging messages")
parser.add_argument('--version', action='version', version=str("GoogleDDNS v" + CURRENT_VERSION))
parser.add_argument("--hostname", action="store", dest="hostname", help="Hostname to be updated", nargs=1)
parser.add_argument("--ip", action="store", dest="ip", help="IPv4/6 address to update the hostname to (Optional for IPv4, Google uses the requestor IP address otherwise)", nargs=1)
parser.add_argument("--username", action="store", dest="username", help="Username for this specific hostname (NOT YOUR GOOGLE ACCOUNT USERNAME)", nargs=1)
parser.add_argument("--password", action="store", dest="password", help="Password for this specific hostname (NOT YOUR GOOGLE ACCOUNT PASSWORD)", nargs=1)
parser.add_argument("--offline", action="store_true", dest="offline", help="Put the hostname offline until further updates")
argv = parser.parse_args()
if(argv.verbose):
DEBUG = 1
if(argv.hostname != None):
if(argv.username != None):
if(argv.password != None):
if(updateRecord(argv.username[0], argv.password[0], argv.hostname[0], (argv.ip[0] if argv.ip != None else None), argv.offline) == False):
exit(1)
else:
msgLog("Password not specified", TYPE_ERROR)
else:
msgLog("Username not specified", TYPE_ERROR)
else:
msgLog("Hostname not specified", TYPE_ERROR)
exit(0)