Command line prototype to update a Dynamic DNS Service that accepts the GnuDIP protocol (like yi.org):
pydyp.py [-u uname] -w password [-s dipserver] [-p dipserverport] [-d domain]
It shows the power of Twisted framework.
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 132 133 134 135 | # 2004 (c) Nicola Paolucci <nick ath durdn doth net>
# OpenSource, Python License
"""
Command line prototype to update a Dynamic DNS Service that
accepts the GnuDIP protocol (like yi.org):
pydyp.py -u <uname> -w <password> -s <dipserver> -p <dipserverport> -d <domain>
"""
import md5
import sys
from twisted.internet import protocol,reactor
from twisted.protocols import basic
from twisted.python import usage
__version__ = '0.4'
__author__=('Nicola Paolucci <nick ath durdn doth net>',)
__thanks_to__=[]
__copyright__=''
__history__="""
0.1 First prototype version
0.2 Use of .hexdigest()
0.3 Refactored to separate mechanism and policy
0.4 No need to pass the factory to the protocol
""" # -> "
def hashPassword(password, salt):
p1 = md5.md5(password).hexdigest() + '.' + salt.strip()
hashedpass = md5.md5(p1).hexdigest()
return hashedpass
class DIPProtocol(basic.LineReceiver):
""" Quick implementation of GnuDIP protocol (TCP) as described here:
http://gnudip2.sourceforge.net/gnudip-www/latest/gnudip/html/protocol.html
"""
delimiter = '\n'
def connectionMade(self):
basic.LineReceiver.connectionMade(self)
self.expectingSalt = True
def lineReceived(self, line):
if self.expectingSalt:
self.saltReceived(line)
self.expectingSalt = False
else:
self.responseReceived(line)
def saltReceived(self, salt):
"""Override this."""
def responseReceived(self, response):
"""Override this."""
class DIPUpdater(DIPProtocol):
"""A quick class to update an IP, then disconnect."""
def saltReceived(self, salt):
password = self.factory.getPassword()
username = self.factory.getUsername()
domain = self.factory.getDomain()
msg = '%s:%s:%s:2' % (username, hashPassword(password, salt), domain)
self.sendLine(msg)
def responseReceived(self, response):
code = response.split(':', 1)[0]
if code == '0':
pass # OK
elif code == '1':
print 'Authentication failed'
else:
print 'Unexpected response from server:', repr(response)
self.transport.loseConnection()
class DIPClientFactory(protocol.ClientFactory):
""" Factory used to instantiate DIP protocol instances with
correct username,password and domain.
""" # -> "
protocol = DIPUpdater
def __init__(self,username,password,domain):
self.u = username
self.p = password
self.d = domain
def getUsername(self):
return self.u
def getPassword(self):
return self.p
def getDomain(self):
return self.d
def clientConnectionLost(self, connector, reason):
reactor.stop()
def clientConnectionFailed(self, connector, reason):
print 'Connection failed. Reason:', reason
class Options(usage.Options):
optParameters = [['server', 's','gnudip2.yi.org', 'DIP Server'],
['port', 'p',3495,'DIP Server port'],
['username', 'u','durdn', 'Username'],
['password', 'w',None,'Password'],
['domain', 'd','durdn.yi.org', 'Domain']]
if __name__ == '__main__':
config = Options()
try:
config.parseOptions()
except usage.UsageError, errortext:
print '%s: %s' % (sys.argv[0], errortext)
print '%s: Try --help for usage details.' % (sys.argv[0])
sys.exit(1)
server = config['server']
port = int(config['port'])
password = config['password']
if not password:
print 'Password not entered. Try --help for usage details.'
sys.exit(1)
reactor.connectTCP(server, port,
DIPClientFactory(config['username'],password,
config['domain']))
reactor.run()
|
I wanted to use a Dynamic DNS Service called yi.org, but did not like the option of installing the suggested small client application to update my IP address on my OpenBSD box. So I resorted to write this really quick script. I put into my crontab to keep my domain always up-to-date with my dynamic IP address at home.
This shows how suited is the Twisted framework to develop networked applications.
Simpler MD5.
&darr
True, its more concise, I updated the recipe. True, its more concise, I updated the recipe. Thanks for the note, Nick
Your dataReceived looks potentially buggy to me. First, a nitpick: you don't need to pass the factory explicitly to the Protocol, or override buildProtocol like that. The default buildProtocol implementation will set the .factory attribute for you.
You seem to be assuming that you only receive exactly one message from the server at a time, and never get partial or multiple messages at once, but there's no guarantee of that (although with such small messages it's unlikely).
At a glance, it appears that perhaps twisted.protocols.basic.LineReceiver would be a better base class DIPProtocol, because it would handle these issues for you, and only trigger the lineReceived method with single, complete lines. (It's not clear from the spec you link to that it is a line-delimited protocol, but I'm guessing it is from your implementation).
Also, your "reqnum" instance variable seems to be misleadingly named; what you really want is a variable to keep track of protocol state, e.g. are you expecting a salt, or expecting a response to a previous request?
Finally, you're mixing mechanism and policy in your DIPProtocol class, which makes it difficult to reuse.
Here's how I'd write that class:
(comment continued...)
(...continued from previous comment)
And then make DIPFactory use DIPUpdater instead of DIPProtocol.
A further improvement might be to implement "updateIP" and "removeIP" methods on DIPProtocol, to make subclasses like DIPUpdater even easier to write.
Of course, this level of generalisation might be overkill for the cookbook ;)
It's always surprising how open collaboration can improve things fast. It's always surprising how open collaboration can improve things fast.
My recipe was put together "quickly" as the title suggested.
Indeed your refactoring makes it more elegant and reusable.
I integrated it, tested it again, and updated it.
I had to pass the factory to the Updater anyway otherwise it could not find it.
Thanks,
Nick
Forget my last remark. Of course passing the factory is not needed... I will update the recipe shortly.