/* * * PACrunner - Proxy configuration daemon * * Copyright (C) 2011 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #define DBG(fmt, arg...) do { \ printf("%s() " fmt "\n", __func__ , ## arg); \ } while (0) enum pacrunner_manual_exclude_appliance { PACRUNNER_MANUAL_EXCLUDE_POST = 0, PACRUNNER_MANUAL_EXCLUDE_PRE = 1, PACRUNNER_MANUAL_EXCLUDE_ANY = 2, }; enum pacrunner_manual_protocol { PACRUNNER_PROTOCOL_ALL = 0, PACRUNNER_PROTOCOL_HTTP = 1, PACRUNNER_PROTOCOL_HTTPS = 2, PACRUNNER_PROTOCOL_FTP = 3, PACRUNNER_PROTOCOL_SOCKS = 4, PACRUNNER_PROTOCOL_SOCKS4 = 5, PACRUNNER_PROTOCOL_SOCKS5 = 6, PACRUNNER_PROTOCOL_MAXIMUM_NUMBER = 7, PACRUNNER_PROTOCOL_UNKNOWN = 8, }; struct pacrunner_manual_exclude { enum pacrunner_manual_exclude_appliance appliance; int host_length; char *host; }; static enum pacrunner_manual_protocol get_protocol_from_string(const char *protocol) { if (!protocol) return PACRUNNER_PROTOCOL_ALL; if (g_strcmp0(protocol, "http") == 0) return PACRUNNER_PROTOCOL_HTTP; if (g_strcmp0(protocol, "https") == 0) return PACRUNNER_PROTOCOL_HTTPS; if (g_strcmp0(protocol, "ftp") == 0) return PACRUNNER_PROTOCOL_FTP; if (g_strcmp0(protocol, "socks") == 0) return PACRUNNER_PROTOCOL_SOCKS; if (g_strcmp0(protocol, "socks4") == 0) return PACRUNNER_PROTOCOL_SOCKS4; if (g_strcmp0(protocol, "socks5") == 0) return PACRUNNER_PROTOCOL_SOCKS5; return PACRUNNER_PROTOCOL_UNKNOWN; } static const char *get_protocol_to_string(enum pacrunner_manual_protocol protocol) { switch (protocol) { case PACRUNNER_PROTOCOL_ALL: return "ALL"; case PACRUNNER_PROTOCOL_HTTP: return "HTTP"; case PACRUNNER_PROTOCOL_HTTPS: return "HTTPS"; case PACRUNNER_PROTOCOL_FTP: return "FTP"; case PACRUNNER_PROTOCOL_SOCKS: return "SOCKS"; case PACRUNNER_PROTOCOL_SOCKS4: return "SOCKS4"; case PACRUNNER_PROTOCOL_SOCKS5: return "SOCKS5"; case PACRUNNER_PROTOCOL_MAXIMUM_NUMBER: case PACRUNNER_PROTOCOL_UNKNOWN: default: break; } return "UNKNOWN"; } static const char *get_appliance_to_string(enum pacrunner_manual_exclude_appliance appliance) { switch (appliance) { case PACRUNNER_MANUAL_EXCLUDE_POST: return "POST"; case PACRUNNER_MANUAL_EXCLUDE_PRE: return "PRE"; case PACRUNNER_MANUAL_EXCLUDE_ANY: return "ANY"; default: break; } return "UNKNOWN"; } static int parse_uri(char *uri, char **host, char **protocol, bool no_path, bool exclusion) { int ret = PACRUNNER_MANUAL_EXCLUDE_POST; bool proto, post_confirmed, ipv6; char *scheme, *sep, *cur; int length; long int port; proto = post_confirmed = ipv6 = false; port = -1; /** * Make sure host and protocol, if given, are properly set. */ if (host) *host = NULL; if (protocol) *protocol = NULL; /** * The parsing will actually process on a copy of given uri */ scheme = g_strdup(uri); if (!scheme) goto error; cur = scheme; /** * 1 - parsing protocol first * Note: protocol scheme is here totally ignored */ sep = strstr(cur, "://"); if (sep) { if (sep == cur) goto error; if (protocol) { *sep = '\0'; *protocol = g_strdup(cur); if (!*protocol) goto error; } cur = sep + 3; proto = true; } /** * 2 - detecting end of uri * Note: in case of server/exclusion configuration, * no path should be present */ sep = strchr(cur, '/'); if (sep) { if (exclusion || (*(sep + 1) != '\0' && no_path)) goto error; *sep = '\0'; } /** * 3 - We skip if present * Note: exclusion rule cannot contain such authentication information */ sep = strchr(cur, '@'); if (sep) { if (exclusion) goto error; *sep = '\0'; cur = sep + 1; } /** * 4 - Are we in front of a possible IPv6 address? * Note: ipv6 format is not checked! */ sep = strchr(cur, '['); if (sep) { char *bracket; bracket = strchr(cur, ']'); if (!bracket) goto error; cur = sep; sep = strchr(bracket, ':'); ipv6 = true; } else sep = strchr(cur, ':'); /** * 5 - Checking port validity if present * Note: exclusion rule cannot embed port */ if (sep) { char *err = NULL; if (exclusion) goto error; errno = 0; port = strtol(sep+1, &err, 10); if (*err != '\0' || port <= 0 || port > USHRT_MAX || errno == ERANGE || errno == EINVAL) goto error; *sep = '\0'; } /** * 6 - We detect/trim '.'/'*' from start * Note: This is valid only for exclusion URI since it defines * its rule's appliance */ for (sep = cur; *sep != '\0' && (*sep == '*' || *sep == '.'); sep++) *sep = '\0'; if (sep != cur) { if (!exclusion) goto error; cur = sep; post_confirmed = true; } /** * 7 - Parsing host if present */ length = strlen(cur); if (length > 0) { const char *forbidden_chars; char **forbidden = NULL; /** * We first detect/trim '.'/'*' from end * Note: valid only for exclusion */ for (sep = cur + length - 1; *sep != '\0' && (*sep == '*' || *sep == '.'); sep--) *sep = '\0'; if (sep - cur + 1 != length) { if (!exclusion) goto error; length = sep - cur + 1; ret = PACRUNNER_MANUAL_EXCLUDE_PRE; if (post_confirmed) ret = PACRUNNER_MANUAL_EXCLUDE_ANY; } if ((length > 255) || (*cur == '-' || *sep == '-') || ((*cur == '\0') && (!exclusion || (exclusion && !proto)))) goto error; /** * We do not allow some characters. However we do not run * a strict check if it's an IP address which is given */ if (ipv6) forbidden_chars = "%?!,;@\\'*|<>{}()+=$&~# \""; else forbidden_chars = "%?!,;@\\'*|<>{}[]()+=$&~# \""; forbidden = g_strsplit_set(cur, forbidden_chars, -1); if (forbidden) { length = g_strv_length(forbidden); g_strfreev(forbidden); if (length > 1) goto error; } if (host && *cur != '\0') { if (port > 0) { /** * Instead of transcoding the port back * to string we just get the host:port line * from the original uri. * */ cur = uri + (cur - scheme); sep = strchr(cur, '/'); if (sep) length = sep - cur; else length = strlen(cur); *host = g_strndup(cur, length); } else *host = g_strdup(cur); if (!*host) goto error; } } else { if (!exclusion || (exclusion && !proto)) goto error; else ret = PACRUNNER_MANUAL_EXCLUDE_ANY; } g_free(scheme); return ret; error: if (protocol) { g_free(*protocol); *protocol = NULL; } g_free(scheme); return -EINVAL; } static void free_exclude(gpointer data) { struct pacrunner_manual_exclude *exclude; exclude = (struct pacrunner_manual_exclude *) data; if (!exclude) return; g_free(exclude->host); g_free(exclude); } static void __pacrunner_manual_destroy_excludes(GList **excludes) { int i; if (!excludes) return; for (i = 0; i < PACRUNNER_PROTOCOL_MAXIMUM_NUMBER; i++) g_list_free_full(excludes[i], free_exclude); g_free(excludes); } static GList **__pacrunner_manual_create_excludes(const char **excludes) { struct pacrunner_manual_exclude *exclude; char *host, *protocol; GList **result = NULL; int ret, proto; char **uri; if (!excludes) return NULL; result = g_try_malloc0(PACRUNNER_PROTOCOL_MAXIMUM_NUMBER * sizeof(GList *)); if (!result) return NULL; for (uri = (char **)excludes; *uri; uri++) { ret = parse_uri(*uri, &host, &protocol, true, true); if (ret < 0) continue; proto = get_protocol_from_string(protocol); if (proto == PACRUNNER_PROTOCOL_UNKNOWN) goto error; exclude = g_try_malloc0(sizeof( struct pacrunner_manual_exclude)); if (!exclude) goto error; exclude->appliance = ret; exclude->host = host; if (host) exclude->host_length = strlen(host); result[proto] = g_list_append(result[proto], exclude); g_free(protocol); protocol = NULL; host = NULL; } return result; error: g_free(host); g_free(protocol); __pacrunner_manual_destroy_excludes(result); return NULL; } static void __pacrunner_manual_destroy_servers(GList **servers) { int i; if (!servers) return; for (i = 0; i < PACRUNNER_PROTOCOL_MAXIMUM_NUMBER; i++) g_list_free_full(servers[i], g_free); g_free(servers); } static const char *protocol_to_prefix_string(enum pacrunner_manual_protocol proto) { switch (proto) { case PACRUNNER_PROTOCOL_ALL: case PACRUNNER_PROTOCOL_HTTP: case PACRUNNER_PROTOCOL_HTTPS: case PACRUNNER_PROTOCOL_FTP: return "PROXY"; case PACRUNNER_PROTOCOL_SOCKS: return "SOCKS"; case PACRUNNER_PROTOCOL_SOCKS4: return "SOCKS4"; case PACRUNNER_PROTOCOL_SOCKS5: return "SOCKS5"; case PACRUNNER_PROTOCOL_MAXIMUM_NUMBER: case PACRUNNER_PROTOCOL_UNKNOWN: break; }; return ""; } static GList *append_proxy(GList *list, enum pacrunner_manual_protocol proto, char *host) { char *proxy; proxy = g_strdup_printf("%s %s", protocol_to_prefix_string(proto), host); if (!proxy) return list; return g_list_append(list, proxy); } static GList **__pacrunner_manual_create_servers(const char **servers) { char *host, *protocol; GList **result; char **uri; int proto; int ret; if (!servers) return NULL; result = g_try_malloc0(PACRUNNER_PROTOCOL_MAXIMUM_NUMBER * sizeof(GList *)); if (!result) return NULL; for (uri = (char **)servers; *uri; uri++) { ret = parse_uri(*uri, &host, &protocol, true, false); if (ret < 0) continue; proto = get_protocol_from_string(protocol); if (proto == PACRUNNER_PROTOCOL_UNKNOWN) goto error; result[proto] = append_proxy(result[proto], proto, host); if (proto == PACRUNNER_PROTOCOL_SOCKS) { result[PACRUNNER_PROTOCOL_SOCKS4] = append_proxy( result[PACRUNNER_PROTOCOL_SOCKS4], PACRUNNER_PROTOCOL_SOCKS4, host); result[PACRUNNER_PROTOCOL_SOCKS5] = append_proxy( result[PACRUNNER_PROTOCOL_SOCKS5], PACRUNNER_PROTOCOL_SOCKS5, host); } g_free(protocol); g_free(host); } return result; error: g_free(host); g_free(protocol); __pacrunner_manual_destroy_servers(result); return NULL; } static bool is_exclusion_matching(GList *excludes_list, const char *host) { struct pacrunner_manual_exclude *exclusion; GList *excludes = NULL; char *cursor; for (excludes = excludes_list; excludes; excludes = excludes->next) { exclusion = (struct pacrunner_manual_exclude *) excludes->data; if (!exclusion) continue; cursor = NULL; if (exclusion->host) cursor = strstr(host, exclusion->host); switch (exclusion->appliance) { case PACRUNNER_MANUAL_EXCLUDE_POST: if (!cursor) break; if ((int)strlen(cursor) < exclusion->host_length) break; if (*(cursor + exclusion->host_length) == '\0') return true; break; case PACRUNNER_MANUAL_EXCLUDE_PRE: if (cursor == host) return true; break; case PACRUNNER_MANUAL_EXCLUDE_ANY: if (exclusion->host) { if (cursor) return true; else break; } return true; default: break; } } return false; } static bool is_url_excluded(GList **excludes, const char *host, enum pacrunner_manual_protocol proto) { if (!excludes) return false; if (excludes[PACRUNNER_PROTOCOL_ALL]) if (is_exclusion_matching(excludes[PACRUNNER_PROTOCOL_ALL], host)) return true; if (proto == PACRUNNER_PROTOCOL_UNKNOWN) return false; if (excludes[proto]) if (is_exclusion_matching(excludes[proto], host)) return true; return false; } static inline char *append_server(char *prev_result, const char *proxy) { char *result; if (!prev_result) return g_strdup(proxy); result = g_strjoin("; ", prev_result, proxy, NULL); if (!result) return prev_result; g_free(prev_result); return result; } static inline char *append_servers_to_proxy_string(char *prev_result, GList *proxies) { char *result = prev_result; GList *list, *prev; prev = NULL; for (list = proxies; list && list != prev; prev = list, list = list->next) result = append_server(result, (const char *) list->data); return result; } static char *generate_proxy_string(GList **servers, enum pacrunner_manual_protocol proto) { enum pacrunner_manual_protocol i; char *result = NULL; /* if the protocol is known, we will prefer to set same * protocol-based proxies first, if any... */ if (proto >= PACRUNNER_PROTOCOL_HTTP && proto < PACRUNNER_PROTOCOL_MAXIMUM_NUMBER) { if (servers[proto]) result = append_servers_to_proxy_string(result, servers[proto]); if (proto == PACRUNNER_PROTOCOL_SOCKS) { if (servers[PACRUNNER_PROTOCOL_SOCKS4]) result = append_servers_to_proxy_string(result, servers[PACRUNNER_PROTOCOL_SOCKS4]); if (servers[PACRUNNER_PROTOCOL_SOCKS5]) result = append_servers_to_proxy_string(result, servers[PACRUNNER_PROTOCOL_SOCKS5]); } } /* And/or we add the rest in the list */ for (i = 0; i < PACRUNNER_PROTOCOL_MAXIMUM_NUMBER; i++) { if (i == proto || (proto == PACRUNNER_PROTOCOL_SOCKS && (i == PACRUNNER_PROTOCOL_SOCKS4 || i == PACRUNNER_PROTOCOL_SOCKS5))) continue; if (servers[i]) result = append_servers_to_proxy_string(result, servers[i]); } return result; } static char *__pacrunner_manual_execute(const char *url, GList **servers, GList **excludes) { char *protocol = NULL; char *result = NULL; char *host = NULL; int proto; if (!servers) return NULL; if (parse_uri((char *)url, &host, &protocol, false, false) < 0) goto direct; proto = get_protocol_from_string(protocol); if (is_url_excluded(excludes, host, proto)) goto direct; result = generate_proxy_string(servers, proto); direct: g_free(protocol); g_free(host); return result; } static void dump_proxy_exclusion(gpointer data, gpointer user_data) { struct pacrunner_manual_exclude *exclusion; exclusion = (struct pacrunner_manual_exclude *) data; if (!exclusion) return; printf("\tappliance: %s\n", get_appliance_to_string(exclusion->appliance)); printf("\thost: %s (%d)\n", exclusion->host, exclusion->host_length); } static void dump_excludes(GList **excludes) { int i; printf("EXCLUDES:\n"); if (!excludes) { printf("\tNO EXCLUDES\n"); return; } for (i = 0; i < PACRUNNER_PROTOCOL_MAXIMUM_NUMBER; i++) { printf("Protocol: %s\n", get_protocol_to_string(i)); if (!excludes[i]) { printf("\tNONE\n"); continue; } g_list_foreach(excludes[i], dump_proxy_exclusion, NULL); printf("\n"); } } static void dump_proxy_server(gpointer data, gpointer user_data) { printf("\t%s\n", (char *)data); } static void dump_servers(GList **servers) { int i; printf("SERVERS:\n"); if (!servers) { printf("\tNO SERVERS\n"); return; } for (i = 0; i < PACRUNNER_PROTOCOL_MAXIMUM_NUMBER; i++) { printf("Protocol: %s\n", get_protocol_to_string(i)); if (!servers[i]) { printf("\tNONE\n"); continue; } g_list_foreach(servers[i], dump_proxy_server, NULL); printf("\n"); } } static void print_help(void) { printf("Manual Proxy configuration test tool\n" "Usage:\n" "-e,--excludes : provides an exclusion list (optional)\n" "-h,--help : prints this help message\n" "-s,--servers : provides the proxy server(s) (mandatory)\n" "-t,--test : provides the URLs to test (mandatory)\n" "list should be made like \"elt1,elt2\" and so on\n" "\n" "Servers example:\n" "http://stuff.com -> http proxy\n" "stuff.com -> general proxy\n" "ftp://foo.org:4578 -> ftp proxy, with specific port\n" "\n" "Exclusion example:\n" "If a uri matches an exclusion rule, it will not be proxied\n" "port number is not allowed in exclusion rules\n" "'*' is not allowed for protocols\n" "stuff*foo is not allowed\n" "http:// -> all http\n" "http://*foo* -> all http containing foo\n" "*com -> all host ending with com\n" "ftp://stuff* -> all ftp starting with stuff\n"); } static struct option long_options[] = { {"excludes", 1, 0, 'e'}, {"help", 0, 0, 'h'}, {"servers", 1, 0, 's'}, {"test", 1, 0, 't'}, {NULL, 0, 0, 0 } }; static const char *short_options = "e:hs:t:"; int main(int argc, char *argv[]) { char **excludes_list, **servers_list, **tests_list; GList **excludes = NULL; GList **servers = NULL; int opt, option_index; char **url; excludes_list = NULL; servers_list = NULL; tests_list = NULL; option_index = 0; while ((opt = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) { switch (opt) { case 'e': excludes_list = g_strsplit(optarg, ",", 0); if (!excludes_list) exit(EXIT_FAILURE); break; case 'h': print_help(); exit(EXIT_SUCCESS); break; case 's': servers_list = g_strsplit(optarg, ",", 0); if (!servers_list) exit(EXIT_FAILURE); break; case 't': tests_list = g_strsplit(optarg, ",", 0); if (!tests_list) exit(EXIT_FAILURE); break; case '?': break; default: printf("Unknown option\n" "please see --help for more informations\n"); exit(EXIT_FAILURE); break; } } if (!servers_list || !tests_list) { printf("You must provide server(s) and test(s) options\n"); exit(EXIT_FAILURE); } servers = __pacrunner_manual_create_servers((const char **) servers_list); if (!servers) exit(EXIT_FAILURE); excludes = __pacrunner_manual_create_excludes((const char **) excludes_list); dump_excludes(excludes); dump_servers(servers); g_strfreev(excludes_list); g_strfreev(servers_list); for (url = tests_list; *url; url++) { char *proxy; printf("Url: %s -> ", *url); proxy = __pacrunner_manual_execute(*url, servers, excludes); if (!proxy) printf("DIRECT\n"); else printf("%s\n", proxy); } g_strfreev(tests_list); __pacrunner_manual_destroy_excludes(excludes); __pacrunner_manual_destroy_servers(servers); return 0; }