summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Cady <d@jerkface.net>2020-08-21 09:47:12 -0400
committerAndrew Cady <d@jerkface.net>2020-08-21 11:16:35 -0400
commit127d2c239bb4c8d156154c5fb87e082ef22ed5a4 (patch)
tree57c23475f529657254a2bc94724817ea57f78774
parent22fd38896ae61ba66a41d5f4d27f9e1096a7ca1e (diff)
Implement wildcard rules
The rules are reloaded as needed upon every connection.
-rw-r--r--main.c163
-rw-r--r--util.c50
-rw-r--r--util.h4
3 files changed, 118 insertions, 99 deletions
diff --git a/main.c b/main.c
index c109deb..7e38022 100644
--- a/main.c
+++ b/main.c
@@ -42,10 +42,11 @@ long int udp_end_port = 0;
42char config_path[500] = "/etc/tuntox/"; 42char config_path[500] = "/etc/tuntox/";
43 43
44/* Limit hostname and port in server */ 44/* Limit hostname and port in server */
45int nrules = 0; 45int tunnel_target_whitelist_size = 0;
46char rules_file[500] = "/etc/tuntox/rules"; 46char *tunnel_target_whitelist_file;
47bool enforce_whitelist = false; 47bool tunnel_target_whitelist_enforced = false;
48rule *rules = NULL; 48rule *tunnel_target_whitelist_rules = NULL;
49time_t tunnel_target_whitelist_mtime = 0;
49 50
50/* Ports and hostname for port forwarding */ 51/* Ports and hostname for port forwarding */
51int remote_port = 0; 52int remote_port = 0;
@@ -422,13 +423,16 @@ int handle_ping_frame(protocol_frame *rcvd_frame)
422 return 0; 423 return 0;
423} 424}
424 425
426void tunnel_target_whitelist_load();
425bool check_requested_tunnel_against_rules(char *hostname, in_port_t port) 427bool check_requested_tunnel_against_rules(char *hostname, in_port_t port)
426{ 428{
427 if (!enforce_whitelist) return true; 429 if (!tunnel_target_whitelist_enforced) return true;
428 430
429 if (nrules <= 0) 431 tunnel_target_whitelist_load();
432
433 if (tunnel_target_whitelist_size <= 0)
430 { 434 {
431 log_printf(l_warning, "filter option active but no allowed host/port. all requests will be dropped.\n"); 435 log_printf(L_WARNING, "Whitelist enforced, but no whitelisted entries. All requests will be dropped.\n");
432 return false; 436 return false;
433 } 437 }
434 438
@@ -436,11 +440,7 @@ bool check_requested_tunnel_against_rules(char *hostname, in_port_t port)
436 candidate.host = hostname; 440 candidate.host = hostname;
437 candidate.port = port; 441 candidate.port = port;
438 442
439 LL_SEARCH(rules, found, &candidate, rule_match); 443 LL_SEARCH(tunnel_target_whitelist_rules, found, &candidate, rule_match);
440 if(!found)
441 {
442 log_printf(L_WARNING, "Rejected, request not in rules\n");
443 }
444 return found; 444 return found;
445} 445}
446 446
@@ -473,6 +473,8 @@ int handle_request_tunnel_frame(protocol_frame *rcvd_frame)
473 473
474 if (!check_requested_tunnel_against_rules(hostname, port)) 474 if (!check_requested_tunnel_against_rules(hostname, port))
475 { 475 {
476 log_printf(L_WARNING, "Rejected tunnel request from #%d to non-whitelisted target host:port (%s:%d)",
477 rcvd_frame->friendnumber, hostname, port);
476 free(hostname); 478 free(hostname);
477 return -1; 479 return -1;
478 } 480 }
@@ -791,71 +793,89 @@ static size_t load_save(uint8_t **out_data)
791 } 793 }
792} 794}
793 795
796void tunnel_target_whitelist_clear();
794/* Loads a list of allowed hostnames and ports from file. Format is hostname:port*/ 797/* Loads a list of allowed hostnames and ports from file. Format is hostname:port*/
795void load_rules() 798void tunnel_target_whitelist_load()
796{ 799{
797 char *ahost=NULL; 800 char *ahost=NULL;
798 int aport=0; 801 int aport=0;
799 char line[100 + 1] = ""; 802 char line[1024];
800 FILE *file = NULL; 803 FILE *file = NULL;
801 rule *rule_obj = NULL; 804 rule *rule_obj = NULL;
802 int valid_rules = 0;
803 805
804 file = fopen(rules_file, "r"); 806 if (!tunnel_target_whitelist_enforced) return;
805
806 if (file == NULL) {
807 log_printf(L_WARNING, "Could not open rules file (%s)\n", rules_file);
808 return;
809 }
810 807
811 while (fgets(line, sizeof(line), file)) { 808 /* If we have existing rules, check to see if we need to continue. */
812 /* allow comments & white lines */ 809 if(tunnel_target_whitelist_rules)
813 if (line[0]=='#'||line[0]=='\n') { 810 {
814 continue; 811 struct stat buf;
812 if (stat(tunnel_target_whitelist_file, &buf) < 0)
813 {
814 /* File removed? Better clear the whitelist. */
815 tunnel_target_whitelist_mtime = 0;
816 tunnel_target_whitelist_clear();
817 return;
815 } 818 }
816 if (parse_pipe_port_forward(line, &ahost, &aport) >= 0) { 819 if (buf.st_mtime == tunnel_target_whitelist_mtime) return;
817 if (aport > 0 && aport < 65535) {
818 820
819 rule_obj = (rule *)calloc(sizeof(rule), 1); 821 tunnel_target_whitelist_mtime = buf.st_mtime;
820 if(!rule_obj) 822 tunnel_target_whitelist_clear();
821 { 823 }
822 log_printf(L_ERROR, "Could not allocate memory for rule");
823 exit(1);
824 }
825 824
826 rule_obj->port = aport; 825 file = fopen(tunnel_target_whitelist_file, "r");
827 rule_obj->host = strdup(ahost);
828 826
829 LL_APPEND(rules, rule_obj); 827 if(file == NULL) {
830 valid_rules++; 828 log_printf(L_WARNING, "Could not open rules file (%s)\n", tunnel_target_whitelist_file);
831 } else { 829 return;
832 log_printf(L_WARNING, "Invalid port in line: %s\n", line); 830 }
831
832 while(fgets(line, sizeof(line), file))
833 {
834 strtok(line, "#\n"); /* Chop line at first hash */
835 char *orig_line = strdup(line); /* Not quite the original line; keeps
836 newline and comments out of logs */
837 if(parse_pipe_port_forward(line, &ahost, &aport))
838 {
839 rule_obj = (rule *)calloc(sizeof(rule), 1);
840 if(!rule_obj)
841 {
842 log_printf(L_ERROR, "Could not allocate memory for rule");
843 exit(1);
833 } 844 }
834 } else { 845
835 log_printf(L_WARNING, "Could not parse line: %s\n", line); 846 rule_obj->port = aport;
847 rule_obj->host = strdup(ahost);
848
849 LL_APPEND(tunnel_target_whitelist_rules, rule_obj);
850 tunnel_target_whitelist_size++;
851 }
852 else
853 {
854 log_printf(L_WARNING, "Could not parse line: %s\n", orig_line);
836 } 855 }
856 free(orig_line);
837 } 857 }
838 fclose(file); 858 fclose(file);
839 859
840 /* save valid rules in global variable */ 860 log_printf(L_INFO, "Loaded %d rules\n", tunnel_target_whitelist_size);
841 nrules = valid_rules; 861 if (tunnel_target_whitelist_size == 0 && tunnel_target_whitelist_enforced){
842
843 log_printf(L_INFO, "Loaded %d rules\n", nrules);
844 if (nrules==0 && enforce_whitelist){
845 log_printf(L_WARNING, "No rules loaded! NO CONNECTIONS WILL BE ALLOWED!\n"); 862 log_printf(L_WARNING, "No rules loaded! NO CONNECTIONS WILL BE ALLOWED!\n");
846 } 863 }
847} 864}
848 865
849/* Clear rules loaded into memory */ 866/* Clear rules loaded into memory */
850void clear_rules() 867void tunnel_target_whitelist_clear()
851{ 868{
852 rule * elt, *tmp; 869 rule * elt, *tmp;
853 /* delete each elemen using the safe iterator */ 870 /* delete each elemen using the safe iterator */
854 LL_FOREACH_SAFE(rules,elt,tmp) { 871 LL_FOREACH_SAFE(tunnel_target_whitelist_rules,elt,tmp) {
855 LL_DELETE(rules,elt); 872 LL_DELETE(tunnel_target_whitelist_rules,elt);
856 free(elt->host); 873 free(elt->host);
857 free(elt); 874 free(elt);
858 } 875 }
876 free(tunnel_target_whitelist_rules);
877 tunnel_target_whitelist_rules = NULL;
878 tunnel_target_whitelist_size = 0;
859} 879}
860 880
861void accept_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *userdata) 881void accept_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *userdata)
@@ -914,12 +934,17 @@ void accept_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *m
914} 934}
915 935
916/* Callback for tox_callback_self_connection_status() */ 936/* Callback for tox_callback_self_connection_status() */
917void handle_connection_status_change(Tox *tox, TOX_CONNECTION p_connection_status, void *user_data) 937void handle_connection_status_change(Tox *tox, TOX_CONNECTION new_connection_status, void *user_data)
918{ 938{
919 const char *status = NULL; 939 connection_status = new_connection_status;
920 connection_status = p_connection_status; 940 if (connection_status)
921 status = readable_connection_status(connection_status); 941 {
922 log_printf(L_INFO, "Connection status changed: %s", status); 942 log_printf(L_INFO, "Connected to Tox network: %s\n", readable_connection_status(connection_status));
943 }
944 else
945 {
946 log_printf(L_INFO, "Disconnected from Tox network\n");
947 }
923} 948}
924 949
925#ifdef LOG_IP_ADDRESS 950#ifdef LOG_IP_ADDRESS
@@ -971,7 +996,6 @@ int do_server_loop()
971 unsigned char tox_packet_buf[PROTOCOL_MAX_PACKET_SIZE]; 996 unsigned char tox_packet_buf[PROTOCOL_MAX_PACKET_SIZE];
972 tunnel *tun = NULL; 997 tunnel *tun = NULL;
973 tunnel *tmp = NULL; 998 tunnel *tmp = NULL;
974 TOX_CONNECTION connected = 0;
975 int sent_data = 0; 999 int sent_data = 0;
976 1000
977 tox_callback_friend_lossless_packet(tox, parse_lossless_packet); 1001 tox_callback_friend_lossless_packet(tox, parse_lossless_packet);
@@ -997,20 +1021,6 @@ int do_server_loop()
997 log_printf(L_DEBUG2, "Iteration interval: %dms\n", tox_do_interval_ms); 1021 log_printf(L_DEBUG2, "Iteration interval: %dms\n", tox_do_interval_ms);
998 gettimeofday(&tv_start, NULL); 1022 gettimeofday(&tv_start, NULL);
999 1023
1000 /* Check change in connection state */
1001 if(connection_status != connected)
1002 {
1003 connected = connection_status;
1004 if(connected)
1005 {
1006 log_printf(L_DEBUG, "Connected to Tox network\n");
1007 }
1008 else
1009 {
1010 log_printf(L_DEBUG, "Disconnected from Tox network\n");
1011 }
1012 }
1013
1014 fds = master_server_fds; 1024 fds = master_server_fds;
1015 1025
1016 /* Poll for data from our client connection */ 1026 /* Poll for data from our client connection */
@@ -1304,7 +1314,7 @@ int main(int argc, char *argv[])
1304 /* Local port forwarding */ 1314 /* Local port forwarding */
1305 client_mode = 1; 1315 client_mode = 1;
1306 client_local_port_mode = 1; 1316 client_local_port_mode = 1;
1307 if(parse_local_port_forward(optarg, &local_port, &remote_host, &remote_port) < 0) 1317 if(!parse_local_port_forward(optarg, &local_port, &remote_host, &remote_port))
1308 { 1318 {
1309 log_printf(L_ERROR, "Invalid value for -L option - use something like -L 22:127.0.0.1:22\n"); 1319 log_printf(L_ERROR, "Invalid value for -L option - use something like -L 22:127.0.0.1:22\n");
1310 exit(1); 1320 exit(1);
@@ -1319,7 +1329,7 @@ int main(int argc, char *argv[])
1319 /* Pipe forwarding */ 1329 /* Pipe forwarding */
1320 client_mode = 1; 1330 client_mode = 1;
1321 client_pipe_mode = 1; 1331 client_pipe_mode = 1;
1322 if(parse_pipe_port_forward(optarg, &remote_host, &remote_port) < 0) 1332 if(!parse_pipe_port_forward(optarg, &remote_host, &remote_port) || remote_port == 0)
1323 { 1333 {
1324 log_printf(L_ERROR, "Invalid value for -W option - use something like -W 127.0.0.1:22\n"); 1334 log_printf(L_ERROR, "Invalid value for -W option - use something like -W 127.0.0.1:22\n");
1325 exit(1); 1335 exit(1);
@@ -1369,9 +1379,9 @@ int main(int argc, char *argv[])
1369 load_saved_toxid_in_client_mode = 1; 1379 load_saved_toxid_in_client_mode = 1;
1370 break; 1380 break;
1371 case 'f': 1381 case 'f':
1372 strncpy(rules_file, optarg, sizeof(rules_file) - 1); 1382 tunnel_target_whitelist_file = strdup(optarg);
1373 enforce_whitelist = true; 1383 tunnel_target_whitelist_enforced = true;
1374 log_printf(L_INFO, "Filter policy set to VALIDATE\n"); 1384 log_printf(L_INFO, "Whitelist enforced on outgoing connections: only matched hosts/ports will be allowed.\n");
1375 break; 1385 break;
1376 case 's': 1386 case 's':
1377 /* Shared secret */ 1387 /* Shared secret */
@@ -1471,9 +1481,9 @@ int main(int argc, char *argv[])
1471 log_printf(L_INFO, "Server in ToxID whitelisting mode - only clients listed with -i can connect"); 1481 log_printf(L_INFO, "Server in ToxID whitelisting mode - only clients listed with -i can connect");
1472 } 1482 }
1473 1483
1474 if((!client_mode) && enforce_whitelist) 1484 if(!client_mode && tunnel_target_whitelist_enforced)
1475 { 1485 {
1476 load_rules(); 1486 tunnel_target_whitelist_load();
1477 } 1487 }
1478 1488
1479 /* If shared secret has not been provided via -s, read from TUNTOX_SHARED_SECRET env variable */ 1489 /* If shared secret has not been provided via -s, read from TUNTOX_SHARED_SECRET env variable */
@@ -1589,7 +1599,6 @@ int main(int argc, char *argv[])
1589 { 1599 {
1590 tox_callback_friend_request(tox, accept_friend_request); 1600 tox_callback_friend_request(tox, accept_friend_request);
1591 do_server_loop(); 1601 do_server_loop();
1592 clear_rules();
1593 } 1602 }
1594 } 1603 }
1595 1604
diff --git a/util.c b/util.c
index 0084c19..03cf30f 100644
--- a/util.c
+++ b/util.c
@@ -83,52 +83,62 @@ int string_to_id(char_t *w, char_t *a)
83} 83}
84 84
85/* Parse the -L parameter */ 85/* Parse the -L parameter */
86/* 0 = success */ 86/* true = success */
87int parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port) 87bool parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port)
88{ 88{
89 char *lport; 89 char *lport;
90 char *host;
91 char *rport;
92 90
93 /* Alternative delimiter '@', as ':' is forbidden in some environments */ 91 /* Alternative delimiter '@', as ':' is forbidden in some environments */
94
95 lport = strtok(string, ":@"); 92 lport = strtok(string, ":@");
96 host = strtok(NULL, ":@");
97 rport = strtok(NULL, ":@");
98 93
99 if(!lport || !host || !rport) 94 if(!(*local_port = atoi(lport)))
100 { 95 {
101 return -1; 96 return false;
102 } 97 }
103 98
104 *local_port = atoi(lport); 99 if(parse_pipe_port_forward(lport + strlen(lport), hostname, remote_port))
105 *hostname = host; 100 {
106 *remote_port = atoi(rport); 101 return *remote_port;
107 102 }
108 return 0; 103 return false;
109} 104}
110 105
111/* Parse the -W parameter */ 106/* Parse the -W parameter */
112/* 0 = success */ 107/* true = success */
113int parse_pipe_port_forward(char *string, char **hostname, int *remote_port) 108bool parse_pipe_port_forward(char *string, char **hostname, int *remote_port)
114{ 109{
115 char *host; 110 char *host;
116 char *rport; 111 char *rport;
117 112
118 /* Alternative delimiter '@', as ':' is forbidden in some environments */ 113 /* Alternative delimiter '@', as ':' is forbidden in some environments */
119
120 host = strtok(string, ":@"); 114 host = strtok(string, ":@");
121 rport = strtok(NULL, ":@"); 115 rport = strtok(NULL, "");
122 116
123 if(!host || !rport) 117 if(!host || !rport)
124 { 118 {
125 return -1; 119 return false;
126 } 120 }
127 121
128 *hostname = host; 122 *hostname = host;
129 *remote_port = atoi(rport); 123 *remote_port = atoi(rport);
130 124
131 return 0; 125 if(*remote_port > 0 && *remote_port < 65535)
126 {
127 /* This is tolerant of nonsense tokens after the port. */
128 return true;
129 }
130 else
131 {
132 /* Port 0 is not allowed in the input. Only a literal '*' can produce a
133 * port 0 in the output, which will be treated as a wildcard if this is
134 * a rule. */
135 if (rport[0] != '*')
136 {
137 return false;
138 }
139 /* Return an error if an extra token follows, but tolerate whitespace. */
140 return !strtok(rport+1, "\n\t ");
141 }
132} 142}
133 143
134void* file_raw(char *path, uint32_t *size) 144void* file_raw(char *path, uint32_t *size)
diff --git a/util.h b/util.h
index 7dced6b..e97119b 100644
--- a/util.h
+++ b/util.h
@@ -13,7 +13,7 @@ void id_to_string(char_t *dest, const char_t *src);
13int string_to_id(char_t *w, char_t *a); 13int string_to_id(char_t *w, char_t *a);
14void* file_raw(char *path, uint32_t *size); 14void* file_raw(char *path, uint32_t *size);
15const char *readable_connection_status(TOX_CONNECTION status); 15const char *readable_connection_status(TOX_CONNECTION status);
16int parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port); 16bool parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port);
17int parse_pipe_port_forward(char *string, char **hostname, int *remote_port); 17bool parse_pipe_port_forward(char *string, char **hostname, int *remote_port);
18 18
19#endif 19#endif