Skip to content

Commit f671aa4

Browse files
ui, rules editor: allow to use commas
Allow to use commas to specify multiple data: - src/dst ip: 1.2.3.4, 5.6.7.8 - dst host: www.a-b-c.org, www.abcd.org - proto: tcp, udp - net ifaces: eth0, eth1 - src/dst port: 123, 456 The data will be translated to ^(..|..|..)$ when saving the rule, and back to "..,..,.." when loading it. Closes: #1392.
1 parent f71f42b commit f671aa4

File tree

1 file changed

+148
-7
lines changed

1 file changed

+148
-7
lines changed

ui/opensnitch/dialogs/ruleseditor.py

Lines changed: 148 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,55 @@ def _load_duration(self, duration):
363363
# always
364364
return 8
365365

366+
def _comma_to_regexp(self, text, expected_type):
367+
"""translates items separated by comma, to regular expression
368+
returns True|False, regexp|error
369+
"""
370+
s_parts = text.replace(" ", "").split(",")
371+
sp_regex = r'^('
372+
for p in s_parts:
373+
if expected_type == int:
374+
try:
375+
int(p)
376+
except:
377+
return False, QC.translate("rules", "Invalid text")
378+
if p == "":
379+
return False, QC.translate("rules", "Invalid text")
380+
381+
sp_regex += '{0}|'.format(p)
382+
sp_regex = sp_regex.removesuffix("|")
383+
sp_regex += r')$'
384+
if not self._is_valid_regex(sp_regex):
385+
return False, QC.translate("rules", "regexp error (report it)")
386+
387+
return True, sp_regex
388+
389+
def _regexp_to_comma(self, text, expected_type):
390+
"""translates a regular expression to a comma separated list
391+
from ^(1|2|3)$ to "1,2,3"
392+
"""
393+
error = ""
394+
# match ^(1|2|3)$
395+
regexp_str = r'\^\(([\d|]+)\)\$'
396+
if expected_type == str:
397+
# match ^(www.a-b-c.org|fff.uk|ooo.tw)$
398+
regexp_str = r'\^\(([.\-\w|]+)\)\$'
399+
q = re.search(regexp_str, text)
400+
if not q:
401+
return None, error
402+
try:
403+
parts = q.group(1).split("|")
404+
for p in parts:
405+
# unlikely. The regexp should haven't match.
406+
if expected_type == int:
407+
int(p)
408+
return ",".join(parts), ""
409+
except Exception as e:
410+
print("_regexp_to_comma exception:", e)
411+
error = "Error parsing regexp to comma: {0}".format(e)
412+
413+
return None, error
414+
366415
def _is_regex(self, text):
367416
charset="\\*{[|^?$"
368417
for c in charset:
@@ -500,7 +549,12 @@ def _load_rule_operator(self, operator):
500549
if operator.operand == Config.OPERAND_PROTOCOL:
501550
self.protoCheck.setChecked(True)
502551
self.protoCombo.setEnabled(True)
503-
self.protoCombo.setCurrentText(operator.data.upper())
552+
prots, err = self._regexp_to_comma(operator.data, str)
553+
if err != "":
554+
self._set_status_error(err)
555+
if prots is None:
556+
prots = operator.data
557+
self.protoCombo.setCurrentText(prots.upper())
504558

505559
if operator.operand == Config.OPERAND_PROCESS_PATH:
506560
self.procCheck.setChecked(True)
@@ -529,17 +583,32 @@ def _load_rule_operator(self, operator):
529583
if operator.operand == Config.OPERAND_IFACE_OUT:
530584
self.ifaceCheck.setChecked(True)
531585
self.ifaceCombo.setEnabled(True)
532-
self.ifaceCombo.setCurrentText(operator.data)
586+
ifaces, err = self._regexp_to_comma(operator.data, str)
587+
if err != "":
588+
self._set_status_error(err)
589+
if ifaces is None:
590+
ifaces = operator.data
591+
self.ifaceCombo.setCurrentText(ifaces)
533592

