| [9] | 1 | /********************************************************************\ |
|---|
| 2 | * This program is free software; you can redistribute it and/or * |
|---|
| 3 | * modify it under the terms of the GNU General Public License as * |
|---|
| 4 | * published by the Free Software Foundation; either version 2 of * |
|---|
| 5 | * the License, or (at your option) any later version. * |
|---|
| 6 | * * |
|---|
| 7 | * This program is distributed in the hope that it will be useful, * |
|---|
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
|---|
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
|---|
| 10 | * GNU General Public License for more details. * |
|---|
| 11 | * * |
|---|
| 12 | * You should have received a copy of the GNU General Public License* |
|---|
| 13 | * along with this program; if not, contact: * |
|---|
| 14 | * * |
|---|
| 15 | * Free Software Foundation Voice: +1-617-542-5942 * |
|---|
| 16 | * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * |
|---|
| 17 | * Boston, MA 02111-1307, USA gnu@gnu.org * |
|---|
| 18 | * * |
|---|
| [34] | 19 | \********************************************************************/ |
|---|
| [9] | 20 | |
|---|
| [100] | 21 | /* |
|---|
| 22 | * $Header: /cvsroot/wifidog/wifidog/src/firewall.c,v 1.32 2004/04/23 |
|---|
| 23 | * 11:37:43 aprilp Exp $ |
|---|
| 24 | */ |
|---|
| [9] | 25 | /** @internal |
|---|
| [34] | 26 | @file firewall.c |
|---|
| 27 | @brief Firewall update functions |
|---|
| 28 | @author Copyright (C) 2004 Philippe April <papril777@yahoo.com> |
|---|
| 29 | */ |
|---|
| [9] | 30 | |
|---|
| [119] | 31 | #define _GNU_SOURCE |
|---|
| [9] | 32 | |
|---|
| [119] | 33 | #include <stdio.h> |
|---|
| 34 | #include <stdlib.h> |
|---|
| 35 | #include <syslog.h> |
|---|
| 36 | #include <errno.h> |
|---|
| 37 | #include <pthread.h> |
|---|
| 38 | #include <sys/wait.h> |
|---|
| 39 | #include <sys/types.h> |
|---|
| 40 | #include <sys/unistd.h> |
|---|
| 41 | |
|---|
| 42 | #include <string.h> |
|---|
| 43 | |
|---|
| [422] | 44 | #include <sys/socket.h> |
|---|
| 45 | #include <netinet/in.h> |
|---|
| 46 | #include <arpa/inet.h> |
|---|
| 47 | #include <unistd.h> |
|---|
| 48 | #include <net/ethernet.h> |
|---|
| 49 | #include <netinet/ip.h> |
|---|
| 50 | #include <netinet/ip_icmp.h> |
|---|
| 51 | #include <netpacket/packet.h> |
|---|
| 52 | #include <sys/uio.h> |
|---|
| 53 | #include <fcntl.h> |
|---|
| 54 | #include <netdb.h> |
|---|
| 55 | #include <sys/time.h> |
|---|
| 56 | |
|---|
| [274] | 57 | #include "httpd.h" |
|---|
| 58 | |
|---|
| [119] | 59 | #include "debug.h" |
|---|
| 60 | #include "conf.h" |
|---|
| 61 | #include "firewall.h" |
|---|
| [170] | 62 | #include "fw_iptables.h" |
|---|
| [119] | 63 | #include "auth.h" |
|---|
| 64 | #include "centralserver.h" |
|---|
| [135] | 65 | #include "client_list.h" |
|---|
| [119] | 66 | |
|---|
| [135] | 67 | extern pthread_mutex_t client_list_mutex; |
|---|
| [72] | 68 | |
|---|
| [422] | 69 | int icmp_fd = 0; |
|---|
| 70 | |
|---|
| [75] | 71 | /** |
|---|
| [136] | 72 | * Allow a client access through the firewall by adding a rule in the firewall to MARK the user's packets with the proper |
|---|
| [109] | 73 | * rule by providing his IP and MAC address |
|---|
| [95] | 74 | * @param ip IP address to allow |
|---|
| 75 | * @param mac MAC address to allow |
|---|
| [135] | 76 | * @param fw_connection_state fw_connection_state Tag |
|---|
| [95] | 77 | * @return Return code of the command |
|---|
| [75] | 78 | */ |
|---|
| [35] | 79 | int |
|---|
| [135] | 80 | fw_allow(char *ip, char *mac, int fw_connection_state) |
|---|
| [9] | 81 | { |
|---|
| [135] | 82 | debug(LOG_DEBUG, "Allowing %s %s with fw_connection_state %d", ip, mac, fw_connection_state); |
|---|
| [107] | 83 | |
|---|
| [135] | 84 | return iptables_fw_access(FW_ACCESS_ALLOW, ip, mac, fw_connection_state); |
|---|
| [9] | 85 | } |
|---|
| 86 | |
|---|
| [75] | 87 | /** |
|---|
| [136] | 88 | * @brief Deny a client access through the firewall by removing the rule in the firewall that was fw_connection_stateging the user's traffic |
|---|
| [95] | 89 | * @param ip IP address to deny |
|---|
| 90 | * @param mac MAC address to deny |
|---|
| [135] | 91 | * @param fw_connection_state fw_connection_state Tag |
|---|
| [95] | 92 | * @return Return code of the command |
|---|
| [75] | 93 | */ |
|---|
| [35] | 94 | int |
|---|
| [135] | 95 | fw_deny(char *ip, char *mac, int fw_connection_state) |
|---|
| [9] | 96 | { |
|---|
| [135] | 97 | debug(LOG_DEBUG, "Denying %s %s with fw_connection_state %d", ip, mac, fw_connection_state); |
|---|
| [107] | 98 | |
|---|
| [135] | 99 | return iptables_fw_access(FW_ACCESS_DENY, ip, mac, fw_connection_state); |
|---|
| [9] | 100 | } |
|---|
| 101 | |
|---|
| [75] | 102 | /** |
|---|
| [136] | 103 | * Get an IP's MAC address from the ARP cache. |
|---|
| [75] | 104 | * Go through all the entries in /proc/net/arp until we find the requested |
|---|
| 105 | * IP address and return the MAC address bound to it. |
|---|
| [95] | 106 | * @todo Make this function portable (using shell scripts?) |
|---|
| [75] | 107 | */ |
|---|
| [100] | 108 | char * |
|---|
| [9] | 109 | arp_get(char *req_ip) |
|---|
| 110 | { |
|---|
| [100] | 111 | FILE *proc; |
|---|
| [116] | 112 | char *ip, *mac; |
|---|
| [449] | 113 | char * reply; |
|---|
| [9] | 114 | |
|---|
| [100] | 115 | if (!(proc = fopen("/proc/net/arp", "r"))) { |
|---|
| 116 | return NULL; |
|---|
| 117 | } |
|---|
| [449] | 118 | if (!(ip = malloc(16))) { |
|---|
| 119 | debug(LOG_CRIT, "Cannot allocate 16 bytes of memory for IP, Banzai!"); |
|---|
| 120 | exit(1); |
|---|
| 121 | } |
|---|
| 122 | if (!(mac = malloc(18))) { |
|---|
| 123 | debug(LOG_CRIT, "Cannot allocate 18 bytes of memory for MAC, Banzai!"); |
|---|
| 124 | exit(1); |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| [100] | 127 | /* Skip first line */ |
|---|
| [449] | 128 | while (!feof(proc) && fgetc(proc) != '\n'); |
|---|
| 129 | |
|---|
| 130 | /* Find ip, put mac in reply */ |
|---|
| 131 | reply = NULL; |
|---|
| 132 | while (!feof(proc) && (fscanf(proc, " %15[0-9.] %*s %*s %17[A-F0-9:] %*s %*s", ip, mac) == 2)) { |
|---|
| 133 | if (strcmp(ip, req_ip) == 0) { |
|---|
| 134 | reply = mac; |
|---|
| 135 | break; |
|---|
| 136 | } |
|---|
| [100] | 137 | } |
|---|
| [28] | 138 | |
|---|
| [116] | 139 | free(ip); |
|---|
| [449] | 140 | if (!reply) |
|---|
| 141 | free(mac); |
|---|
| [9] | 142 | |
|---|
| [449] | 143 | fclose(proc); |
|---|
| 144 | |
|---|
| 145 | return reply; |
|---|
| [9] | 146 | } |
|---|
| 147 | |
|---|
| [136] | 148 | /** Initialize the firewall rules |
|---|
| [75] | 149 | */ |
|---|
| [35] | 150 | int |
|---|
| [9] | 151 | fw_init(void) |
|---|
| 152 | { |
|---|
| [422] | 153 | int flags, oneopt = 1, zeroopt = 0; |
|---|
| 154 | |
|---|
| 155 | debug(LOG_INFO, "Creating ICMP socket"); |
|---|
| 156 | if ((icmp_fd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1 || |
|---|
| 157 | (flags = fcntl(icmp_fd, F_GETFL, 0)) == -1 || |
|---|
| 158 | fcntl(icmp_fd, F_SETFL, flags | O_NONBLOCK) == -1 || |
|---|
| 159 | setsockopt(icmp_fd, SOL_SOCKET, SO_RCVBUF, &oneopt, sizeof(oneopt)) || |
|---|
| 160 | setsockopt(icmp_fd, SOL_SOCKET, SO_DONTROUTE, &zeroopt, sizeof(zeroopt)) == -1) { |
|---|
| 161 | debug(LOG_ERR, "Cannot create ICMP raw socket."); |
|---|
| 162 | return; |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| [101] | 165 | debug(LOG_INFO, "Initializing Firewall"); |
|---|
| [135] | 166 | return iptables_fw_init(); |
|---|
| [9] | 167 | } |
|---|
| 168 | |
|---|
| [277] | 169 | /** Clear the authserver rules |
|---|
| 170 | */ |
|---|
| 171 | void |
|---|
| 172 | fw_clear_authservers(void) |
|---|
| 173 | { |
|---|
| 174 | debug(LOG_INFO, "Clearing the authservers list"); |
|---|
| 175 | iptables_fw_clear_authservers(); |
|---|
| 176 | } |
|---|
| 177 | |
|---|
| 178 | /** Set the authservers rules |
|---|
| 179 | */ |
|---|
| 180 | void |
|---|
| 181 | fw_set_authservers(void) |
|---|
| 182 | { |
|---|
| 183 | debug(LOG_INFO, "Setting the authservers list"); |
|---|
| 184 | iptables_fw_set_authservers(); |
|---|
| 185 | } |
|---|
| 186 | |
|---|
| [136] | 187 | /** Remove the firewall rules |
|---|
| [75] | 188 | * This is used when we do a clean shutdown of WiFiDog. |
|---|
| [95] | 189 | * @return Return code of the fw.destroy script |
|---|
| [75] | 190 | */ |
|---|
| [35] | 191 | int |
|---|
| [9] | 192 | fw_destroy(void) |
|---|
| 193 | { |
|---|
| [422] | 194 | if (icmp_fd != 0) { |
|---|
| 195 | debug(LOG_INFO, "Closing ICMP socket"); |
|---|
| 196 | close(icmp_fd); |
|---|
| 197 | } |
|---|
| 198 | |
|---|
| [101] | 199 | debug(LOG_INFO, "Removing Firewall rules"); |
|---|
| [135] | 200 | return iptables_fw_destroy(); |
|---|
| [9] | 201 | } |
|---|
| 202 | |
|---|
| [136] | 203 | /**Probably a misnomer, this function actually refreshes the entire client list's traffic counter, re-authenticates every client with the central server and update's the central servers traffic counters and notifies it if a client has logged-out. |
|---|
| [95] | 204 | * @todo Make this function smaller and use sub-fonctions |
|---|
| 205 | */ |
|---|
| [35] | 206 | void |
|---|
| [9] | 207 | fw_counter(void) |
|---|
| 208 | { |
|---|
| [100] | 209 | t_authresponse authresponse; |
|---|
| [126] | 210 | char *token, *ip, *mac; |
|---|
| [135] | 211 | t_client *p1, *p2; |
|---|
| 212 | long long incoming, outgoing; |
|---|
| [136] | 213 | s_config *config = config_get_config(); |
|---|
| [9] | 214 | |
|---|
| [135] | 215 | if (-1 == iptables_fw_counters_update()) { |
|---|
| [121] | 216 | debug(LOG_ERR, "Could not get counters from firewall!"); |
|---|
| [113] | 217 | return; |
|---|
| 218 | } |
|---|
| [18] | 219 | |
|---|
| [256] | 220 | LOCK_CLIENT_LIST(); |
|---|
| [136] | 221 | |
|---|
| 222 | for (p1 = p2 = client_get_first_client(); NULL != p1; p1 = p2) { |
|---|
| [121] | 223 | p2 = p1->next; |
|---|
| [126] | 224 | |
|---|
| [121] | 225 | ip = strdup(p1->ip); |
|---|
| 226 | token = strdup(p1->token); |
|---|
| [126] | 227 | mac = strdup(p1->mac); |
|---|
| [422] | 228 | outgoing = p1->counters.outgoing; |
|---|
| 229 | incoming = p1->counters.incoming; |
|---|
| [126] | 230 | |
|---|
| [422] | 231 | UNLOCK_CLIENT_LIST(); |
|---|
| 232 | /* Ping the client, if he responds it'll keep activity on the link */ |
|---|
| 233 | icmp_ping(ip); |
|---|
| 234 | /* Update the counters on the remote server */ |
|---|
| [135] | 235 | auth_server_request(&authresponse, REQUEST_TYPE_COUNTERS, ip, mac, token, incoming, outgoing); |
|---|
| [422] | 236 | LOCK_CLIENT_LIST(); |
|---|
| [256] | 237 | |
|---|
| [135] | 238 | if (!(p1 = client_list_find(ip, mac))) { |
|---|
| [121] | 239 | debug(LOG_ERR, "Node %s was freed while being re-validated!", ip); |
|---|
| 240 | } else { |
|---|
| 241 | if (p1->counters.last_updated + |
|---|
| [136] | 242 | (config->checkinterval * config->clienttimeout) |
|---|
| [135] | 243 | <= time(NULL)) { |
|---|
| [121] | 244 | /* Timing out user */ |
|---|
| [136] | 245 | debug(LOG_INFO, "%s - Inactive for %ld seconds, removing client and denying in firewall", p1->ip, config->checkinterval * config->clienttimeout); |
|---|
| [135] | 246 | fw_deny(p1->ip, p1->mac, p1->fw_connection_state); |
|---|
| 247 | client_list_delete(p1); |
|---|
| [126] | 248 | |
|---|
| 249 | /* Advertise the logout */ |
|---|
| [256] | 250 | UNLOCK_CLIENT_LIST(); |
|---|
| [135] | 251 | auth_server_request(&authresponse, REQUEST_TYPE_LOGOUT, ip, mac, token, 0, 0); |
|---|
| [256] | 252 | LOCK_CLIENT_LIST(); |
|---|
| [121] | 253 | } else { |
|---|
| 254 | /* |
|---|
| 255 | * This handles any change in |
|---|
| 256 | * the status this allows us |
|---|
| 257 | * to change the status of a |
|---|
| 258 | * user while he's connected |
|---|
| 259 | */ |
|---|
| 260 | switch (authresponse.authcode) { |
|---|
| 261 | case AUTH_DENIED: |
|---|
| [72] | 262 | |
|---|
| [121] | 263 | case AUTH_VALIDATION_FAILED: |
|---|
| [135] | 264 | debug(LOG_NOTICE, "%s - Validation timeout, now denied. Removing client and firewall rules", p1->ip); |
|---|
| 265 | fw_deny(p1->ip, p1->mac, p1->fw_connection_state); |
|---|
| 266 | client_list_delete(p1); |
|---|
| [121] | 267 | break; |
|---|
| [95] | 268 | |
|---|
| [121] | 269 | case AUTH_ALLOWED: |
|---|
| [135] | 270 | if (p1->fw_connection_state != FW_MARK_KNOWN) { |
|---|
| [121] | 271 | debug(LOG_INFO, "%s - Access has changed, refreshing firewall and clearing counters", p1->ip); |
|---|
| [135] | 272 | fw_deny(p1->ip, p1->mac, p1->fw_connection_state); |
|---|
| 273 | p1->fw_connection_state = FW_MARK_KNOWN; |
|---|
| [422] | 274 | p1->counters.togateway = p1->counters.incoming = p1->counters.outgoing = 0; |
|---|
| [135] | 275 | fw_allow(p1->ip, p1->mac, p1->fw_connection_state); |
|---|
| [121] | 276 | } |
|---|
| 277 | break; |
|---|
| [113] | 278 | |
|---|
| [121] | 279 | case AUTH_VALIDATION: |
|---|
| 280 | /* |
|---|
| 281 | * Do nothing, user |
|---|
| 282 | * is in validation |
|---|
| 283 | * period |
|---|
| 284 | */ |
|---|
| 285 | debug(LOG_INFO, "%s - User in validation period", p1->ip); |
|---|
| 286 | break; |
|---|
| [113] | 287 | |
|---|
| [121] | 288 | default: |
|---|
| 289 | debug(LOG_DEBUG, "I do not know about authentication code %d", authresponse.authcode); |
|---|
| 290 | break; |
|---|
| [113] | 291 | } |
|---|
| [121] | 292 | } |
|---|
| 293 | } |
|---|
| [113] | 294 | |
|---|
| [121] | 295 | free(token); |
|---|
| 296 | free(ip); |
|---|
| [127] | 297 | free(mac); |
|---|
| [121] | 298 | } |
|---|
| [256] | 299 | UNLOCK_CLIENT_LIST(); |
|---|
| [9] | 300 | } |
|---|
| [422] | 301 | |
|---|
| 302 | void icmp_ping(char *host) { |
|---|
| 303 | struct sockaddr_in saddr; |
|---|
| 304 | struct { |
|---|
| 305 | struct ip ip; |
|---|
| 306 | struct icmp icmp; |
|---|
| 307 | } packet; |
|---|
| 308 | unsigned int i, j; |
|---|
| 309 | int opt = 2000; |
|---|
| 310 | unsigned short id = rand16(); |
|---|
| 311 | |
|---|
| 312 | saddr.sin_family = AF_INET; |
|---|
| 313 | saddr.sin_port = 0; |
|---|
| 314 | inet_aton(host, &saddr.sin_addr); |
|---|
| 315 | #ifdef HAVE_SOCKADDR_SA_LEN |
|---|
| 316 | saddr.sin_len = sizeof(struct sockaddr_in); |
|---|
| 317 | #endif |
|---|
| 318 | |
|---|
| 319 | memset(&(saddr.sin_zero), '\0', sizeof(saddr.sin_zero)); |
|---|
| 320 | |
|---|
| 321 | memset(&packet.icmp, 0, sizeof(packet.icmp)); |
|---|
| 322 | packet.icmp.icmp_type = ICMP_ECHO; |
|---|
| 323 | packet.icmp.icmp_id = id; |
|---|
| 324 | for (j = 0, i = 0; i < sizeof(struct icmp) / 2; i++) |
|---|
| 325 | j += ((unsigned short *)&packet.icmp)[i]; |
|---|
| 326 | while (j>>16) |
|---|
| 327 | j = (j & 0xffff) + (j >> 16); |
|---|
| 328 | packet.icmp.icmp_cksum = (j == 0xffff) ? j : ~j; |
|---|
| 329 | |
|---|
| 330 | if (setsockopt(icmp_fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) == -1) { |
|---|
| 331 | debug(LOG_ERR, "setsockopt(): %s", strerror(errno)); |
|---|
| 332 | } |
|---|
| 333 | if (sendto(icmp_fd, (char *)&packet.icmp, sizeof(struct icmp), 0, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) { |
|---|
| 334 | debug(LOG_ERR, "sendto(): %s", strerror(errno)); |
|---|
| 335 | } |
|---|
| 336 | opt = 1; |
|---|
| 337 | if (setsockopt(icmp_fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) == -1) { |
|---|
| 338 | debug(LOG_ERR, "setsockopt(): %s", strerror(errno)); |
|---|
| 339 | } |
|---|
| 340 | |
|---|
| 341 | return; |
|---|
| 342 | } |
|---|
| 343 | |
|---|
| 344 | unsigned short rand16(void) { |
|---|
| 345 | static int been_seeded = 0; |
|---|
| 346 | |
|---|
| 347 | if (!been_seeded) { |
|---|
| 348 | int fd, n = 0; |
|---|
| 349 | unsigned int c = 0, seed = 0; |
|---|
| 350 | char sbuf[sizeof(seed)]; |
|---|
| 351 | char *s; |
|---|
| 352 | struct timeval now; |
|---|
| 353 | |
|---|
| 354 | /* not a very good seed but what the heck, it needs to be quickly acquired */ |
|---|
| 355 | gettimeofday(&now, NULL); |
|---|
| 356 | seed = now.tv_sec ^ now.tv_usec ^ (getpid() << 16); |
|---|
| 357 | |
|---|
| 358 | srand(seed); |
|---|
| 359 | been_seeded = 1; |
|---|
| 360 | } |
|---|
| 361 | |
|---|
| 362 | /* Some rand() implementations have less randomness in low bits |
|---|
| 363 | * than in high bits, so we only pay attention to the high ones. |
|---|
| 364 | * But most implementations don't touch the high bit, so we |
|---|
| 365 | * ignore that one. |
|---|
| 366 | **/ |
|---|
| 367 | return( (unsigned short) (rand() >> 15) ); |
|---|
| 368 | } |
|---|