summaryrefslogtreecommitdiffstats
path: root/.scripts/retiolum.py
blob: 99da7aae041a7413b5bb814225017acdde0beb64 (plain)
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
#!/usr/bin/python2 
import sys, os, time, socket, subprocess, thread, random, Queue, binascii, logging #these should all be in the stdlib
import sqlite3
from Crypto.PublicKey import RSA
from optparse import OptionParser

def pub_encrypt(netname, hostname_t, text):  #encrypt data with public key
    conn = sqlite3.connect("/etc/tinc/" + netname + "/hosts.sqlite")
    c = conn.cursor()
    hostname_tupel = [hostname_t]
    pubkey = ""
    try:
        c.execute("select r_pub from hosts where hostname=?", hostname_tupel)
    except:
        logging.error("RSA_Encryption: Database error")
        return -1
    for i in c:
        pubkey += i[0]
    c.close
    rsa_pub = RSA.importKey(pubkey)
    enc_text = rsa_pub.encrypt(text, 0) #seems like RSA_encrypt needs no random
    return(binascii.b2a_base64(enc_text[0]))

def priv_decrypt(netname, enc_data): #decrypt data with private key
    raw_privkey = open("/etc/tinc/" + netname + "/rsa_key.priv", "r")
    r_privkey = raw_privkey.readlines()
    privkey = ""
    for i in xrange(len(r_privkey)):
        privkey += r_privkey[i]
    raw_privkey.close()
    

    rsa_priv = RSA.importKey(privkey)
    dec_text = rsa_priv.decrypt(binascii.a2b_base64(enc_data))
    return(dec_text)

def database2hostfiles(netname): #make hostsfiles from database
    conn = sqlite3.connect("/etc/tinc/" + netname + "/hosts.sqlite")
    c = conn.cursor()
    c.execute("select * from hosts")
    for i in c:
        host = open("/etc/tinc/" + netname + "/hosts/" + i[0], "w")
        host.write(i[2])
        host.write(i[3])
        host.write(i[1])
        host.write(i[5])
        host.close()
    c.close()

def address2hostfile(netname, hostname, address): #adds address to hostsfile or restores it if address is empty
    tupel = [hostname,]
    conn = sqlite3.connect("/etc/tinc/" + netname + "/hosts.sqlite")
    c = conn.cursor()
    c.execute("select * from hosts where hostname=?", tupel)
    for i in c:
        host = open("/etc/tinc/" + netname + "/hosts/" + i[0], "w")
        if address != "":
            host.write("Address = " + address + "\n")
        host.write(i[2])
        host.write(i[3])
        host.write(i[1])
        host.write(i[5])
        host.close()
    c.close()
    logging.info("sending ALRM to tinc deamon!")
    tincd_ALRM = subprocess.call(["tincd -n " + netname + " --kill=HUP" ],shell=True)

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


####Thread functions


def sendthread(netname, hostname, 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(netname, hostname, 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")
                        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")
                        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")
        except:
            logging.error("recv: socket init failed")
            time.sleep(10)

def timeoutthread(netname, 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":
                hostslist.append([curhost[1], curhost[2], time.time()])
                address2hostfile(netname, curhost[1], curhost[2])
                logging.info("adding host to hostslist")
            elif curhost[0] == "tst":
                line = findhostinlist(hostslist, curhost[1], curhost[2])
                if line != -1:
                    hostslist[line][2] = time.time()
                    logging.debug("timeout: refreshing timestamp")
                else:
                    authfifo.put(["Stage1", curhost[1], curhost[2]])
                    logging.info("timeout: writing to auth")

        else:
            i = 0
            while i < len(hostslist):
                if time.time() - hostslist[i][2] > 60:
                    address2hostfile(netname, hostslist[i][0], "")
                    del hostslist[i]
                    logging.info("timeout: deleting dead host")
                else:
                    i += 1
            time.sleep(2)

def auththread(netname, hostname, authfifo, sendfifo, timeoutfifo): #manages authentication with clients (bruteforce sensitve, should be fixed)
    authlist = [] #hostname, ip, Challenge, timestamp


    while True:
        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(netname, curauth[1], "#" + hostname + "#" + str(challengenum) + "#")
                    authlist.append([curauth[1], curauth[2], challengenum, time.time()])
                else:
                    encrypted_message = pub_encrypt(netname, 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")

            if curauth[0] == "Stage2":
                dec_message = priv_decrypt(netname, curauth[3])
                splitmes = dec_message.split("#")
                if splitmes[0] == "":
                    encrypted_message = pub_encrypt(netname, 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")

            if curauth[0] == "Stage3":
                line = findhostinlist(authlist, curauth[1], curauth[2])
                if line != -1:
                    dec_message = priv_decrypt(netname, curauth[3])
                    splitmes = dec_message.split("#")
                    logging.info("auth: checking challenge")
                    if splitmes[0] == "" and 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 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)

#Program starts here!
#netname = "retiolum"
#hostname = "miefda901"

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("-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")
(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


#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)

wget = subprocess.call(["wget vpn.miefda.org/hosts.sqlite -O /etc/tinc/" + netname + "/hosts.sqlite"], shell=True)
database2hostfiles(netname)
start_tincd = subprocess.call(["tincd -n " + netname ],shell=True)

sendfifo = Queue.Queue() #sendtext
authfifo = Queue.Queue() #Stage{1, 2, 3} hostname ip enc_data
timeoutfifo = Queue.Queue() #State{tst, add} hostname ip

thread_recv = thread.start_new_thread(recvthread, (netname, hostname, timeoutfifo, authfifo))
thread_send = thread.start_new_thread(sendthread, (netname, hostname, sendfifo, option.ghost))
thread_timeout = thread.start_new_thread(timeoutthread, (netname, timeoutfifo, authfifo))
thread_auth = thread.start_new_thread(auththread, (netname, hostname, authfifo, sendfifo, timeoutfifo))

##dirty while function, SHOULD BE IMPROVED
while True:
    time.sleep(10)