Skip to content

Commit fb9899b

Browse files
author
root
committed
first commit
0 parents  commit fb9899b

7 files changed

+327
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dyndns.xml

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- copy scriptaggiornaip4 into /bin/ directory
2+
- rename dyndns.xml.example in dyndns.xml and add users there.
3+
- run dnsninuxserver.py

dnsninuxclient.sh

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
# netcat is required to run this script
3+
4+
USERNAME=pippo
5+
PASSWORD=pippo
6+
HOSTNAME=testmachie
7+
ADDRESS=localhost
8+
PORT=8078
9+
10+
connect() {
11+
echo -e "$USERNAME\n$PASSWORD\n$HOSTNAME" | nc $ADDRESS $PORT > /tmp/dnsninuxclient
12+
cat /tmp/dnsninuxclient
13+
}
14+
15+
if connect; then
16+
if grep "KO" /tmp/dnsninuxclient > /dev/null; then
17+
echo "ERROR!"
18+
/bin/false
19+
else
20+
echo "OK!"
21+
/bin/true
22+
fi
23+
else
24+
echo "ERROR!"
25+
/bin/false
26+
fi
27+

dnsninuxserver.py

+271
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
#!/usr/bin/env python
2+
3+
from SocketServer import *
4+
from xml.sax import *
5+
import time
6+
import os
7+
import socket
8+
import sys
9+
from threading import Lock
10+
import logging
11+
12+
#first level domain
13+
DOMAIN='ninux.org'
14+
#the port on which the server will listen
15+
PORT=8078
16+
#configuration file
17+
XMLFILE='/usr/local/dyndnsninux/dyndns.xml'
18+
#nsupdate script - should take 2 parameters: ip and name
19+
NSUPDATESCRIPT='/bin/scriptaggiornaip4'
20+
#after how many seconds allow connection from same IP address
21+
CONNTIMEOUT=40
22+
#logging settings
23+
#logging.basicConfig(level=logging.DEBUG, format="[%(asctime)s %(thread)d] %(levelname)s: %(message)s")
24+
logging.getLogger().setLevel(logging.CRITICAL)
25+
#user class
26+
class User:
27+
name=""
28+
password=""
29+
comment=""
30+
hostnames=[]
31+
def __repr__(self):
32+
return"%s %s %s %s" % (self.name, self.password, self.comment, self.hostnames)
33+
34+
def __str__(self):
35+
return self.__repr__()
36+
37+
38+
39+
#xml parser
40+
class DynDnsParsingHandler(ContentHandler):
41+
isUserElement=False
42+
isNameElement=False
43+
isPasswordElement=False
44+
isCommentElement=False
45+
isHostnameElement=False
46+
47+
def __init__(self,authmanager):
48+
self.authmanager=authmanager
49+
ContentHandler.__init__(self)
50+
51+
def startElement(self,name,attrs):
52+
if name=="user":
53+
self.isUserElement=True
54+
self.newuser=User()
55+
if name=="name":
56+
self.isNameElement=True
57+
elif name=="password":
58+
self.isPasswordElement=True
59+
elif name=="comment":
60+
self.isCommentElement=True
61+
elif name=="hostname":
62+
self.hostname=""
63+
self.isHostnameElement=True
64+
65+
def endElement(self, name):
66+
if name=="name":
67+
self.isNameElement=False
68+
if name=="password":
69+
self.isPasswordElement=False
70+
if name=="comment":
71+
self.isCommentElement=False
72+
if name=="hostname":
73+
self.newuser.hostnames.append(self.hostname)
74+
self.isHostnameElement=False
75+
if name=="user":
76+
self.isUserElement=False
77+
#set the global dictionaries
78+
self.authmanager._addUser(self.newuser)
79+
80+
def characters (self,ch):
81+
if self.isNameElement:
82+
self.newuser.name+=ch
83+
elif self.isPasswordElement:
84+
self.newuser.password+=ch
85+
elif self.isCommentElement:
86+
self.newuser.comment+=ch
87+
elif self.isHostnameElement:
88+
self.hostname+=ch
89+
90+
91+
92+
#authorization manager
93+
class AuthManager:
94+
"The AuthManager script parses the xml file and gives authorization"
95+
users={}
96+
xmlfile=""
97+
def __init__(self,xmlfile):
98+
self.xmlfile=xmlfile
99+
self.__updateEntries()
100+
101+
def __updateEntries(self):
102+
parser=make_parser()
103+
curhandler=DynDnsParsingHandler(self)
104+
parser.setContentHandler(curhandler)
105+
try:
106+
parser.parse(open(self.xmlfile))
107+
except IOError:
108+
logging.critical("%s not found" % self.xmlfile)
109+
sys.exit(2)
110+
logging.debug(self.xmlfile+" parsed")
111+
112+
def _addUser(self,userobject):
113+
assert(userobject.__class__==User)
114+
self.users.update({userobject.name:userobject})
115+
116+
def isAuthorized(self,username,password,hostname):
117+
self.__updateEntries()
118+
try:
119+
loginok=(self.users[username].password==password and hostname in self.users[username].hostnames)
120+
except KeyError:
121+
loginok=False
122+
return loginok
123+
124+
def getComment(self,username):
125+
self.__updateEntries()
126+
try:
127+
return users[username].comment
128+
except KeyError:
129+
return ""
130+
131+
132+
#connection manager: to avoid concurrent connections from same IP address
133+
class ConnManager:
134+
clients={}
135+
def __init__(self):
136+
self.clientslock=Lock()
137+
138+
def isAllowed(self,ipaddress):
139+
self.clientslock.acquire()
140+
allowed=self.__isAllowed(ipaddress)
141+
self.clientslock.release()
142+
if not allowed:
143+
logging.warning("Repeated connection attempt from %s" % ipaddress)
144+
return allowed
145+
146+
def __isAllowed(self,ipaddress):
147+
#unlocked function! Don't use!
148+
try:
149+
ts=self.clients[ipaddress]
150+
except KeyError:
151+
self.clients[ipaddress]=time.time()
152+
return True
153+
else:
154+
if (time.time()-ts) > CONNTIMEOUT:
155+
self.clients[ipaddress]=time.time()
156+
return True
157+
else:
158+
return False
159+
160+
161+
#exceptions
162+
class ConnectionException(Exception):
163+
pass
164+
165+
#Server handler
166+
class LoginHandler (StreamRequestHandler):
167+
user=""
168+
passw=""
169+
host=""
170+
ip=""
171+
loadedrecords=False
172+
def outm(self,positive,message,critical=False):
173+
#send out a positive or negative message
174+
response=""
175+
if positive:
176+
response="OK. "+message
177+
else:
178+
response="KO. "+message
179+
try:
180+
self.wfile.write(response+"\n")
181+
except:
182+
logging.error("Connection error client %s" % (self.ip,))
183+
else:
184+
if critical:
185+
logging.critical(response)
186+
else:
187+
logging.info(response)
188+
189+
def checklogin(self):
190+
if am.isAuthorized(self.user,self.passw,self.host):
191+
self.outm(True,"Access Granted to user %s (%s)." % (self.user,self.ip))
192+
return True
193+
else:
194+
self.outm(False,"Access Denied to %s IP %s." % (self.user,self.ip),critical=True)
195+
return False
196+
197+
def dyndnsdo(self):
198+
#do the dyndns stuff
199+
command = "%s %s %s" % (NSUPDATESCRIPT,self.ip,self.host)
200+
logging.debug("Executing %s" % command)
201+
if os.system(command)==0:
202+
self.outm(True, "DNS records updated: %s <-- %s.%s" % (self.ip,self.host,DOMAIN),critical=True)
203+
else:
204+
self.outm(False, "Error updating DNS records %s != %s.%s" % (self.ip,self.host,DOMAIN),critical=True)
205+
206+
def handle(self):
207+
user=""
208+
passw=""
209+
host=""
210+
211+
#get client IP address
212+
self.ip=self.client_address[0]
213+
214+
if not cm.isAllowed(self.ip):
215+
self.outm(False,self.ip+" already connected or timeout not expired. Try again later")
216+
return
217+
218+
self.outm(True,self.ip+" connected. Please supply your username.")
219+
220+
#stateful protocol
221+
serverstatus=0
222+
while(serverstatus<3):
223+
cmd=self.rfile.readline().rstrip()
224+
if serverstatus==0:
225+
user=cmd
226+
pos=True
227+
msg="User "+user+". Please supply password"
228+
serverstatus=1
229+
elif serverstatus==1:
230+
passw=cmd
231+
pos=True
232+
msg="User "+user+". Password supplied. Please supply hostname"
233+
serverstatus=2
234+
elif serverstatus==2:
235+
host=cmd
236+
pos=True
237+
msg="Hostname "+host+" supplied"
238+
serverstatus=3
239+
self.outm(pos, msg)
240+
241+
#We should have an username, a password and a hostname
242+
self.user=user
243+
self.passw=passw
244+
self.host=host
245+
246+
#check the login and do dyndns stuff
247+
if self.checklogin():
248+
self.dyndnsdo()
249+
250+
251+
class ThreadingTCPServer(ThreadingMixIn,TCPServer): pass
252+
253+
254+
if __name__=="__main__":
255+
256+
am=AuthManager(XMLFILE)
257+
cm=ConnManager()
258+
259+
try:
260+
logging.critical("Binding server to port %s" % PORT)
261+
dyndns=ThreadingTCPServer(('',PORT),LoginHandler)
262+
except socket.error, e:
263+
logging.error(e[1])
264+
logging.warning("Server Shutdown")
265+
sys.exit(1)
266+
267+
try:
268+
logging.critical("Launching Server")
269+
dyndns.serve_forever()
270+
except KeyboardInterrupt:
271+
logging.critical("Server Shutdown")

dyndns.dtd

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!ELEMENT dyndns (user*)>
2+
<!ELEMENT user (name,password,comment?,hostname+)>
3+
<!ELEMENT name (#PCDATA)>
4+
<!ELEMENT password (#PCDATA)>
5+
<!ELEMENT comment (#PCDATA)>
6+
<!ELEMENT hostname (#PCDATA)>

dyndns.xml.example

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!DOCTYPE dyndns SYSTEM "dyndns.dtd">
3+
<dyndns>
4+
<user>
5+
<name>example_user</name>
6+
<password>example_password</password>
7+
<comment>example_comment</comment>
8+
<hostname>example_hostname(to be updated)</hostname>
9+
</user>
10+
</dyndns>
11+

scriptaggiornaip4

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
nsupdate <<-EOF
3+
server 127.0.0.1
4+
update delete $2.ninux.org. A
5+
update add $2.ninux.org. 3600 A $1
6+
send
7+
EOF
8+
echo IP: $1 aggiornato a $2

0 commit comments

Comments
 (0)