534593
if operator.operand == Config.OPERAND_SOURCE_PORT:
535594
self.srcPortCheck.setChecked(True)
536595
self.srcPortLine.setEnabled(True)
537-
self.srcPortLine.setText(operator.data)
596+
ports, err = self._regexp_to_comma(operator.data, int)
597+
if err != "":
598+
self._set_status_error(err)
599+
if ports is None:
600+
ports = operator.data
601+
self.srcPortLine.setText(ports)
538602

539603
if operator.operand == Config.OPERAND_DEST_PORT:
540604
self.dstPortCheck.setChecked(True)
541605
self.dstPortLine.setEnabled(True)
542-
self.dstPortLine.setText(operator.data)
606+
ports, err = self._regexp_to_comma(operator.data, int)
607+
if err != "":
608+
self._set_status_error(err)
609+
if ports is None:
610+
ports = operator.data
611+
self.dstPortLine.setText(ports)
543612

544613
if operator.operand == Config.OPERAND_SOURCE_IP or operator.operand == Config.OPERAND_SOURCE_NETWORK:
545614
self.srcIPCheck.setChecked(True)
@@ -549,7 +618,12 @@ def _load_rule_operator(self, operator):
549618
elif operator.data == self.MULTICAST_RANGE:
550619
self.srcIPCombo.setCurrentText(self.MULTICAST_LABEL)
551620
else:
552-
self.srcIPCombo.setCurrentText(operator.data)
621+
ips, err = self._regexp_to_comma(operator.data, str)
622+
if err != "":
623+
self._set_status_error(err)
624+
if ips is None:
625+
ips = operator.data
626+
self.srcIPCombo.setCurrentText(ips)
553627

554628
if operator.operand == Config.OPERAND_DEST_IP or operator.operand == Config.OPERAND_DEST_NETWORK:
555629
self.dstIPCheck.setChecked(True)
@@ -559,12 +633,22 @@ def _load_rule_operator(self, operator):
559633
elif operator.data == self.MULTICAST_RANGE:
560634
self.dstIPCombo.setCurrentText(self.MULTICAST_LABEL)
561635
else:
562-
self.dstIPCombo.setCurrentText(operator.data)
636+
ips, err = self._regexp_to_comma(operator.data, str)
637+
if err != "":
638+
self._set_status_error(err)
639+
if ips is None:
640+
ips = operator.data
641+
self.dstIPCombo.setCurrentText(ips)
563642

564643
if operator.operand == Config.OPERAND_DEST_HOST:
565644
self.dstHostCheck.setChecked(True)
566645
self.dstHostLine.setEnabled(True)
567-
self.dstHostLine.setText(operator.data)
646+
hosts, err = self._regexp_to_comma(operator.data, str)
647+
if err != "":
648+
self._set_status_error(err)
649+
if hosts is None:
650+
hosts = operator.data
651+
self.dstHostLine.setText(hosts)
568652

569653
if operator.operand == Config.OPERAND_LIST_DOMAINS:
570654
self.dstListsCheck.setChecked(True)
@@ -715,6 +799,14 @@ def _save_rule(self):
715799
if self._is_valid_regex(self.protoCombo.currentText()) == False:
716800
return False, QC.translate("rules", "Protocol regexp error")
717801

802+
elif "," in self.protoCombo.currentText():
803+
ok, result = self._comma_to_regexp(self.protoCombo.currentText().lower(), str)
804+
if ok:
805+
rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
806+
rule_data[len(rule_data)-1]['data'] = result
807+
else:
808+
return False, result
809+
718810
if self.procCheck.isChecked():
719811
if self.procLine.text() == "":
720812
return False, QC.translate("rules", "process path can not be empty")
@@ -769,6 +861,14 @@ def _save_rule(self):
769861
if self._is_valid_regex(self.ifaceCombo.currentText()) == False:
770862
return False, QC.translate("rules", "Network interface regexp error")
771863

