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