diff options
author | Andrew Cady <d@jerkface.net> | 2020-08-21 09:47:12 -0400 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2020-08-21 11:16:35 -0400 |
commit | 127d2c239bb4c8d156154c5fb87e082ef22ed5a4 (patch) | |
tree | 57c23475f529657254a2bc94724817ea57f78774 | |
parent | 22fd38896ae61ba66a41d5f4d27f9e1096a7ca1e (diff) |
Implement wildcard rules
The rules are reloaded as needed upon every connection.
-rw-r--r-- | main.c | 163 | ||||
-rw-r--r-- | util.c | 50 | ||||
-rw-r--r-- | util.h | 4 |
3 files changed, 118 insertions, 99 deletions
@@ -42,10 +42,11 @@ long int udp_end_port = 0; | |||
42 | char config_path[500] = "/etc/tuntox/"; | 42 | char config_path[500] = "/etc/tuntox/"; |
43 | 43 | ||
44 | /* Limit hostname and port in server */ | 44 | /* Limit hostname and port in server */ |
45 | int nrules = 0; | 45 | int tunnel_target_whitelist_size = 0; |
46 | char rules_file[500] = "/etc/tuntox/rules"; | 46 | char *tunnel_target_whitelist_file; |
47 | bool enforce_whitelist = false; | 47 | bool tunnel_target_whitelist_enforced = false; |
48 | rule *rules = NULL; | 48 | rule *tunnel_target_whitelist_rules = NULL; |
49 | time_t tunnel_target_whitelist_mtime = 0; | ||
49 | 50 | ||
50 | /* Ports and hostname for port forwarding */ | 51 | /* Ports and hostname for port forwarding */ |
51 | int remote_port = 0; | 52 | int 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 | ||
426 | void tunnel_target_whitelist_load(); | ||
425 | bool check_requested_tunnel_against_rules(char *hostname, in_port_t port) | 427 | bool 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 | ||
796 | void 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*/ |
795 | void load_rules() | 798 | void 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 */ |
850 | void clear_rules() | 867 | void 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 | ||
861 | void accept_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *userdata) | 881 | void 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() */ |
917 | void handle_connection_status_change(Tox *tox, TOX_CONNECTION p_connection_status, void *user_data) | 937 | void 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 | ||
@@ -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 */ |
87 | int parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port) | 87 | bool 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 */ |
113 | int parse_pipe_port_forward(char *string, char **hostname, int *remote_port) | 108 | bool 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 | ||
134 | void* file_raw(char *path, uint32_t *size) | 144 | void* file_raw(char *path, uint32_t *size) |
@@ -13,7 +13,7 @@ void id_to_string(char_t *dest, const char_t *src); | |||
13 | int string_to_id(char_t *w, char_t *a); | 13 | int string_to_id(char_t *w, char_t *a); |
14 | void* file_raw(char *path, uint32_t *size); | 14 | void* file_raw(char *path, uint32_t *size); |
15 | const char *readable_connection_status(TOX_CONNECTION status); | 15 | const char *readable_connection_status(TOX_CONNECTION status); |
16 | int parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port); | 16 | bool parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port); |
17 | int parse_pipe_port_forward(char *string, char **hostname, int *remote_port); | 17 | bool parse_pipe_port_forward(char *string, char **hostname, int *remote_port); |
18 | 18 | ||
19 | #endif | 19 | #endif |