864+
elif "," in self.ifaceCombo.currentText():
865+
ok, result = self._comma_to_regexp(self.ifaceCombo.currentText(), str)
866+
if ok:
867+
rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
868+
rule_data[len(rule_data)-1]['data'] = result
869+
else:
870+
return False, result
871+
772872
if self.srcPortCheck.isChecked():
773873
if self.srcPortLine.text() == "":
774874
return False, QC.translate("rules", "Source port can not be empty")
@@ -787,6 +887,14 @@ def _save_rule(self):
787887
if self._is_valid_regex(self.srcPortLine.text()) == False:
788888
return False, QC.translate("rules", "Source port regexp error")
789889

890+
elif "," in self.srcPortLine.text():
891+
ok, result = self._comma_to_regexp(self.srcPortLine.text(), int)
892+
if ok:
893+
rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
894+
rule_data[len(rule_data)-1]['data'] = result
895+
else:
896+
return False, result
897+
790898
if self.dstPortCheck.isChecked():
791899
if self.dstPortLine.text() == "":
792900
return False, QC.translate("rules", "Dest port can not be empty")
@@ -805,6 +913,14 @@ def _save_rule(self):
805913
if self._is_valid_regex(self.dstPortLine.text()) == False:
806914
return False, QC.translate("rules", "Dst port regexp error")
807915

916+
elif "," in self.dstPortLine.text():
917+
ok, result = self._comma_to_regexp(self.dstPortLine.text(), int)
918+
if ok:
919+
rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
920+
rule_data[len(rule_data)-1]['data'] = result
921+
else:
922+
return False, result
923+
808924
if self.dstHostCheck.isChecked():
809925
if self.dstHostLine.text() == "":
810926
return False, QC.translate("rules", "Dest host can not be empty")
@@ -823,6 +939,14 @@ def _save_rule(self):
823939
if self._is_valid_regex(self.dstHostLine.text()) == False:
824940
return False, QC.translate("rules", "Dst host regexp error")
825941

942+
elif "," in self.dstHostLine.text():
943+
ok, result = self._comma_to_regexp(self.dstHostLine.text(), str)
944+
if ok:
945+
rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
946+
rule_data[len(rule_data)-1]['data'] = result
947+
else:
948+
return False, result
949+
826950
if self.srcIPCheck.isChecked():
827951
if self.srcIPCombo.currentText() == "":
828952
return False, QC.translate("rules", "Source IP/Network can not be empty")
@@ -853,6 +977,15 @@ def _save_rule(self):
853977
if self._is_valid_regex(self.srcIPCombo.currentText()) == False:
854978
return False, QC.translate("rules", "Source IP regexp error")
855979

980+
elif "," in srcIPtext:
981+
ok, result = self._comma_to_regexp(srcIPtext, str)
982+
if ok:
983+
self.rule.operator.operand = Config.OPERAND_SOURCE_IP
984+
self.rule.operator.type = Config.RULE_TYPE_REGEXP
985+
srcIPtext = result
986+
else:
987+
return False, result
988+
856989
rule_data.append(
857990
{
858991
'type': self.rule.operator.type,
@@ -895,6 +1028,14 @@ def _save_rule(self):
8951028
self.rule.operator.type = Config.RULE_TYPE_REGEXP
8961029
if self._is_valid_regex(self.dstIPCombo.currentText()) == False:
8971030
return False, QC.translate("rules", "Dst IP regexp error")
1031+
elif "," in dstIPtext:
1032+
ok, result = self._comma_to_regexp(dstIPtext, str)
1033+
if ok:
1034+
self.rule.operator.operand = Config.OPERAND_DEST_IP
1035+
self.rule.operator.type = Config.RULE_TYPE_REGEXP
1036+
dstIPtext = result
1037+
else:
1038+
return False, result
8981039

8991040
rule_data.append(
9001041
{

0 commit comments

Comments
 (0)