]> git.ipfire.org Git - ipfire-2.x.git/blob - src/misc-progs/captivectrl.c
Merge branch 'next' of ssh://git.ipfire.org/pub/git/ipfire-2.x into next-suricata
[ipfire-2.x.git] / src / misc-progs / captivectrl.c
1 /* This file is part of the IPFire Firewall.
2 *
3 * This program is distributed under the terms of the GNU General Public
4 * Licence. See the file COPYING for details. */
5
6 #define _BSD_SOURCE
7 #define _XOPEN_SOURCE
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <time.h>
13
14 #include "libsmooth.h"
15 #include "setuid.h"
16
17 #define CAPTIVE_PORTAL_SETTINGS CONFIG_ROOT "/captive/settings"
18 #define ETHERNET_SETTINGS CONFIG_ROOT "/ethernet/settings"
19
20 #define CLIENTS CONFIG_ROOT "/captive/clients"
21 #define IPTABLES "/sbin/iptables --wait"
22 #define HTTP_PORT 80
23 #define REDIRECT_PORT 1013
24
25 typedef struct client {
26 char etheraddr[STRING_SIZE];
27 char ipaddr[STRING_SIZE];
28 time_t time_start;
29 int expires;
30
31 struct client* next;
32 } client_t;
33
34 static time_t parse_time(const char* s) {
35 int t = 0;
36
37 if (sscanf(s, "%d", &t) == 1) {
38 return (time_t)t;
39 }
40
41 return -1;
42 }
43
44 static char* format_time(const time_t* t) {
45 char buffer[STRING_SIZE];
46
47 struct tm* tm = gmtime(t);
48 if (tm == NULL)
49 return NULL;
50
51 strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M", tm);
52
53 return strdup(buffer);
54 }
55
56 static client_t* read_clients(char* filename) {
57 FILE* f = NULL;
58
59 if (!(f = fopen(filename, "r"))) {
60 fprintf(stderr, "Could not open configuration file: %s\n", filename);
61 return NULL;;
62 }
63
64 char line[STRING_SIZE];
65
66 client_t* client_first = NULL;
67 client_t* client_last = NULL;
68 client_t* client_curr;
69
70 while ((fgets(line, STRING_SIZE, f) != NULL)) {
71 if (line[strlen(line) - 1] == '\n')
72 line[strlen(line) - 1] = '\0';
73
74 // Skip all commented lines
75 if (*line == '#')
76 continue;
77
78 client_curr = (client_t*)malloc(sizeof(client_t));
79 memset(client_curr, 0, sizeof(client_t));
80
81 if (client_first == NULL)
82 client_first = client_curr;
83 else
84 client_last->next = client_curr;
85 client_last = client_curr;
86
87 unsigned int count = 0;
88 char* lineptr = line;
89 while (1) {
90 if (!*lineptr)
91 break;
92
93 char* word = lineptr;
94 while (*lineptr != '\0') {
95 if (*lineptr == ',') {
96 *lineptr = '\0';
97 lineptr++;
98 break;
99 }
100 lineptr++;
101 }
102
103 switch (count++) {
104 // Ethernet address
105 case 1:
106 strcpy(client_curr->etheraddr, word);
107 break;
108
109 // IP address
110 case 2:
111 strcpy(client_curr->ipaddr, word);
112 break;
113
114 // Start time
115 case 3:
116 client_curr->time_start = parse_time(word);
117 break;
118
119 // Expire duration
120 case 4:
121 client_curr->expires = atoi(word);
122 break;
123
124 default:
125 break;
126 }
127 }
128 }
129
130 if (f)
131 fclose(f);
132
133 return client_first;
134 }
135
136 static void flush_chains() {
137 // filter
138 safe_system(IPTABLES " -F CAPTIVE_PORTAL");
139 safe_system(IPTABLES " -F CAPTIVE_PORTAL_CLIENTS");
140
141 // nat
142 safe_system(IPTABLES " -t nat -F CAPTIVE_PORTAL");
143 }
144
145 static int setup_dns_filters() {
146 const char* protos[] = { "udp", "tcp", NULL };
147
148 // Limits the number of DNS requests to 3 kByte/s
149 // A burst of 1MB is permitted at the start
150 const char* limiter = "-m hashlimit --hashlimit-name dns-filter"
151 " --hashlimit-mode srcip --hashlimit-upto 3kb/sec --hashlimit-burst 1024kb";
152
153 char command[STRING_SIZE];
154
155 const char** proto = protos;
156 while (*proto) {
157 snprintf(command, sizeof(command), IPTABLES " -A CAPTIVE_PORTAL_CLIENTS -p %s"
158 " --dport 53 %s -j RETURN", *proto, limiter);
159
160 int r = safe_system(command);
161 if (r)
162 return r;
163
164 proto++;
165 }
166
167 return 0;
168 }
169
170 static int add_client_rules(const client_t* clients) {
171 char command[STRING_SIZE];
172 char match[STRING_SIZE];
173
174 while (clients) {
175 size_t len = 0;
176
177 if (*clients->ipaddr && clients->expires > 0) {
178 len += snprintf(match + len, sizeof(match) - len,
179 "-s %s", clients->ipaddr);
180 }
181
182 len += snprintf(match + len, sizeof(match) - len,
183 " -m mac --mac-source %s", clients->etheraddr);
184
185 if (clients->expires > 0) {
186 time_t expires = clients->time_start + clients->expires;
187
188 char* time_start = format_time(&clients->time_start);
189 char* time_end = format_time(&expires);
190
191 len += snprintf(match + len, sizeof(match) - len,
192 " -m time --datestart %s --datestop %s",
193 time_start, time_end);
194
195 free(time_start);
196 free(time_end);
197 }
198
199 // filter
200 snprintf(command, sizeof(command), IPTABLES " -A CAPTIVE_PORTAL_CLIENTS"
201 " %s -j RETURN", match);
202 safe_system(command);
203
204 // nat
205 snprintf(command, sizeof(command), IPTABLES " -t nat -A CAPTIVE_PORTAL"
206 " %s -j RETURN", match);
207 safe_system(command);
208
209 // Move on to the next client
210 clients = clients->next;
211 }
212
213 return 0;
214 }
215
216 static char* get_key(struct keyvalue* settings, char* key) {
217 char value[STRING_SIZE];
218
219 if (!findkey(settings, key, value))
220 return NULL;
221
222 return strdup(value);
223 }
224
225 static int add_interface_rule(const char* intf, int allow_webif_access) {
226 int r;
227 char command[STRING_SIZE];
228
229 if ((intf == NULL) || (strlen(intf) == 0)) {
230 fprintf(stderr, "Empty interface given\n");
231 return -1;
232 }
233
234 snprintf(command, sizeof(command), IPTABLES " -A CAPTIVE_PORTAL -i %s"
235 " -j CAPTIVE_PORTAL_CLIENTS", intf);
236 r = safe_system(command);
237 if (r)
238 return r;
239
240 if (allow_webif_access) {
241 snprintf(command, sizeof(command), IPTABLES " -A CAPTIVE_PORTAL_CLIENTS"
242 " -i %s -p tcp --dport 444 -j RETURN", intf);
243 r = safe_system(command);
244 if (r)
245 return r;
246 }
247
248 // Redirect all unauthenticated clients
249 snprintf(command, sizeof(command), IPTABLES " -t nat -A CAPTIVE_PORTAL -i %s"
250 " -p tcp --dport %d -j REDIRECT --to-ports %d", intf, HTTP_PORT, REDIRECT_PORT);
251 r = safe_system(command);
252 if (r)
253 return r;
254
255 // Allow access to captive portal site
256 snprintf(command, sizeof(command), IPTABLES " -A CAPTIVE_PORTAL_CLIENTS"
257 " -i %s -p tcp --dport %d -j RETURN", intf, REDIRECT_PORT);
258 r = safe_system(command);
259 if (r)
260 return r;
261
262 return 0;
263 }
264
265 static int add_interface_rules(struct keyvalue* captive_portal_settings, struct keyvalue* ethernet_settings) {
266 const char* intf;
267 char* setting;
268 int r = 0;
269
270 setting = get_key(captive_portal_settings, "ENABLE_GREEN");
271 if (setting && (strcmp(setting, "on") == 0)) {
272 free(setting);
273
274 intf = get_key(ethernet_settings, "GREEN_DEV");
275 r = add_interface_rule(intf, /* allow webif access from green */ 1);
276 if (r)
277 return r;
278 }
279
280 setting = get_key(captive_portal_settings, "ENABLE_BLUE");
281 if (setting && (strcmp(setting, "on") == 0)) {
282 free(setting);
283
284 intf = get_key(ethernet_settings, "BLUE_DEV");
285 r = add_interface_rule(intf, /* do not allow webif access */ 0);
286 if (r)
287 return r;
288 }
289
290 // Always pass DNS packets through all firewall rules
291 r = setup_dns_filters();
292 if (r)
293 return r;
294
295 // Add the last rule
296 r = safe_system(IPTABLES " -A CAPTIVE_PORTAL_CLIENTS -j DROP");
297 if (r)
298 return r;
299
300 return r;
301 }
302
303 int main(int argc, char** argv) {
304 int r = 0;
305 char* intf = NULL;
306 client_t* clients = NULL;
307
308 struct keyvalue* captive_portal_settings = NULL;
309 struct keyvalue* ethernet_settings = NULL;
310
311 if (!(initsetuid()))
312 exit(2);
313
314 ethernet_settings = initkeyvalues();
315 if (!readkeyvalues(ethernet_settings, ETHERNET_SETTINGS)) {
316 fprintf(stderr, "Could not read %s\n", ETHERNET_SETTINGS);
317 r = 1;
318 goto END;
319 }
320
321 captive_portal_settings = initkeyvalues();
322 if (!readkeyvalues(captive_portal_settings, CAPTIVE_PORTAL_SETTINGS)) {
323 fprintf(stderr, "Could not read %s\n", CAPTIVE_PORTAL_SETTINGS);
324 r = 1;
325 goto END;
326 }
327
328 clients = read_clients(CLIENTS);
329
330 // Clean up all old rules
331 flush_chains();
332
333 // Add all client rules
334 r = add_client_rules(clients);
335 if (r)
336 goto END;
337
338 // Add all interface rules
339 r = add_interface_rules(captive_portal_settings, ethernet_settings);
340 if (r)
341 goto END;
342
343 END:
344 while (clients) {
345 client_t* head = clients;
346 clients = clients->next;
347
348 free(head);
349 }
350
351 if (ethernet_settings)
352 freekeyvalues(ethernet_settings);
353
354 if (captive_portal_settings)
355 freekeyvalues(captive_portal_settings);
356
357 if (intf)
358 free(intf);
359
360 return r;
361 }