3
# Copyright 2008 Dan Smith <dsmith@danplanet.com>
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program. If not, see <http://www.gnu.org/licenses/>.
25
gettext.install("D-RATS")
27
from d_rats import platform
28
from d_rats import transport
29
from d_rats import comm
31
if __name__ == "__main__":
32
from optparse import OptionParser
35
o.add_option("-c", "--config",
37
help="Use alternate configuration directory")
38
o.add_option("-d", "--debug",
41
help="Show debug messages on stdout")
42
o.add_option("-C", "--console",
45
help="Run in console mode only")
46
(opts, args) = o.parse_args()
49
platform.get_platform(opts.config)
51
from d_rats.comm import SWFSerial
52
from d_rats import utils
54
def call_with_lock(lock, fn, *args):
69
def __init__(self, call, transport):
71
self.just_heard(transport)
76
def just_heard(self, transport):
77
self.__heard = time.time()
78
self.__transport = transport
81
return time.time() - self.__heard
83
def last_transport(self):
84
return self.__transport
86
def call_in_list(callinfo, call):
88
if call == info.get_call():
93
def __init__(self, id="D-RATS Network Proxy", reqauth=False, trustlocal=False):
99
self.repeat_thread = None
101
self.reqauth = reqauth
102
self.trustlocal = trustlocal
103
self.condition = threading.Condition()
105
# Forget port for a station after 10 minutes
106
self.__call_timeout = 600
108
def __repeat(self, transport, frame):
110
if frame.d_station == "!":
113
srcinfo = self.calls.get(frame.s_station, None)
114
if srcinfo is None and frame.s_station != "CQCQCQ":
115
print "Adding new station %s to port %s" % (frame.s_station,
117
self.calls[frame.s_station] = CallInfo(frame.s_station,
120
if srcinfo.last_transport() != transport:
121
print "Station %s moved to port %s" % (frame.s_station,
123
srcinfo.just_heard(transport)
125
dstinfo = self.calls.get(frame.d_station, None)
126
if dstinfo is not None:
127
if not dstinfo.last_transport().enabled:
128
print "Last transport for %s is dead" % frame.d_station
129
elif dstinfo.last_heard() < self.__call_timeout:
130
print "Delivering frame to %s at %s" % \
131
(frame.d_station, dstinfo.last_transport())
132
dstinfo.last_transport().send_frame(frame.get_copy())
135
print "Last port for %s was %i sec ago (>%i sec)" % \
137
dstinfo.last_heard(),
140
print "Repeating frame to %s on all ports" % frame.d_station
142
for path in self.paths[:]:
143
if path == transport:
146
print "Found a stale path, removing..."
148
self.paths.remove(path)
150
path.send_frame(frame.get_copy())
152
def add_new_transport(self, transport):
153
self.paths.append(transport)
156
self.condition.acquire()
158
self.__repeat(transport, frame)
160
print "Exception during __repeat: %s" % e
161
self.condition.release()
163
transport.inhandler = handler
165
def auth_exchange(self, pipe):
166
username = password = None
171
while "\r\n" not in data:
174
except socket.timeout:
183
while (not username or not password) and count < 3:
184
line = readline(pipe)
188
cmd, value = line.split(" ", 1)
190
print "Unable to read auth command: `%s': %s" % (line, e)
191
pipe.write("501 Invalid Syntax\r\n")
196
if cmd == "USER" and not username and not password:
198
elif cmd == "PASS" and username and not password:
201
pipe.write("201 Protocol violation\r\n")
204
if username and not password:
205
pipe.write("102 %s okay\r\n" % cmd)
207
if not username or not password:
208
print "Negotiation failed with client"
210
return username, password
212
def auth_user(self, pipe):
213
host, port = pipe._socket.getpeername()
216
pipe.write("100 Authentication not required\r\n")
218
elif self.trustlocal and host == "127.0.0.1":
219
pipe.write("100 Authentication not required for localhost\r\n")
222
auth_fn = platform.get_platform().config_file("users.txt")
225
lines = auth.readlines()
228
print "Failed to open %s: %s" % (auth_fn, e)
230
pipe.write("101 Authorization required\r\n")
231
username, password = self.auth_exchange(pipe)
237
u, p = line.split(" ", 1)
240
print "Failed to parse line %i in users.txt: %s" % (lno, line)
243
if u == username and p == password:
244
print "Authorized user %s" % u
245
pipe.write("200 Authorized\r\n")
248
print "User %s failed to authenticate" % username
249
pipe.write("500 Not authorized\r\n")
252
def accept_new(self):
257
(csocket, addr) = self.socket.accept()
261
print "Accepted new client %s:%i" % addr
262
path = comm.SocketDataPath(csocket)
263
tport = transport.Transporter(path,
264
authfn=self.auth_user,
266
self.add_new_transport(tport)
268
def listen_on(self, port):
269
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
270
self.socket.setblocking(0)
271
self.socket.setsockopt(socket.SOL_SOCKET,
274
self.socket.bind(('0.0.0.0', port))
275
self.socket.listen(0)
279
self.condition.acquire()
281
self.condition.release()
285
print "Repeater thread ended"
288
self.repeat_thread = threading.Thread(target=self._repeat)
289
self.repeat_thread.setDaemon(True)
290
self.repeat_thread.start()
295
self.condition.acquire()
296
self.condition.notify()
297
self.condition.release()
299
if self.repeat_thread:
300
print "Stopping repeater"
301
self.repeat_thread.join()
316
self.platform = platform.get_platform()
317
self.config = self.load_config()
319
def load_config(self):
320
self.config_fn = self.platform.config_file("repeater.config")
321
config = ConfigParser.ConfigParser()
322
config.add_section("settings")
323
config.set("settings", "devices", "[]")
324
config.set("settings", "acceptnet", "True")
325
config.set("settings", "netport", "9000")
326
config.set("settings", "id", "W1AW")
327
config.set("settings", "idfreq", "30")
328
config.set("settings", "require_auth", "False")
329
config.set("settings", "trust_local", "True")
330
config.read(self.config_fn)
334
def save_config(self, config):
336
f = file(self.config_fn, "w")
340
def add_outgoing_paths(self, id, paths):
341
reqauth = self.config.get("settings", "require_auth") == "True"
342
trustlocal = self.config.get("settings", "trust_local") == "True"
343
print "Repeater id is %s" % id
344
self.repeater = Repeater(id, reqauth, trustlocal)
345
for dev,param in paths:
347
if dev.startswith("net:"):
349
net, host, port = dev.split(":", 2)
352
print "Invalid net string: %s (%s)" % (dev, e)
355
print "Socket %s %i (%s)" % (host, port, param)
358
path = comm.SocketDataPath((host, port, id, param))
360
path = comm.SocketDataPath((host, port))
361
elif dev.startswith("tnc:"):
363
tnc, port, device = dev.split(":", 2)
366
print "Invalid tnc string: %s (%s)" % (dev, e)
368
print "TNC %s %i" % (dev.replace("tnc:", ""), int(param))
369
path = comm.TNCDataPath((dev.replace("tnc:", ""), int(param)))
371
print "Serial: %s %i" % (dev, int(param))
372
path = comm.SerialDataPath((dev, int(param)))
376
tport = transport.Transporter(path, warmup_timout=to)
377
self.repeater.add_new_transport(tport)
380
class RepeaterGUI(RepeaterUI):
381
def add_serial(self, widget):
382
name, portspec, param = prompt_for_port(None, pname=False)
386
self.dev_list.add_item(portspec, param)
388
def sig_destroy(self, widget, data=None):
389
self.button_off(None, False)
390
self.save_config(self.config)
393
def ev_delete(self, widget, event, data=None):
394
self.button_off(None, False)
395
self.save_config(self.config)
399
def make_side_buttons(self):
400
vbox = gtk.VBox(False, 2)
402
but_add = gtk.Button("Add")
403
but_add.connect("clicked", self.add_serial)
404
but_add.set_size_request(75, 30)
406
vbox.pack_start(but_add, 0,0,0)
408
but_remove = gtk.Button("Remove")
409
but_remove.set_size_request(75, 30)
410
but_remove.connect("clicked", self.button_remove)
412
vbox.pack_start(but_remove, 0,0,0)
418
def load_devices(self):
420
l = eval(self.config.get("settings", "devices"))
422
self.dev_list.add_item(d, r)
424
print "Unable to load devices: %s" % e
426
def make_devices(self):
427
frame = gtk.Frame("Paths")
429
vbox = gtk.VBox(False, 2)
432
hbox = gtk.HBox(False, 2)
434
self.dev_list = miscwidgets.ListWidget([(gobject.TYPE_STRING, "Device"),
435
(gobject.TYPE_STRING, "Param")])
439
sw = gtk.ScrolledWindow()
440
sw.add_with_viewport(self.dev_list)
443
hbox.pack_start(sw, 1,1,1)
444
hbox.pack_start(self.make_side_buttons(), 0,0,0)
447
vbox.pack_start(hbox, 1,1,1)
454
def make_network(self):
455
frame = gtk.Frame("Network")
457
vbox = gtk.VBox(False, 2)
459
hbox = gtk.HBox(False, 2)
462
self.net_enabled = gtk.CheckButton("Accept incoming connections")
464
accept = self.config.getboolean("settings", "acceptnet")
468
self.net_enabled.set_active(accept)
469
self.net_enabled.show()
471
hbox.pack_start(self.net_enabled, 0,0,0)
473
self.entry_port = gtk.Entry()
475
port = self.config.get("settings", "netport")
479
self.entry_port.set_text(port)
480
self.entry_port.set_size_request(100, -1)
481
self.entry_port.show()
482
hbox.pack_end(self.entry_port, 0,0,0)
484
lab = gtk.Label("Port:")
486
hbox.pack_end(lab, 0,0,0)
489
vbox.pack_start(hbox, 0,0,0)
496
def make_bottom_buttons(self):
497
hbox = gtk.HBox(False, 2)
499
self.but_on = gtk.Button("On")
500
self.but_on.set_size_request(75, 30)
501
self.but_on.connect("clicked", self.button_on)
503
hbox.pack_start(self.but_on, 0,0,0)
505
self.but_off = gtk.Button("Off")
506
self.but_off.set_size_request(75, 30)
507
self.but_off.connect("clicked", self.button_off)
508
self.but_off.set_sensitive(False)
510
hbox.pack_start(self.but_off, 0,0,0)
517
frame = gtk.Frame("Repeater Callsign")
519
hbox = gtk.HBox(False, 2)
521
self.entry_id = gtk.Entry()
523
deftxt = self.config.get("settings", "id")
527
self.entry_id.set_text(deftxt)
528
self.entry_id.set_max_length(8)
530
hbox.pack_start(self.entry_id, 1,1,1)
533
idfreq = self.config.get("settings", "idfreq")
537
self.id_freq = make_choice(["Never", "30", "60", "120"],
540
self.id_freq.set_size_request(75, -1)
542
hbox.pack_start(self.id_freq, 0,0,0)
551
frame = gtk.Frame("Authentication")
553
hbox = gtk.HBox(False, 20)
555
def toggle_option(cb, option):
556
self.config.set("settings", option, str(cb.get_active()))
558
self.req_auth = gtk.CheckButton("Require Authentication")
559
self.req_auth.connect("toggled", toggle_option, "require_auth")
561
self.req_auth.set_active(self.config.getboolean("settings",
563
hbox.pack_start(self.req_auth, 0, 0, 0)
565
self.trust_local = gtk.CheckButton("Trust localhost")
566
self.trust_local.connect("toggled", toggle_option, "trust_local")
567
self.trust_local.show()
568
self.trust_local.set_active(self.config.getboolean("settings",
570
hbox.pack_start(self.trust_local, 0, 0, 0)
572
def do_edit_users(but):
573
p = platform.get_platform()
574
p.open_text_file(p.config_file("users.txt"))
576
edit_users = gtk.Button("Edit Users")
577
edit_users.connect("clicked", do_edit_users)
579
edit_users.set_size_request(75, 30)
580
hbox.pack_end(edit_users, 0, 0, 0)
587
def make_settings(self):
588
vbox = gtk.VBox(False, 5)
590
hbox = gtk.HBox(False, 5)
592
vbox.pack_start(self.make_devices(), 1,1,1)
593
vbox.pack_start(self.make_network(), 0,0,0)
594
vbox.pack_start(self.make_auth(), 0,0,0)
595
vbox.pack_start(self.make_id(), 0,0,0)
597
vbox.pack_start(hbox, 0, 0, 0)
605
def make_connected(self):
606
frame = gtk.Frame("Connected Paths")
608
idlist = miscwidgets.ListWidget([(gobject.TYPE_STRING, "ID")])
611
self.conn_list = gtk.ScrolledWindow()
612
self.conn_list.add_with_viewport(idlist)
613
self.conn_list.show()
615
frame.add(self.conn_list)
620
def make_traffic(self):
621
frame = gtk.Frame("Traffic Monitor")
623
self.traffic_buffer = gtk.TextBuffer()
624
self.traffic_view = gtk.TextView(buffer=self.traffic_buffer)
625
self.traffic_view.set_wrap_mode(gtk.WRAP_WORD)
626
self.traffic_view.show()
628
self.traffic_buffer.create_mark("end",
629
self.traffic_buffer.get_end_iter(),
632
sw = gtk.ScrolledWindow()
633
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
634
sw.add(self.traffic_view)
642
def make_monitor(self):
643
vbox = gtk.VBox(False, 5)
645
vbox.pack_start(self.make_connected(), 1,1,1)
646
vbox.pack_start(self.make_traffic(), 1,1,1)
652
def sync_config(self):
653
id = self.entry_id.get_text()
654
#idfreq = self.id_freq.get_active_text()
655
port = self.entry_port.get_text()
656
acceptnet = str(self.net_enabled.get_active())
657
devices = self.dev_list.get_values()
658
auth = self.req_auth.get_active()
659
local = self.trust_local.get_active()
661
self.config.set("settings", "id", id)
662
#self.config.set("settings", "idfreq", idfreq)
663
self.config.set("settings", "netport", port)
664
self.config.set("settings", "acceptnet", acceptnet)
665
self.config.set("settings", "devices", devices)
666
self.config.set("settings", "require_auth", str(auth))
667
self.config.set("settings", "trust_local", str(local))
669
def button_remove(self, widget):
670
self.dev_list.remove_selected()
672
def button_on(self, widget, data=None):
675
self.config.set("settings", "state", "True")
676
self.save_config(self.config)
678
self.but_off.set_sensitive(True)
679
self.but_on.set_sensitive(False)
680
self.settings.set_sensitive(False)
682
self.add_outgoing_paths(self.config.get("settings", "id"),
683
self.dev_list.get_values())
686
port = int(self.entry_port.get_text())
687
enabled = self.net_enabled.get_active()
692
self.repeater.listen_on(port)
694
#self.tap = LoopDataPath("TAP", self.repeater.condition)
695
#self.repeater.paths.append(self.tap)
697
self.repeater.repeat()
699
def button_off(self, widget, user=True):
701
self.config.set("settings", "state", "False")
702
self.save_config(self.config)
704
self.but_off.set_sensitive(False)
705
self.but_on.set_sensitive(True)
706
self.settings.set_sensitive(True)
715
paths = self.repeater.paths
716
l = [(x.id,) for x in paths]
723
self.conn_list.child.set_values(l)
726
traffic = self.tap.peek()
727
end = self.traffic_buffer.get_end_iter()
728
self.traffic_buffer.insert(end, utils.filter_to_ascii(traffic))
730
count = self.traffic_buffer.get_line_count()
732
start = self.traffic_buffer.get_start_iter()
733
limit = self.traffic_buffer.get_iter_at_line(count - 200)
734
self.traffic_buffer.delete(start, limit)
736
endmark = self.traffic_buffer.get_mark("end")
737
self.traffic_view.scroll_to_mark(endmark, 0.0, True, 0, 1)
739
limit = int(self.id_freq.get_active_text())
740
if (self.tick / 60) == limit:
741
self.repeater.send_data(None, self.entry_id.get_text())
751
RepeaterUI.__init__(self)
753
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
754
self.window.set_default_size(450, 380)
755
self.window.connect("delete_event", self.ev_delete)
756
self.window.connect("destroy", self.sig_destroy)
757
self.window.set_title("D-RATS Repeater Proxy")
759
vbox = gtk.VBox(False, 5)
761
self.tabs = gtk.Notebook()
762
self.tabs.append_page(self.make_settings(), gtk.Label("Settings"))
764
# self.tabs.append_page(self.make_monitor(), gtk.Label("Monitor"))
767
vbox.pack_start(self.tabs, 1,1,1)
768
vbox.pack_start(self.make_bottom_buttons(), 0,0,0)
771
self.window.add(vbox)
774
#gobject.timeout_add(1000, self.update)
777
if self.config.get("settings", "state") == "True":
778
self.button_on(None, None)
782
class RepeaterConsole(RepeaterUI):
784
devices = eval(self.config.get("settings", "devices"))
785
self.add_outgoing_paths(self.config.get("settings", "id"), devices)
788
acceptnet = eval(self.config.get("settings", "acceptnet"))
789
netport = int(self.config.get("settings", "netport"))
790
idfreq = self.config.get("settings", "idfreq")
791
if idfreq == "Never":
796
print "Failed to parse network info: %s" % e
800
self.repeater.listen_on(netport)
802
self.repeater.repeat()
807
except KeyboardInterrupt:
812
if __name__=="__main__":
817
p = platform.get_platform()
818
f = file(p.config_file("repeater.log"), "w", 0)
823
print "Failed to open log"
826
r = RepeaterConsole()
831
from d_rats.miscwidgets import make_choice
832
from d_rats import miscwidgets
833
from d_rats.config import prompt_for_port
835
gobject.threads_init()