/** * Parses the hsts directives from given header like specified in RFC 6797 6.1 */ static void parse_hsts_header(HSTSProvider *provider, const char *host, const char *header) { GHashTable *directives = soup_header_parse_semi_param_list(header); HSTSEntry *entry; int max_age = G_MAXINT; gboolean include_sub_domains = false; GHashTableIter iter; gpointer key, value; gboolean success = true; HSTSProviderClass *klass = g_type_class_ref(HSTS_TYPE_PROVIDER); g_hash_table_iter_init(&iter, directives); while (g_hash_table_iter_next(&iter, &key, &value)) { /* parse the max-age directive */ if (!g_ascii_strncasecmp(key, "max-age", 7)) { /* max age needs a value */ if (value) { max_age = g_ascii_strtoll(value, NULL, 10); if (max_age < 0) { success = false; break; } } else { success = false; break; } } else if (g_ascii_strncasecmp(key, "includeSubDomains", 17)) { /* includeSubDomains must not have a value */ if (!value) { include_sub_domains = true; } else { success = false; break; } } } soup_header_free_param_list(directives); g_type_class_unref(klass); if (success) { /* remove host if max-age = 0 RFC 6797 6.1.1 */ if (max_age == 0) { remove_host_entry(provider, host); } else { entry = g_slice_new(HSTSEntry); entry->expires_at = soup_date_new_from_now(max_age); entry->include_sub_domains = include_sub_domains; add_host_entry(provider, host, entry); add_host_entry_to_file(provider, host, entry); } } }
/** * Reads the entries save in given file and store them in the whitelist. */ static void load_entries(HSTSProvider *provider, const char *file) { char **lines, **parts, *host, *line; int i, len, partlen; gboolean include_sub_domains; SoupDate *date; HSTSEntry *entry; lines = util_get_lines(file); if (!(len = g_strv_length(lines))) { return; } for (i = len - 1; i >= 0; i--) { line = lines[i]; /* skip empty or commented lines */ if (!*line || *line == '#') { continue; } parts = g_strsplit(line, "\t", 3); partlen = g_strv_length(parts); if (partlen == 3) { host = parts[0]; if (g_hostname_is_ip_address(host)) { continue; } date = soup_date_new_from_string(parts[1]); if (!date) { continue; } include_sub_domains = (*parts[2] == 'y') ? true : false; /* built the new entry to add */ entry = g_slice_new(HSTSEntry); entry->expires_at = soup_date_new_from_string(parts[1]); entry->include_sub_domains = include_sub_domains; add_host_entry(provider, host, entry); } else { g_warning("could not parse hsts line '%s'", line); } g_strfreev(parts); } g_strfreev(lines); }
/* * read_hosts_file * read the hosts file, resolve addresses, and put items into the main list * if it cannot resolve a name it fails */ static int read_hosts_file() { FILE *file; char line[132], host[132]; struct host_entry *cur = NULL; file = fopen(filename, "r"); if(file == NULL) { printerror(errno, strerror, "could not open %s", filename); return 0; } /* * read all the items in the file and add an entry for each of them * in the list of hosts to be pinged */ while(fgets(line,sizeof(line),file) != NULL) { line[sizeof(line)-1] = '\0'; /* extract out the first word in the line */ if(sscanf(line, "%s", host) != -1) { /* see if the line is either blank or a comment */ if(!(host[0] == '\0' || host[0] == '#')) { /* if we get here, add a host entry for this name */ cur = add_host_entry(host, cur); if(cur == NULL) { fclose(file); return 0; } } } } fclose(file); return 1; }
/* * Function: main * Purpose: * Comments: */ int main(int argc, char *argv[]) { int i; struct sigaction si_sa; int euid, ruid; struct host_entry *cursor; /* * first things first * if anything goes wrong anywhere, we want to be super careful to release * any file descriptor we have on /dev/lkm, and it probably makes sense to * shutdown the raw socket we have too. */ atexit(cleanup); /* * get the effective and the real user id's so we know if we are running * with elevated permissions or not */ euid = geteuid(); ruid = getuid(); /* check for the command line arguments */ if(check_options(argc,argv) == 0) { return -1; } /* * open the raw socket now. i used to have this further down closer to * where it was needed, but because you can't change the euid/uid to and * from root (unless you're the superuser) i can only do this once */ if(options & OPT_IPV4) { if(open_ipmp_sockets4() == 0) return -1; } else if(options & OPT_IPV6) { if(open_ipmp_sockets6() == 0) return -1; } else { if(open_ipmp_sockets4() == 0) return -1; if(open_ipmp_sockets6() == 0) return -1; } /* * revoke the permissions we requested as we only need them to open a raw * socket. this is to reduce the impact of any buffer overflow exploits * that may be present */ if(ruid != euid) { setreuid(ruid, ruid); } /* * we get the pid so we can identify incoming ipmp packets destined for * this instance of ipmp_ping */ pid = getpid(); /* * need to know about the addresses this host has */ learn_localaddresses(); /* * in FreeBSD, the actual ping is done by a kernel module that has a syscall * in it. the kernel module allows the protocol to get a timestamp as close * to when the mbuf is actually sent to ip_output as possible */ #if defined(__FreeBSD__) if((options & OPT_RAW) == 0) { i = get_syscall("ipmp_ping", &syscall_num); if(i != 0) { printerror(i, strerror, "could not get the syscall for ipmp_ping"); return -1; } } #endif /* * the -n option means that the user has supplied a list of hosts to * ping, so we read those entries and put them in an list of hosts with * details regarding each host. the task of putting hosts into the list * is handled by read_hosts_file * * if the -n option isnt specified, we create a list containing just the * one host to ping. this way, all the program logic can be used in a * multitude of situations. */ if(options & OPT_NLANR) { /* * if something went wrong parsing the file, we quit. */ if(read_hosts_file() == 0) { return -1; } } else { /* * if the user didnt specify a host to ping, we bail, telling them * why first... */ if(argc - optind != 1) { usage(0); return -1; } /* * if we can't add a host entry for the host supplied on the command line * tell the user that it couldnt be parse and cleanup */ if(add_host_entry(argv[optind], NULL) == 0) { return -1; } } /* * we now put some handlers into action so if the user ctrl-c's us we have * the opportunity to tell them what we found out first * also, if the user specified a timeout, put an alarm in for that so we * can bail when they tell us to... */ sigemptyset(&si_sa.sa_mask); si_sa.sa_flags = 0; si_sa.sa_handler = alarm_bells; if(sigaction(SIGINT, &si_sa, 0) == -1) { printerror(errno, strerror, "could not set sigaction for SIGINT"); return -1; } if(options & OPT_TIMEOUT) { if(sigaction(SIGALRM, &si_sa, 0) == -1) { printerror(errno, strerror, "could not set sigaction for SIGALRM"); return -1; } } /* * we loop for as long as we have not been told to finish up. * the finish_up loop will exit when * - there has been an alarm set that goes off * - a SIGINT is received (from e.g. Ctrl-C) * - we have got_all_response()'s * * this is not an expensive loop in terms of cpu cycles, as the * recv_echo_response will sleep if there is nothing to recv until we have * told it to stop blocking - typically one second or whatever the between * timeout is. * * the loop does two things: * - sends echo requests * - receives echo responses * * the loop sends packets, pausing for however long the timeout is set for * between packets. this pause is implemented in the recv_echo_response * function in a call to select(2). if we cannot send a request to one of * them, we bail, as this probably means the syscall could not be called. * * the loop recv's the response and associates the packet with a host_entry * we then parse that response for the host entry, and then check if we have * now got all the responses we are looking for. if we have, we exit the * loop by setting the finish_up flag */ sent_all_requests = 0; i = 0; cursor = head; while(finish_up == 0) { while(sent_all_requests == 0) { if(send_echo_request(cursor) != 0) { return -1; } cursor->tx++; if(cursor->tx == count) { cursor = cursor->next; if(cursor == NULL) { sent_all_requests = 1; alarm(timeout); break; } } if(wait_between > 0) { gettimeofday(&wait_between_tv, &tz); timeval_add(&wait_between_tv, wait_between); break; } } while(time_to_send_request() == 0 && finish_up == 0) { if(recv_echo_responses() > 0) { if(got_all_responses() == 1) { finish_up = 1; } } } } /* * if we have been given a list of hosts to ping, we have to print out which * hosts did not give us a reply */ if(options & OPT_NLANR) { show_loss(); } return 0; }