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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
|
#!/usr/bin/python2
import sys, os, time, signal, socket, subprocess, thread, random, Queue, binascii, logging, hashlib, urllib2 #these should all be in the stdlib
from optparse import OptionParser
def pub_encrypt(hostname_t, text): #encrypt data with public key
logging.debug("encrypt: " + text)
if hostname_t.find("`") != -1: return(-1)
try:
enc_text = subprocess.os.popen("echo '" + text + "' | openssl rsautl -pubin -inkey /etc/tinc/" + netname + "/hosts/.pubkeys/" + hostname_t + " -encrypt | base64 -w0")
return(enc_text.read())
except:
return(-1)
def priv_decrypt(enc_data): #decrypt data with private key
if enc_data.find("`") != -1: return(-1)
dec_text = subprocess.os.popen("echo '" + enc_data + "' | base64 -d | openssl rsautl -inkey /etc/tinc/" + netname + "/rsa_key.priv -decrypt")
return(dec_text.read())
def address2hostfile(hostname, address): #adds address to hostsfile or restores it if address is empty
hostfile = "/etc/tinc/" + netname + "/hosts/" + hostname
addr_file = open(hostfile, "r")
addr_cache = addr_file.readlines()
addr_file.close()
if address != "":
addr_cache.insert(0, "Address = " + address + "\n")
addr_file = open(hostfile, "w")
addr_file.writelines(addr_cache)
addr_file.close
logging.info("sending SIGHUP to tinc deamon!")
tincd_ALRM = subprocess.call(["tincd -n " + netname + " --kill=HUP" ],shell=True)
else:
recover = subprocess.os.popen("tar xzf /etc/tinc/" + netname + "/hosts/hosts.tar.gz -C /etc/tinc/" + netname + "/hosts/ " + hostname)
def findhostinlist(hostslist, hostname, ip): #finds host + ip in list
for line in xrange(len(hostslist)):
if hostname == hostslist[line][0] and ip == hostslist[line][1]:
return line
return -1 #nothing found
def getHostname(netname):
tconf = open("/etc/tinc/" + netname + "/tinc.conf", "r")
feld = tconf.readlines()
tconf.close()
for x in feld:
if x.startswith("Name"):
return str(x.partition("=")[2].lstrip().rstrip("\n"))
print("hostname not found!")
return -1 #nothing found
def get_hostfiles(url_files, url_md5sum):
try:
get_hosts_tar = urllib2.urlopen(url_files)
get_hosts_md5 = urllib2.urlopen(url_md5sum)
hosts_tar = get_hosts_tar.read()
hosts_md5 = get_hosts_md5.read()
if str(hosts_md5) == str(hashlib.md5(hosts_tar).hexdigest() + " hosts.tar.gz\n"):
hosts = open("/etc/tinc/" + netname + "/hosts/hosts.tar.gz", "w")
hosts.write(hosts_tar)
hosts.close()
else:
logging.error("hosts.tar.gz md5sum check failed!")
except:
logging.error("hosts file download failed!")
####Thread functions
def sendthread(sendfifo, ghostmode): #send to multicast, sends keep alive packets
while True:
try:
#{socket init start
ANY = "0.0.0.0"
SENDPORT = 23542
MCAST_ADDR = "224.168.2.9"
MCAST_PORT = 1600
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) #initalize socket with udp
sock.bind((ANY,SENDPORT)) #now bound to Interface and Port
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) #activate multicast
#}socket init end
if ghostmode == 0:
i = 9
while True:
i += 1
if not sendfifo.empty():
sock.sendto(sendfifo.get(), (MCAST_ADDR,MCAST_PORT) )
logging.info("send: sending sendfifo")
else:
time.sleep(1)
if i == 10:
sock.sendto("#Stage1#" + netname + "#" + hostname + "#", (MCAST_ADDR,MCAST_PORT) )
logging.debug("send: sending keep alive")
i = 0
else:
while True:
if not sendfifo.empty():
sock.sendto(sendfifo.get(), (MCAST_ADDR,MCAST_PORT) )
logging.info("send: sending sendfifo")
else:
time.sleep(1)
except:
logging.error("send: socket init failed")
time.sleep(10)
def recvthread(timeoutfifo, authfifo): #recieves input from multicast, send them to timeout or auth
while True:
try:
ANY = "0.0.0.0"
MCAST_ADDR = "224.168.2.9"
MCAST_PORT = 1600
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) #create a UDP socket
sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #allow multiple sockets to use the same PORT number
sock.bind((ANY,MCAST_PORT)) #Bind to the port that we know will receive multicast data
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) #tell the kernel that we are a multicast socket
status = sock.setsockopt(socket.IPPROTO_IP,
socket.IP_ADD_MEMBERSHIP, #Tell the kernel that we want to add ourselves to a multicast group
socket.inet_aton(MCAST_ADDR) + socket.inet_aton(ANY)); #The address for the multicast group is the third param
while True:
while True:
try:
data, addr = sock.recvfrom(1024)
ip, port = addr
break
except socket.error, e:
pass
logging.debug("recv: got data")
dataval = data.split("#")
if dataval[0] == "":
if dataval[2] == netname:
if dataval[1] == "Stage1":
if dataval[3] != hostname:
timeoutfifo.put(["tst", dataval[3], ip])
logging.info("recv: got Stage1: writing data to timeout")
logging.debug("recv: ;tst;" + dataval[3] + ";" + ip)
if dataval[1] == "Stage2":
if dataval[3] == hostname:
authfifo.put([dataval[1], dataval[3], ip, dataval[4]])
logging.info("recv: got Stage2: writing data to auth")
logging.debug("recv: ;" + dataval[1] + ";" + dataval[3] + ";" + ip + ";" + dataval[4])
if dataval[1] == "Stage3":
if dataval[3] != hostname:
authfifo.put([dataval[1], dataval[3], ip, dataval[4]])
logging.info("recv: got Stage3: writing data to auth")
logging.debug("recv: ;" + dataval[1] + ";" + dataval[3] + ";" + ip + ";" + dataval[4])
except:
logging.error("recv: socket init failed")
time.sleep(10)
def timeoutthread(timeoutfifo, authfifo): #checks if the hostname is already in the list, deletes timeouted nodes
# hostslist = [] #hostname, ip, timestamp
while True:
if not timeoutfifo.empty():
curhost = timeoutfifo.get()
if curhost[0] == "add":
with hostslock:
hostslist.append([curhost[1], curhost[2], time.time()])
address2hostfile(curhost[1], curhost[2])
logging.info("adding host to hostslist")
elif curhost[0] == "tst":
with hostslock:
line = findhostinlist(hostslist, curhost[1], curhost[2])
if line != -1:
hostslist[line][2] = time.time()
logging.debug("timeout: refreshing timestamp of " + hostslist[line][0])
else:
authfifo.put(["Stage1", curhost[1], curhost[2]])
logging.info("timeout: writing to auth")
else:
i = 0
with hostslock:
while i < len(hostslist):
if time.time() - hostslist[i][2] > 60:
address2hostfile(hostslist[i][0], "")
hostslist.remove(hostslist[i])
logging.info("timeout: deleting dead host")
else:
i += 1
time.sleep(2)
def auththread(authfifo, sendfifo, timeoutfifo): #manages authentication with clients (bruteforce sensitve, should be fixed)
authlist = [] #hostname, ip, Challenge, timestamp
while True:
try:
if not authfifo.empty():
logging.debug("auth: authfifo is not empty")
curauth = authfifo.get()
if curauth[0] == "Stage1":
line = findhostinlist(authlist, curauth[1], curauth[2])
if line == -1:
challengenum = random.randint(0,65536)
encrypted_message = pub_encrypt(curauth[1], "#" + hostname + "#" + str(challengenum) + "#")
authlist.append([curauth[1], curauth[2], challengenum, time.time()])
else:
encrypted_message = pub_encrypt(authlist[line][0], "#" + hostname + "#" + str(authlist[line][2]) + "#")
if encrypted_message == -1:
logging.info("auth: RSA Encryption Error")
else:
sendtext = "#Stage2#" + netname + "#" + curauth[1] + "#" + encrypted_message + "#"
sendfifo.put(sendtext)
logging.info("auth: got Stage1 sending now Stage2")
logging.debug("auth: " + sendtext)
if curauth[0] == "Stage2":
dec_message = priv_decrypt(curauth[3])
splitmes = dec_message.split("#")
if splitmes[0] == "":
encrypted_message = pub_encrypt(splitmes[1], "#" + splitmes[2] + "#")
if encrypted_message == -1:
logging.error("auth: RSA Encryption Error")
else:
sendtext = "#Stage3#" + netname + "#" + curauth[1] + "#" + encrypted_message + "#"
sendfifo.put(sendtext)
logging.info("auth: got Stage2 sending now Stage3")
logging.debug("auth: " + sendtext)
if curauth[0] == "Stage3":
line = findhostinlist(authlist, curauth[1], curauth[2])
if line != -1:
dec_message = priv_decrypt(curauth[3])
splitmes = dec_message.split("#")
logging.info("auth: checking challenge")
if splitmes[0] == "":
if splitmes[1] == str(authlist[line][2]):
timeoutfifo.put(["add", curauth[1], curauth[2]])
del authlist[line]
logging.info("auth: Stage3 checked, sending now to timeout")
else: logging.error("auth: challenge checking failed")
else: logging.error("auth: decryption failed")
else:
i = 0
while i < len(authlist):
if time.time() - authlist[i][3] > 120:
del authlist[i]
logging.info("auth: deleting timeoutet auth")
else:
i += 1
time.sleep(1)
except:
logging.error("auth: thread crashed")
def process_start(): #starting of the process
#download and untar hostfile
logging.info("downloading hostfiles")
get_hostfiles("http://vpn.miefda.org/hosts.tar.gz", "http://vpn.miefda.org/hosts.md5") #Currently Hardcoded, should be editable by config or parameter
tar = subprocess.call(["tar -xzf /etc/tinc/" + netname + "/hosts/hosts.tar.gz -C /etc/tinc/" + netname + "/hosts/"], shell=True)
#initialize fifos
sendfifo = Queue.Queue() #sendtext
authfifo = Queue.Queue() #Stage{1, 2, 3} hostname ip enc_data
timeoutfifo = Queue.Queue() #State{tst, add} hostname ip
#start threads
thread_recv = thread.start_new_thread(recvthread, (timeoutfifo, authfifo))
thread_send = thread.start_new_thread(sendthread, (sendfifo, option.ghost))
thread_timeout = thread.start_new_thread(timeoutthread, (timeoutfifo, authfifo))
thread_auth = thread.start_new_thread(auththread, (authfifo, sendfifo, timeoutfifo))
def process_restart(signum, frame):
logging.error("root: restarting process")
with hostslock:
del hostslist[:]
#download and untar hostfile
logging.info("downloading hostfiles")
get_hostfiles("http://vpn.miefda.org/hosts.tar.gz", "http://vpn.miefda.org/hosts.md5") #Currently Hardcoded, should be editable by config or parameter
tar = subprocess.call(["tar -xzf /etc/tinc/" + netname + "/hosts/hosts.tar.gz -C /etc/tinc/" + netname + "/hosts/"], shell=True)
logging.info("sending SIGHUP")
tincd_ALRM = subprocess.call(["tincd -n " + netname + " --kill=HUP" ],shell=True)
def kill_process(signum, frame):
logging.error("got SIGINT/SIGTERM exiting now")
os.remove("/var/lock/retiolum." + netname)
if option.tinc != False:
stop_tincd = subprocess.call(["tincd -n " + netname + " -k"],shell=True)
sys.exit(0)
#Program starts here!
parser = OptionParser()
parser.add_option("-n", "--netname", dest="netname", help="the netname of the tinc network")
parser.add_option("-H", "--hostname", dest="hostname", default="default", help="your nodename, if not given, it will try too read it from tinc.conf")
parser.add_option("-t", "--timeout", dest="timeout", default=65536, help="timeout after retiolum gets restartet, default is 65536")
parser.add_option("-d", "--debug", dest="debug", default="0", help="debug level: 0,1,2,3 if empty debug level=0")
parser.add_option("-g", "--ghost", action="store_true", dest="ghost", default=False, help="deactivates active sending, keeps you anonymous in the public network")
parser.add_option("-T", "--Tinc", action="store_true", dest="tinc", default=False, help="starts tinc with this script")
(option, args) = parser.parse_args()
if option.netname == None:
parser.error("Netname is required, use -h for help!")
if option.hostname == "default":
option.hostname = getHostname(option.netname)
hostname = option.hostname
netname = option.netname
hostslist = []
hostslock = thread.allocate_lock()
#set process name
if not os.path.exists("/var/lock/retiolum." + netname):
pidfile = open("/var/lock/retiolum." + netname, "w")
pidfile.write(str(os.getpid()))
pidfile.close()
else:
logging.error("pidfile already exists")
sys.exit(0)
#Logging stuff
LEVELS = {'3' : logging.DEBUG,
'2' : logging.INFO,
'1' : logging.ERROR,
'0' : logging.CRITICAL}
level_name = option.debug
level = LEVELS.get(level_name, logging.NOTSET)
logging.basicConfig(level=level)
#normally tinc doesnt start with retiolum
if option.tinc != False:
start_tincd = subprocess.call(["tincd -n " + netname ],shell=True)
process_start()
signal.signal(signal.SIGTERM, kill_process)
signal.signal(signal.SIGINT, kill_process)
signal.signal(signal.SIGUSR1, process_restart)
while True:
time.sleep(float(option.timeout))
process_restart(0, 0)
|