From 8096db706dbdf48f6dcbf528c5716fda493bb9fb Mon Sep 17 00:00:00 2001 From: PantherJohn Date: Sat, 13 Jul 2019 17:11:04 +0800 Subject: [PATCH] ;) Nailed it! SO FAR SO GOOD --- acl/delegation.acl | 40 ++- src/acl.c | 617 ++++++++++++++++++++++++++++++--------------- src/acl.h | 31 ++- src/cache.c | 15 +- src/cache.h | 7 + src/jconf.c | 18 +- src/manager.c | 4 - src/netutils.c | 45 +++- src/netutils.h | 10 +- src/relay.c | 58 +++-- src/server.c | 20 +- src/shadowsocks.h | 9 +- src/udprelay.c | 31 ++- src/utils.c | 255 ++++++++++--------- src/utils.h | 101 ++++++-- 15 files changed, 839 insertions(+), 422 deletions(-) diff --git a/acl/delegation.acl b/acl/delegation.acl index 65b94200..cc9ce5f4 100644 --- a/acl/delegation.acl +++ b/acl/delegation.acl @@ -1,12 +1,42 @@ # All IPs listed here will be blocked while the ss-server try to outbound. # Only IP is allowed, *NOT* domain name. # -#[bypass_all] +@mode bypass [bypass_list] -127.0.0.1 -(^|\.)bing\.com$ +@import local.acl # relative path +@import $$ + #!/bin/bash + arin() { + declare authority + declare country="$1" version="$2" + case ${country} in + "US"|"CA"|"PR") + authority='http://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest' ;; + *) authority='http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' ;; + esac + wget -qO- "${authority}" | grep "${version}" | grep "${country}" | awk -F'|' '{ printf("%s/%d\n", $4, 32-log($5)/log(2)) }' + } + # see: https://github.com/firehol/iprange/wiki#iprange + #arin CN | iprange # optimized list of ip addresses +$$ # shell script + +#@import [barfoo] +#@import [foo] from /etc/foo.acl /etc/barz.acl +(^|\.)cn$ [proxy_list: Seattle, WA] -(^|\.)edu$ -(^|\.)google\.com$ +(^|\.)(edu|mil|gov|us)$ + +[proxy_list: Netflix] +#@match regex +(^|\.)(netflix|nflxext)\.com$ +(^|\.)(nflxso|nflximg|nflxvideo)\.net$ + +[proxy_list: La Jolla, CA] +.* + + +#[dname_map] +#(^|\.)cdninstagram\.com 127.0.0.1 +#(^|\.)instagram\.com facebook.com diff --git a/src/acl.c b/src/acl.c index 215805ac..692f95a6 100644 --- a/src/acl.c +++ b/src/acl.c @@ -25,6 +25,8 @@ #endif #include +#include +#include #include "rule.h" #include "netutils.h" @@ -35,168 +37,44 @@ static acl_t acl; -void free_rules(struct cork_dllist *rules); -void update_addrlist(addrlist *list, int atyp, const void *host); +typedef cork_array(char *) labels_t; +typedef struct { + char *ltag; // list tag + labels_t *tags; // delegation label +} label_t; -void -init_addrlist(addrlist *addrlist) { - ipset_init(&addrlist->ipv4); - ipset_init(&addrlist->ipv6); - cork_dllist_init(&addrlist->domain); -} +typedef struct ctrlist { + label_t label; + cork_array(addrlist *) *list; +} ctrlist_t; void -free_addrlist(addrlist *addrlist) { - ipset_done(&addrlist->ipv4); - ipset_done(&addrlist->ipv6); - free_rules(&addrlist->domain); +init_addrlist(addrlist *addrlist) +{ + ipset_init(&addrlist->ip); + cork_dllist_init(&addrlist->domain); } void -free_rules(struct cork_dllist *rules) +merge_addrlist(addrlist *dst, addrlist *src) { - rule_t *rule; - struct cork_dllist_item *curr, *next; - cork_dllist_foreach(rules, curr, next, - rule_t, rule, entries) { - remove_rule(rule); - } -} + struct ipset_iterator + *sit = ipset_iterate(&src->ip, true), + *sit_ntwks = ipset_iterate_networks(&src->ip, true); -int -init_acl(jconf_t *conf) -{ - const char *path = conf->acl; - if (path == NULL) { - return -1; + while (!sit->finished) { + ipset_ip_add(&dst->ip, &sit->addr); + ipset_iterator_advance(sit); } - FILE *f = fopen(path, "r"); - if (f == NULL) { - LOGE("Invalid acl path."); - return -1; + while (!sit_ntwks->finished) { + ipset_ip_add_network(&dst->ip, &sit->addr, sit->cidr_prefix); + ipset_iterator_advance(sit_ntwks); } - acl.mode = ACL_BLACKLIST; - - init_addrlist(&acl.blocklist); - init_addrlist(&acl.blacklist); - init_addrlist(&acl.whitelist); - - addrlist *list = &acl.blacklist; - - char buf[MAX_HOSTNAME_LEN]; - - while (!feof(f)) - if (fgets(buf, 256, f)) { - // Discards the whole line if longer than 255 characters - int long_line = 0; // 1: Long 2: Error - while ((strlen(buf) == 255) && (buf[254] != '\n')) { - long_line = 1; - LOGE("Discarding long ACL content: %s", buf); - if (fgets(buf, 256, f) == NULL) { - long_line = 2; - break; - } - } - if (long_line) { - if (long_line == 1) { - LOGE("Discarding long ACL content: %s", buf); - } - continue; - } - - // Trim the newline - int len = strlen(buf); - if (len > 0 && buf[len - 1] == '\n') { - buf[len - 1] = '\0'; - } - - char *comment = strchr(buf, '#'); - if (comment) { - *comment = '\0'; - } - - char *line = trim_whitespace(buf); - if (strlen(line) == 0) { - continue; - } - - if (strcmp(line, "[outbound_block_list]") == 0) { - list = &acl.blocklist; - continue; - } else if (strcmp(line, "[black_list]") == 0 - || strcmp(line, "[bypass_list]") == 0) { - list = &acl.blacklist; - continue; - } else if (strcmp(line, "[white_list]") == 0 - || strcmp(line, "[proxy_list]") == 0) { - list = &acl.whitelist; - continue; - } else if (strcmp(line, "[reject_all]") == 0 - || strcmp(line, "[bypass_all]") == 0) { - acl.mode = ACL_WHITELIST; - continue; - } else if (strcmp(line, "[accept_all]") == 0 - || strcmp(line, "[proxy_all]") == 0) { - acl.mode = ACL_BLACKLIST; - continue; - } else { - const char *delim = "[]", *sep = ":"; - char *keyword = strtok(strdup(line), delim); - - if (strcmp(line, keyword) == 0) { - update_addrlist(list, ACL_ATYP_ANY, line); - continue; - } - - int j = 0; - do { - char *cmd = trim_whitespace(strtok(keyword, sep)); - if (cmd != NULL) { - if (strcmp(cmd, "proxy_list") == 0) { - int remote_num = 0; - int *remote_idxs = NULL; - - char *tag = NULL; - while ((tag = strtok(NULL, sep))) { - for (int i = 0; i < conf->remote_num; i++) { - char *tag_ = trim_whitespace(tag); - char *rtag = trim_whitespace(conf->remotes[i]->tag); - if (rtag != NULL && strcmp(tag_, rtag) == 0) { - remote_idxs = ss_realloc(remote_idxs, - (remote_num + 1) * sizeof(*remote_idxs)); - remote_idxs[remote_num++] = i; - } - } - } - - acl.deleglist = ss_realloc(acl.deleglist, - (j + 1) * sizeof(acl.deleglist)); - delglist *deleglist = acl.deleglist[j++] - = ss_calloc(1, sizeof(*acl.deleglist)); - - deleglist->remote_num = remote_num; - deleglist->remote_idxs = remote_idxs; - init_addrlist(list = &deleglist->_); - } - } - } while ((keyword = strtok(NULL, delim))); - acl.deleglist[j] = NULL; - } - } - - fclose(f); - - return 0; -} - -void -free_acl(void) -{ - free_addrlist(&acl.blocklist); - free_addrlist(&acl.blacklist); - free_addrlist(&acl.whitelist); + cork_dllist_merge(&dst->domain, &src->domain); + ipset_iterator_free(sit); + ipset_iterator_free(sit_ntwks); } void @@ -212,23 +90,16 @@ update_addrlist(addrlist *list, parse_addr_cidr((const char *)host, hostaddr, &cidr); int err = cork_ip_init(&addr, hostaddr); if (!err) { - switch (addr.version) { - case 4: - if (cidr >= 0) { - ipset_ipv4_add_network(&list->ipv4, &addr.ip.v4, cidr); - } else { - ipset_ipv4_add(&list->ipv4, &addr.ip.v4); - } break; - case 6: - if (cidr >= 0) { - ipset_ipv6_add_network(&list->ipv6, &addr.ip.v6, cidr); - } else { - ipset_ipv6_add(&list->ipv6, &addr.ip.v6); - } break; + if (cidr < 0) { + ipset_ip_add(&list->ip, &addr); + } else { + ipset_ip_add_network(&list->ip, &addr, cidr); } } else { rule_t *rule = new_rule(); - if (accept_rule_arg(rule, (const char *)host) != -1 && init_rule(rule)) { + if (accept_rule_arg(rule, (const char *)host) != -1 + && init_rule(rule)) + { add_rule(&list->domain, rule); } } @@ -236,35 +107,30 @@ update_addrlist(addrlist *list, case ACL_ATYP_IP: { switch(((struct sockaddr_storage *)host)->ss_family) { case AF_INET: { - struct cork_ipv4 addr; - cork_ipv4_copy(&addr, - &((struct sockaddr_in *)host)->sin_addr); - ipset_ipv4_add(&list->ipv4, &addr); + ipset_ipv4_add(&list->ip, + (struct cork_ipv4 *)&((struct sockaddr_in *)host)->sin_addr); } break; case AF_INET6: { - struct cork_ipv6 addr; - cork_ipv6_copy(&addr, - &((struct sockaddr_in6 *)host)->sin6_addr); - ipset_ipv6_add(&list->ipv6, &addr); + ipset_ipv6_add(&list->ip, + (struct cork_ipv6 *)&((struct sockaddr_in6 *)host)->sin6_addr); } break; } } break; case ACL_ATYP_IPV4: { - struct cork_ipv4 addr; - cork_ipv4_copy(&addr, (struct in_addr *)host); - ipset_ipv4_add(&list->ipv4, &addr); + ipset_ipv4_add(&list->ip, + (struct cork_ipv4 *)&((struct sockaddr_in *)host)->sin_addr); } break; case ACL_ATYP_IPV6: { - struct cork_ipv6 addr; - cork_ipv6_copy(&addr, (struct in6_addr *)host); - ipset_ipv6_add(&list->ipv6, &addr); + ipset_ipv6_add(&list->ip, + (struct cork_ipv6 *)&((struct sockaddr_in6 *)host)->sin6_addr); } break; case ACL_ATYP_DOMAIN: { - const char *dname = (const char *)host; rule_t *rule = new_rule(); - accept_rule_arg(rule, dname); - init_rule(rule); - add_rule(&list->domain, rule); + if (accept_rule_arg(rule, (const char *)host) != -1 + && init_rule(rule)) + { + add_rule(&list->domain, rule); + } } break; } } @@ -278,29 +144,21 @@ search_addrlist(addrlist *list, switch (atyp) { case ACL_ATYP_IP: { switch(((struct sockaddr_storage *)host)->ss_family) { - case AF_INET: { - struct cork_ipv4 addr; - cork_ipv4_copy(&addr, - &((struct sockaddr_in *)host)->sin_addr); - return ipset_contains_ipv4(&list->ipv4, &addr); - } - case AF_INET6: { - struct cork_ipv6 addr; - cork_ipv6_copy(&addr, - &((struct sockaddr_in6 *)host)->sin6_addr); - return ipset_contains_ipv6(&list->ipv6, &addr); - } + case AF_INET: + return ipset_contains_ipv4(&list->ip, + (struct cork_ipv4 *)&((struct sockaddr_in *)host)->sin_addr); + case AF_INET6: + return ipset_contains_ipv6(&list->ip, + (struct cork_ipv6 *)&((struct sockaddr_in6 *)host)->sin6_addr); } } break; case ACL_ATYP_IPV4: { - struct cork_ipv4 addr; - cork_ipv4_copy(&addr, (struct in_addr *)host); - return ipset_contains_ipv4(&list->ipv4, &addr); + return ipset_contains_ipv4(&list->ip, + (struct cork_ipv4 *)&((struct sockaddr_in *)host)->sin_addr); } case ACL_ATYP_IPV6: { - struct cork_ipv6 addr; - cork_ipv6_copy(&addr, (struct in6_addr *)host); - return ipset_contains_ipv6(&list->ipv6, &addr); + return ipset_contains_ipv6(&list->ip, + (struct cork_ipv6 *)&((struct sockaddr_in6 *)host)->sin6_addr); } case ACL_ATYP_DOMAIN: { dname_t *dname = (dname_t *)host; @@ -311,6 +169,361 @@ search_addrlist(addrlist *list, return false; } +void +free_addrlist(addrlist *addrlist) +{ + rule_t *rule = NULL; + struct cork_dllist_item *curr, *next; + cork_dllist_foreach(&addrlist->domain, curr, next, + rule_t, rule, entries) { + remove_rule(rule); + } + ipset_done(&addrlist->ip); +} + +ctrlist_t * +fetch_ctrlist(struct cache *ctrlists, label_t *label) +{ + ctrlist_t *ctrlist = NULL; + size_t ltaglen = strlen(label->ltag); + if (cache_lookup(ctrlists, + label->ltag, ltaglen + 1, &ctrlist) != 0) { + ctrlist = ss_calloc(1, sizeof(ctrlist_t)); + ctrlist->label = *label; + ctrlist->list = ss_malloc(sizeof(*ctrlist->list)); + cork_array_init(ctrlist->list); + addrlist *addrlist = ss_malloc(sizeof(*addrlist)); + init_addrlist(addrlist); + cork_array_append(ctrlist->list, addrlist); + cache_insert(ctrlists, label->ltag, ltaglen + 1, ctrlist); + } + return ctrlist; +} + +void +ctrlist_free_cb(void *key, void *element) +{ + ctrlist_t *ctrlist = element; + if (ctrlist->label.ltag) + ss_free(ctrlist->label.ltag); + if (ctrlist->list) { + for (int i = 0; i < ctrlist->list->size; i++) + free_addrlist(ctrlist->list->items[i]); + cork_array_done(ctrlist->list); + ss_free(ctrlist->list); + } + if (ctrlist->label.tags) + cork_array_done(ctrlist->label.tags); +} + +int +merge_ctrlists(struct cache *dst, struct cache *src) +{ + if (!(dst && src)) + return -1; + + struct cache_entry *entry, *tmp; + cache_foreach(src, entry, tmp) { + ctrlist_t *dstlist = NULL, *srclist = entry->data; + size_t ltaglen = strlen(srclist->label.ltag); + if (cache_lookup(dst, srclist->label.ltag, ltaglen + 1, &dstlist) == 0) { + // TODO merge OR replace? + if (!srclist->list) + continue; + if (dstlist->list) + cork_array_merge(dstlist->list, srclist->list); + if (!srclist->label.tags) + continue; + if (!dstlist->label.tags) { + dstlist->label.tags = ss_malloc(sizeof(*dstlist->label.tags)); + cork_array_init(dstlist->label.tags); + } + cork_array_merge(dstlist->label.tags, srclist->label.tags); + } else { + cache_insert(dst, srclist->label.ltag, ltaglen + 1, srclist); + } + } + return 0; +} + +void +parse_aclconf(FILE *f, aclconf_t *aclconf, ctrlist_t *ctrlist_n) +{ + char buf[MAX_HOSTNAME_LEN]; + char dlscript[] = "$$", dlvarnme[] = "[]", + dltagsep[] = ":", dfaultag[] = "default", + dlempty[] = "", whitespace[] = " \t\n"; + + struct cache *ctrlists = new_cache(-1, ctrlist_free_cb); + if (ctrlists == NULL) + return; + + ctrlist_t *ctrlist = fetch_ctrlist(ctrlists, + &(label_t) { .ltag = strdup(dfaultag) }); + + while (fgets(buf, sizeof(buf), f) != NULL) { + // Discards the whole line if longer than 255 characters + while (strlen(buf) >= sizeof(buf) && + buf[sizeof(buf) - 2] != '\n') { + LOGE("Discarding long ACL content: %s", buf); + if (fgets(buf, sizeof(buf), f) == NULL) + break; + continue; + } + + // Trim the newline + int len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') { + buf[len - 1] = '\0'; + } + + char *comment = strchr(buf, '#'); + if (comment) { + *comment = '\0'; + } + + char *line = trim_whitespace(buf); + if (strlen(line) == 0) { + continue; + } + + char *keywd_n = strdup(line); + char *keyword = strtok(keywd_n, whitespace); + + if (strcmp(keyword, "@mode") == 0) { + char *mode = strtok(NULL, whitespace); + if (mode != NULL) { + if (strcmp(mode, "reject") == 0 || + strcmp(mode, "bypass") == 0 || + strcmp(mode, "reject_all") == 0 || + strcmp(mode, "bypass_all") == 0) + { + aclconf->mode = ACL_BLACKLIST; + } else + if (strcmp(mode, "accept") == 0 || + strcmp(mode, "proxy") == 0 || + strcmp(mode, "accept_all") == 0 || + strcmp(mode, "proxy_all") == 0) + { + aclconf->mode = ACL_WHITELIST; + } + } + } else if (strcmp(keyword, "@interval") == 0) { + aclconf->interval = strtotime(strtok(NULL, whitespace)); + } else if (strcmp(keyword, "@algorithm") == 0) { + continue; + } else if (strcmp(keyword, "@import") == 0) { + char *import = NULL; + while ((import = strtok(NULL, whitespace))) { + // scripting identifier + if (strcmp(import, dlscript) == 0) { + char *script = strtok(NULL, dlempty); + size_t script_len = 0; + if (script != NULL) { + script = strdup(script); + } + + do { + script_len = script ? strlen(script) : 0; + script = ss_realloc(script, script_len + BUF_SIZE + 1); + + char *n; + if ((n = strstr(script, dlscript))) { + *n = 0; + break; + } + } while (fgets(script + script_len, BUF_SIZE, f)); + + FILE *f = popen(script, "r"); + parse_aclconf(f, aclconf, ctrlist); + pclose(f); + + ss_free(script); + break; + } else { + char *varname = strtok(strdup(import), dlvarnme); + if (strcmp(varname, import)) { + // variable name + do { + char *ltag = trim_whitespace(varname); + ctrlist_t *ctrlistv = NULL; + // only valid inside the list + if (ltag != NULL && strcmp(ctrlist->label.ltag, dfaultag) != 0 && + cache_lookup(ctrlists, ltag, strlen(ltag) + 1, &ctrlistv) == 0) + { + cork_array_merge(ctrlist->list, ctrlistv->list); + } + } while ((varname = strtok(NULL, dlvarnme))); + } else { + // file path + FILE *f = fopen(import, "r"); + setcwd(current_dir(import)); + parse_aclconf(f, aclconf, ctrlist); + fclose(f); + } + } + } + } else { + char *varname = strtok(line, dlvarnme); + + /** + * acl entry: addrlist element + * ------------------------------- + * format: valid CIDR notation (IP addresses) + * or perl-compatible regex (domain name) + * example: 127.0.0.1, 192.168.0.1/32, (^|\.)(edu|mil|gov|us)$ + */ + + if (strcmp(line, varname) == 0) { + update_addrlist(*ctrlist->list->items, ACL_ATYP_ANY, line); + continue; + } + + /** + * acl definition: addrlist tag + * ------------------------------- + * format: [{ addrlist_type }: { tag }:{ tag }:...] + * example: [proxy_list: La Jolla, CA] + * + * NOTE: delegation tags are not inheritable + */ + + do { + char *ltag = trim_whitespace(strtok(varname, dltagsep)); + if (ltag != NULL) { + char *tag = NULL; + labels_t *tags = ss_malloc(sizeof(*tags)); + cork_array_init(tags); + while ((tag = strtok(NULL, dltagsep))) { + cork_array_append(tags, trim_whitespace(tag)); + } + + ctrlist = fetch_ctrlist(ctrlists, &(label_t) { strdup(ltag), tags }); + } + } while ((varname = strtok(NULL, dlvarnme))); + } + ss_free(keywd_n); + } + + ctrlist_t *ctrlistv = NULL; + if (ctrlist_n != NULL && + strcmp(ctrlist_n->label.ltag, dfaultag) != 0 && + (cache_lookup(ctrlists, ctrlist_n->label.ltag, + strlen(ctrlist_n->label.ltag) + 1, &ctrlistv) == 0 || + cache_lookup(ctrlists, dfaultag, sizeof(dfaultag), &ctrlistv) == 0)) + { + // inside the list being declared + cork_array_merge(ctrlist_n->list, ctrlistv->list); + } else { + // global scope + merge_ctrlists(aclconf->lists, ctrlists); + } + + cache_delete(ctrlists, true); +} + +int +parse_acl(acl_t *acl, aclconf_t *aclconf) +{ + const char *path = aclconf->path; + if (path == NULL) { + return -1; + } + + char *prevwd = getcwd(NULL, 0); + FILE *f = fopen(path, "r"); + if (f == NULL) { + LOGE("[acl] invalid acl path %s", path); + return -1; + } + + if (!aclconf->lists) + return -1; + + setcwd(current_dir(path)); + parse_aclconf(f, aclconf, NULL); + fclose(f); + + if (prevwd != NULL) + setcwd(prevwd); + ss_free(prevwd); + + if (aclconf->interval > 0) { + ev_timer_set(&acl->watcher, aclconf->interval, aclconf->interval); + } + + acl->mode = aclconf->mode; + + int j = 0; + struct cache_entry *entry, *tmp; + cache_foreach(aclconf->lists, entry, tmp) { + ctrlist_t *list = entry->data; + addrlist *rlist = &acl->blacklist; + char *ltag = list->label.ltag; + + if (strcmp(ltag, "outbound_block_list") == 0) { + rlist = &acl->blocklist; + } else if (strcmp(ltag, "black_list") == 0 || + strcmp(ltag, "bypass_list") == 0) { + rlist = &acl->blacklist; + } else if (strcmp(ltag, "white_list") == 0) { + rlist = &acl->whitelist; + } else if (strcmp(ltag, "proxy_list") == 0) { + rlist = &acl->whitelist; + if (list->label.tags != NULL) { + acl->deleglist = ss_realloc(acl->deleglist, + (j + 2) * sizeof(acl->deleglist)); + delglist *deleglist = acl->deleglist[j++] + = ss_calloc(1, sizeof(*deleglist)); + + cork_array_init(&deleglist->idxs); + for (int n = 0; n < list->label.tags->size; n++) { + for (int i = 0; i < aclconf->conf->remote_num; i++) { + char *rtag = trim_whitespace(aclconf->conf->remotes[i]->tag); + if (rtag && strcmp(list->label.tags->items[n], rtag) == 0) + cork_array_append(&deleglist->idxs, i); + } + } + + init_addrlist(rlist = &deleglist->_); + acl->deleglist[j] = NULL; + } + } else { + if (strcmp(ltag, "default") != 0) + LOGE("[acl] invalid list type %s", ltag); + continue; + } + + // merge address lists + for (int i = 0; i < list->list->size; i++) + merge_addrlist(rlist, list->list->items[i]); + } + + cache_clear(aclconf->lists, 0); + + return 0; +} + +int +init_acl(jconf_t *conf) +{ + acl.conf = (aclconf_t) { + .mode = ACL_BLACKLIST, + .lists = new_cache(-1, NULL), + .path = conf->acl, + .conf = conf + }; + + init_addrlist(&acl.blocklist); + init_addrlist(&acl.blacklist); + init_addrlist(&acl.whitelist); + + if (parse_acl(&acl, &acl.conf) != 0) + return -1; + + return 0; +} + /** * search_acl * ---------------------- @@ -337,8 +550,8 @@ search_acl(int atyp, const void *host, int type) delglist **deleglist = acl.deleglist; do { if (search_addrlist(&(*deleglist)->_, atyp, host) - && (*deleglist)->remote_idxs != NULL) - return (*deleglist)->remote_idxs[rand() % (*deleglist)->remote_num]; + && (*deleglist)->idxs.items != NULL) + return (*deleglist)->idxs.items[rand() % (*deleglist)->idxs.size]; } while (*(++deleglist)); } else { bool ret = false; diff --git a/src/acl.h b/src/acl.h index 2aa567bb..d7696cdf 100644 --- a/src/acl.h +++ b/src/acl.h @@ -29,6 +29,12 @@ #include #endif +#ifdef HAVE_LIBEV_EV_H +#include +#else +#include +#endif + #include "jconf.h" enum { @@ -49,28 +55,39 @@ enum { } acl_types; typedef struct { - struct ip_set ipv4, ipv6; + struct ip_set ip; struct cork_dllist domain; } addrlist; typedef struct { - int remote_num; // number of remote servers - int *remote_idxs; // a list of indexes of remote servers - addrlist _; // the delegation list for load-balancing + addrlist _; // delegation list for load-balancing + cork_array(int) idxs; // a list of indexes of remote servers } delglist; +typedef struct aclconf { + int mode, algo; + time_t interval; + struct cache *lists; + const char *path; + jconf_t *conf; +} aclconf_t; + typedef struct acl { int mode; + aclconf_t conf; addrlist blocklist; addrlist blacklist, whitelist; delglist **deleglist; - //addrlist **deleglist; + ev_timer watcher; } acl_t; int init_acl(jconf_t *conf); -void free_acl(void); +int search_acl(int atyp, const void *host, int type); + +void init_addrlist(addrlist *addrlist); +void free_addrlist(addrlist *addrlist); +void merge_addrlist(addrlist *dst, addrlist *src); void update_addrlist(addrlist *list, int atyp, const void *host); bool search_addrlist(addrlist *list, int atyp, const void *host); -int search_acl(int atyp, const void *host, int type); #endif // _ACL_H diff --git a/src/cache.c b/src/cache.c index f73f8371..4ae66151 100644 --- a/src/cache.c +++ b/src/cache.c @@ -35,6 +35,15 @@ #include "cache.h" #include "utils.h" +struct cache * +new_cache(const size_t capacity, + void (*free_cb)(void *key, void *element)) +{ + struct cache *ret = NULL; + cache_create(&ret, capacity, free_cb); + return ret; +} + /** Creates a new cache object * * @param dst @@ -88,7 +97,7 @@ cache_delete(struct cache *cache, int keep_data) if (keep_data) { HASH_CLEAR(hh, cache->entries); } else { - HASH_ITER(hh, cache->entries, entry, tmp){ + HASH_ITER(hh, cache->entries, entry, tmp) { HASH_DEL(cache->entries, entry); if (entry->data != NULL) { if (cache->free_cb) { @@ -127,7 +136,7 @@ cache_clear(struct cache *cache, ev_tstamp age) ev_tstamp now = ev_time(); - HASH_ITER(hh, cache->entries, entry, tmp){ + HASH_ITER(hh, cache->entries, entry, tmp) { if (now - entry->ts > age) { HASH_DEL(cache->entries, entry); if (entry->data != NULL) { @@ -203,7 +212,7 @@ cache_remove(struct cache *cache, void *key, size_t key_len) * you have to call this function with a **ptr, * otherwise this will blow up in your face. * - * @return EINVAL if cache is NULL, 0 otherwise + * @return EINVAL if cache is NULL or the key doesn't exist, 0 otherwise */ int cache_lookup(struct cache *cache, void *key, size_t key_len, void *result) diff --git a/src/cache.h b/src/cache.h index 04c28c7d..02280ffa 100644 --- a/src/cache.h +++ b/src/cache.h @@ -55,6 +55,13 @@ struct cache { void (*free_cb)(void *key, void *element); /**entries) > 0) \ + HASH_ITER(hh, (cache)->entries, (entry), (tmp)) + +struct cache * +new_cache(const size_t capacity, + void (*free_cb)(void *key, void *element)); int cache_create(struct cache **dst, const size_t capacity, void (*free_cb)(void *key, void *element)); int cache_delete(struct cache *cache, int keep_data); diff --git a/src/jconf.c b/src/jconf.c index 2b6caa68..d1dacb98 100644 --- a/src/jconf.c +++ b/src/jconf.c @@ -477,18 +477,16 @@ parse_argopts(jconf_t *conf, int argc, char **argv) { char optstr[3] = { [0] = option->name, [1] = option->has_arg == required_argument ? ':' : 0 }; - short_options = ss_realloc(short_options, strlen(short_options) + strlen(optstr)); + short_options = ss_realloc(short_options, strlen(short_options) + strlen(optstr) + 1); strcat(short_options, optstr); } } - opterr = 0; - - int c, curind; + int c; int conf_parsed = 0; again: - curind = optind; + opterr = 0; while ((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { @@ -547,17 +545,15 @@ again: } break; case jconf_type_config: break; default: - case jconf_type_unknown: { + case jconf_type_unknown: // The option character is not recognized. - LOGE("Unrecognized option: %s", - optopt ? (char []) { optopt } : argv[curind]); - opterr = 1; - } break; + LOGE("Unrecognized option: %s", argv[optind - 1]); + return -1; } } break; } } } - return opterr; + return 0; } diff --git a/src/manager.c b/src/manager.c index 95d863b0..cfd2c0ed 100644 --- a/src/manager.c +++ b/src/manager.c @@ -61,10 +61,6 @@ #include "netutils.h" #include "manager.h" -#ifndef BUF_SIZE -#define BUF_SIZE 65535 -#endif - int verbose = 0; char *executable = "ss-server"; char *working_dir = NULL; diff --git a/src/netutils.c b/src/netutils.c index f7fe8961..8ddb7677 100644 --- a/src/netutils.c +++ b/src/netutils.c @@ -102,15 +102,33 @@ set_fastopen_passive(int socket) return s; } -socklen_t -get_sockaddr_len(struct sockaddr *addr) +int +tproxy_socket(int socket, int family) { - if (addr->sa_family == AF_INET) { - return sizeof(struct sockaddr_in); - } else if (addr->sa_family == AF_INET6) { - return sizeof(struct sockaddr_in6); +#if defined(IP_TRANSPARENT) && \ + defined(IP_RECVORIGDSTADDR) && \ + defined(IPV6_RECVORIGDSTADDR) + int opt = 1, err = -1; + if (setsockopt(socket, SOL_IP, IP_TRANSPARENT, &opt, sizeof(opt))) { + close(socket); + return err; } - return 0; + + switch (family) { + case AF_INET: + err = setsockopt(socket, SOL_IP, + IP_RECVORIGDSTADDR, &opt, sizeof(opt)); + break; + case AF_INET6: + err = setsockopt(socket, SOL_IPV6, + IPV6_RECVORIGDSTADDR, &opt, sizeof(opt)); + break; + } + + return err; +#else + FATAL("transparent proxy not supported in this build"); +#endif } int @@ -219,6 +237,13 @@ create_and_bind(struct sockaddr_storage *storage, && listen_ctx->mptcp) { set_mptcp(fd); } +#elif MODULE_REDIR + if (protocol == IPPROTO_UDP) { + if (tproxy_socket(fd, storage->ss_family)) { + ERROR("tproxy_socket"); + FATAL("failed to enable transparent proxy"); + } + } #endif } @@ -489,7 +514,7 @@ sockaddr_readable(char *format, struct sockaddr_storage *addr) if (addr->ss_family == AF_INET6 && i + 1 < len && format[i + 1] == ':') { - substr = malloc(strlen(host) + 2); + substr = malloc(strlen(host) + 2 + 1); sprintf(substr, "[%s]", host); } else { substr = host; @@ -509,7 +534,7 @@ sockaddr_readable(char *format, struct sockaddr_storage *addr) } size_t substrlen = strlen(substr); - ret = realloc(ret, strlen(ret) + substrlen); + ret = realloc(ret, strlen(ret) + substrlen + 1); strcat(ret, substr); } return ret; @@ -616,7 +641,7 @@ validate_hostname(const char *hostname, const int hostname_len) char * hostname_readable(char *dname, uint16_t port) { - static char ret[] = { 0 }; + char *ret = ss_calloc(strlen(dname) + MAX_PORT_STR_LEN, sizeof(*ret)); sprintf(ret, "%s:%d", dname, ntohs(port)); return ret; } diff --git a/src/netutils.h b/src/netutils.h index e7a1f435..43c2a365 100644 --- a/src/netutils.h +++ b/src/netutils.h @@ -105,6 +105,10 @@ #define MAX_HOSTNAME_LEN 256 // FQCN <= 255 characters #define MAX_PORT_STR_LEN 6 // PORT < 65536 +#ifndef BUF_SIZE +#define BUF_SIZE 65535 +#endif + #define SOCKET_BUF_SIZE (16 * 1024 - 1) // 16383 Byte, equals to the max chunk size #define STREAM_BUF_SIZE SOCKET_BUF_SIZE @@ -194,13 +198,17 @@ static const char mptcp_enabled_values[] = { MPTCP_ENABLED, 0 }; #define get_sockaddr(node, service, storage, resolv, ipv6first) \ get_sockaddr_r(node, service, 0, storage, resolv, ipv6first) -socklen_t get_sockaddr_len(struct sockaddr *addr); +#define get_sockaddr_len(addr) \ + (addr)->sa_family == AF_INET ? sizeof(struct sockaddr_in) : \ + (addr)->sa_family == AF_INET6 ? sizeof(struct sockaddr_in6) : 0 + int get_sockaddr_r(const char *, const char *, uint16_t, struct sockaddr_storage *, int, int); int get_sockaddr_str(struct sockaddr_storage *storage, char *host, char *port); char *sockaddr_readable(char *format, struct sockaddr_storage *addr); +int tproxy_socket(int socket, int family); int getdestaddr(int fd, struct sockaddr_storage *destaddr); int getdestaddr_dgram(struct msghdr *msg, struct sockaddr_storage *destaddr); diff --git a/src/relay.c b/src/relay.c index de22ed6f..1098c7cd 100644 --- a/src/relay.c +++ b/src/relay.c @@ -74,11 +74,9 @@ static int server_conn = 0; static char *manager_addr = NULL; static struct cork_dllist listeners; extern uint64_t tx, rx; - #ifndef __MINGW32__ ev_timer stat_watcher; #endif - #endif static struct cork_dllist connections; @@ -216,18 +214,17 @@ bailed: { create_ssocks_header(server->abuf, destaddr); return init_remote(EV_A_ remote, listen_ctx->remotes[remote_idx]); } else { - struct sockaddr_storage *addr - = elvis(destaddr->addr, ss_calloc(1, sizeof(*addr))); - if (destaddr->dname && + if (destaddr->dname && !destaddr->addr && + (destaddr->addr = ss_calloc(1, sizeof(*destaddr->addr))) && get_sockaddr_r(destaddr->dname, NULL, - destaddr->port, addr, 1, ipv6first) == -1) + destaddr->port, destaddr->addr, 1, ipv6first) == -1) { remote->direct = 0; LOGE("failed to resolve %s", destaddr->dname); goto bailed; } - return init_remote(EV_A_ remote, &(remote_cnf_t) { .addr = addr, .iface = listen_ctx->iface }); + return init_remote(EV_A_ remote, &(remote_cnf_t) { .addr = destaddr->addr, .iface = listen_ctx->iface }); } return 0; } @@ -372,7 +369,7 @@ new_server(int fd, listen_ctx_t *listener) ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ); ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE); ev_timer_init(&server->recv_ctx->watcher, server_timeout_cb, - request_timeout, listener->timeout); + request_timeout, 0); cork_dllist_add(&connections, &server->entries); @@ -657,13 +654,6 @@ start_relay(jconf_t *conf, return -1; } -#ifndef HAVE_LAUNCHD - if (conf->local_port == NULL) { - conf->local_port = "0"; - LOGE("warning: random local port will be assigned"); - } -#endif - if (conf->log) { USE_LOGFILE(conf->log); LOGI("enabled %slogging %s", conf->verbose ? "verbose " : "", conf->log); @@ -685,9 +675,6 @@ start_relay(jconf_t *conf, LOGI("prioritized IPv6 addresses in domain resolution"); } - if (!conf->remote_dns) { - LOGI("disabled remote domain resolution"); - } #ifndef MODULE_TUNNEL if (conf->acl != NULL) { @@ -741,6 +728,18 @@ start_relay(jconf_t *conf, // Setup proxy context struct ev_loop *loop = EV_DEFAULT; #ifdef MODULE_LOCAL + +#ifndef HAVE_LAUNCHD + if (conf->local_port == NULL) { + conf->local_port = "0"; + LOGE("warning: random local port will be assigned"); + } +#endif + + if (!conf->remote_dns) { + LOGI("disabled remote domain resolution"); + } + listen_ctx_t listen_ctx = { .mtu = conf->mtu, .mptcp = conf->mptcp, @@ -1056,18 +1055,28 @@ start_relay(jconf_t *conf, if (conf->mode != UDP_ONLY) { ev_io_stop(EV_A_ & listen_ctx.io); free_connections(loop); + } + if (listen_ctx.remotes != NULL) { for (int i = 0; i < listen_ctx.remote_num; i++) { remote_cnf_t *remote_cnf = listen_ctx.remotes[i]; if (remote_cnf != NULL) { - ss_free(listen_ctx.remotes[i]); + if (remote_cnf->iface) + ss_free(remote_cnf->iface); + // TODO: hey yo! free() the whole struct, pls + if (remote_cnf->crypto) + ss_free(remote_cnf->crypto); + if (remote_cnf->addr) + ss_free(remote_cnf->addr); + ss_free(remote_cnf); } } ss_free(listen_ctx.remotes); } + #elif MODULE_REMOTE #ifndef __MINGW32__ - if (manager_addr != NULL) { + if (conf->manager_addr) { ev_timer_stop(EV_A_ & stat_watcher); } #endif @@ -1078,13 +1087,16 @@ start_relay(jconf_t *conf, } #endif - if (plugin_enabled) - stop_plugin(); - if (conf->mode != TCP_ONLY) { free_udprelay(loop); } + if (plugin_enabled) + stop_plugin(); + + if (conf->log) + CLOSE_LOGFILE(); + #ifdef __MINGW32__ winsock_cleanup(); #endif diff --git a/src/server.c b/src/server.c index e49e6542..1cb43bb1 100644 --- a/src/server.c +++ b/src/server.c @@ -318,9 +318,6 @@ server_recv_cb(EV_P_ ev_io *w, int revents) case STAGE_STREAM: { remote = server->remote; buf = remote->buf; - - // Only timer the watcher if a valid connection is established - ev_timer_again(EV_A_ & server->recv_ctx->watcher); } break; } @@ -387,7 +384,12 @@ server_recv_cb(EV_P_ ev_io *w, int revents) } if (destaddr.dname != NULL) { - if (acl && search_acl(ACL_ATYP_DOMAIN, destaddr.dname, ACL_BLOCKLIST)) { + if (destaddr.dname[destaddr.dname_len - 1] != 0) { + destaddr.dname = ss_realloc(destaddr.dname, destaddr.dname_len + 1); + destaddr.dname[destaddr.dname_len] = 0; + } + + if (acl && search_acl(ACL_ATYP_DOMAIN, &(dname_t){ destaddr.dname_len, destaddr.dname }, ACL_BLOCKLIST)) { if (verbose) LOGI("blocking access to %s", destaddr.dname); close_and_free_server(EV_A_ server); @@ -399,7 +401,7 @@ server_recv_cb(EV_P_ ev_io *w, int revents) ev_io_stop(EV_A_ & server_recv_ctx->io); - query_t *query = ss_calloc(1, sizeof(query_t)); + query_t *query = ss_malloc(sizeof(query_t)); query->server = server; query->hostname = destaddr.dname; @@ -594,8 +596,6 @@ remote_recv_cb(EV_P_ ev_io *w, int revents) return; } - ev_timer_again(EV_A_ & server->recv_ctx->watcher); - ssize_t r = recv(remote->fd, server->buf->data, SOCKET_BUF_SIZE, 0); if (r == 0) { @@ -714,9 +714,9 @@ remote_send_cb(EV_P_ ev_io *w, int revents) memset(&addr, 0, len); int r = getpeername(remote->fd, (struct sockaddr *)&addr, &len); if (r == 0) { - if (verbose) { - LOGI("remote connected"); - } + // connection connected, stop the request timeout timer + ev_timer_stop(EV_A_ & server->recv_ctx->watcher); + remote_send_ctx->connected = 1; if (remote->buf->len == 0) { diff --git a/src/shadowsocks.h b/src/shadowsocks.h index 7fe35049..0696bb5c 100644 --- a/src/shadowsocks.h +++ b/src/shadowsocks.h @@ -100,7 +100,6 @@ new_ssocks_addr() { ssocks_addr_t *destaddr = ss_calloc(1, sizeof(*destaddr)); - destaddr->dname = ss_calloc(1, MAX_HOSTNAME_LEN * sizeof(*destaddr->dname)); destaddr->addr = ss_calloc(1, sizeof(*destaddr->addr)); return destaddr; } @@ -175,14 +174,14 @@ parse_ssocks_header(buffer_t *buf, ssocks_addr_t *destaddr, int offset) } break; case SSOCKS_ATYP_DOMAIN: { size_t dname_len = *(uint8_t *)(buf->data + offset); - char *dname = ss_calloc(dname_len, sizeof(char)); if (buf->len < dname_len + 1 + offset) { return -1; - } else { - memcpy(dname, buf->data + offset + 1, dname_len); - offset += dname_len + 1; } + char *dname = ss_malloc(dname_len); + memcpy(dname, buf->data + offset + 1, dname_len); + offset += dname_len + 1; + destaddr->port = *(uint16_t *)(buf->data + offset); struct cork_ip ip; diff --git a/src/udprelay.c b/src/udprelay.c index bef2adbc..46729db5 100644 --- a/src/udprelay.c +++ b/src/udprelay.c @@ -164,21 +164,21 @@ resolv_cb(struct sockaddr *addr, void *data) #elif defined MODULE_REDIR static int -create_tproxy(struct sockaddr_storage *addr, struct sockaddr_storage *destaddr) +create_tproxy(struct sockaddr_storage *destaddr) { - int sourcefd = socket(addr->ss_family, SOCK_DGRAM, 0); + int sourcefd = socket(destaddr->ss_family, SOCK_DGRAM, 0); if (sourcefd < 0) { return -1; } int opt = 1; - if (setsockopt(sourcefd, - addr->ss_family == AF_INET6 ? SOL_IPV6 : SOL_IP, + if (setsockopt(sourcefd, destaddr->ss_family == AF_INET6 ? SOL_IPV6 : SOL_IP, IP_TRANSPARENT, &opt, sizeof(opt))) { close(sourcefd); return -1; } + if (setsockopt(sourcefd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { close(sourcefd); return -1; @@ -187,12 +187,12 @@ create_tproxy(struct sockaddr_storage *addr, struct sockaddr_storage *destaddr) // Set QoS flag int tos = 46; setsockopt(sourcefd, - addr->ss_family == AF_INET6 ? IPPROTO_IP: IPPROTO_IPV6, + destaddr->ss_family == AF_INET6 ? IPPROTO_IP: IPPROTO_IPV6, IP_TOS, &tos, sizeof(tos)); #endif if (bind(sourcefd, (struct sockaddr *)destaddr, - get_sockaddr_len((struct sockaddr *)&destaddr)) != 0) + get_sockaddr_len((struct sockaddr *)destaddr)) != 0) { ERROR("[udp] remote_recv_bind"); close(sourcefd); @@ -422,7 +422,7 @@ remote_recv_cb(EV_P_ ev_io *w, int revents) #endif #ifdef MODULE_REDIR - int sourcefd = create_tproxy(remote->src_addr, remote->destaddr); + int sourcefd = create_tproxy(remote->destaddr); if (sourcefd < 0) { ERROR("[udp] remote_recv_socket"); goto CLEAN_UP; @@ -502,8 +502,8 @@ server_recv_cb(EV_P_ ev_io *w, int revents) }; struct msghdr msg = { - .msg_name = destaddr->addr, - .msg_namelen = sizeof(*destaddr->addr), + .msg_name = src_addr, + .msg_namelen = sizeof(*src_addr), .msg_control = control_buffer, .msg_controllen = sizeof(control_buffer), .msg_iov = iov, @@ -516,13 +516,14 @@ server_recv_cb(EV_P_ ev_io *w, int revents) goto CLEAN_UP; } + destaddr->addr = ss_calloc(1, sizeof(*destaddr->addr)); if (getdestaddr_dgram(&msg, destaddr->addr)) { LOGE("[udp] unable to determine destination address"); goto CLEAN_UP; } #else - socklen_t src_addr_len = sizeof(&src_addr); + socklen_t src_addr_len = sizeof(*src_addr); ssize_t r = recvfrom(server->fd, buf->data, buf_size, 0, (struct sockaddr *)src_addr, &src_addr_len); @@ -575,6 +576,11 @@ server_recv_cb(EV_P_ ev_io *w, int revents) } if (destaddr->dname != NULL) { + if (destaddr->dname[destaddr->dname_len - 1] != 0) { + destaddr->dname = ss_realloc(destaddr->dname, destaddr->dname_len + 1); + destaddr->dname[destaddr->dname_len] = 0; + } + if (acl && search_acl(ACL_ATYP_DOMAIN, &(dname_t) { destaddr->dname_len, destaddr->dname }, ACL_BLOCKLIST)) { @@ -738,8 +744,9 @@ bailed: { remote->crypto = crypto; remote_addr = remote_cnf->addr; } else { - remote_addr = elvis(destaddr->addr, ss_calloc(1, sizeof(*remote_addr))); - if (destaddr->dname && + remote_addr = destaddr->addr; + if (destaddr->dname && !remote_addr && + (remote_addr = ss_calloc(1, sizeof(*remote_addr))) && get_sockaddr_r(destaddr->dname, NULL, destaddr->port, remote_addr, 1, ipv6first) == -1) { diff --git a/src/utils.c b/src/utils.c index a6b836ab..8372b246 100644 --- a/src/utils.c +++ b/src/utils.c @@ -49,7 +49,7 @@ #include #endif -#define INT_DIGITS 19 /* enough for 64 bit integer */ +#define INT_DIGITS 19 /* enough for 64 bit integer */ int use_tty = 0; int use_syslog = 0; @@ -60,25 +60,29 @@ ss_itoa(int i) { /* Room for INT_DIGITS digits, - and '\0' */ static char buf[INT_DIGITS + 2]; - char *p = buf + INT_DIGITS + 1; /* points to terminating '\0' */ - if (i >= 0) { - do { + char *p = buf + INT_DIGITS + 1; /* points to terminating '\0' */ + if (i >= 0) + { + do + { *--p = '0' + (i % 10); - i /= 10; + i /= 10; } while (i != 0); return p; - } else { /* i < 0 */ - do { + } + else + { /* i < 0 */ + do + { *--p = '0' - (i % 10); - i /= 10; + i /= 10; } while (i != 0); *--p = '-'; } return p; } -int -ss_isnumeric(const char *s) +int ss_isnumeric(const char *s) { if (!s || !*s) return 0; @@ -90,15 +94,16 @@ ss_isnumeric(const char *s) /* * setuid() and setgid() for a specified user. */ -int -run_as(const char *user) +int run_as(const char *user) { #ifndef __MINGW32__ - if (user[0]) { + if (user[0]) + { /* Convert user to a long integer if it is a non-negative number. * -1 means it is a user name. */ long uid = -1; - if (ss_isnumeric(user)) { + if (ss_isnumeric(user)) + { errno = 0; char *endptr; uid = strtol(user, &endptr, 10); @@ -112,18 +117,21 @@ run_as(const char *user) size_t buflen; int err; - for (buflen = 128;; buflen *= 2) { - char buf[buflen]; /* variable length array */ + for (buflen = 128;; buflen *= 2) + { + char buf[buflen]; /* variable length array */ /* Note that we use getpwnam_r() instead of getpwnam(), * which returns its result in a statically allocated buffer and * cannot be considered thread safe. */ err = uid >= 0 ? getpwuid_r((uid_t)uid, &pwdbuf, buf, buflen, &pwd) - : getpwnam_r(user, &pwdbuf, buf, buflen, &pwd); + : getpwnam_r(user, &pwdbuf, buf, buflen, &pwd); - if (err == 0 && pwd) { + if (err == 0 && pwd) + { /* setgid first, because we may not be allowed to do it anymore after setuid */ - if (setgid(pwd->pw_gid) != 0) { + if (setgid(pwd->pw_gid) != 0) + { LOGE( "Could not change group id to that of run_as user '%s': %s", pwd->pw_name, strerror(errno)); @@ -131,28 +139,37 @@ run_as(const char *user) } #ifndef __CYGWIN__ - if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) { + if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) + { LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name); return 0; } #endif - if (setuid(pwd->pw_uid) != 0) { + if (setuid(pwd->pw_uid) != 0) + { LOGE( "Could not change user id to that of run_as user '%s': %s", pwd->pw_name, strerror(errno)); return 0; } break; - } else if (err != ERANGE) { - if (err) { + } + else if (err != ERANGE) + { + if (err) + { LOGE("run_as user '%s' could not be found: %s", user, strerror(err)); - } else { + } + else + { LOGE("run_as user '%s' could not be found.", user); } return 0; - } else if (buflen >= 16 * 1024) { + } + else if (buflen >= 16 * 1024) + { /* If getpwnam_r() seems defective, call it quits rather than * keep on allocating ever larger buffers until we crash. */ LOGE( @@ -166,21 +183,25 @@ run_as(const char *user) /* No getpwnam_r() :-( We'll use getpwnam() and hope for the best. */ struct passwd *pwd; - if (!(pwd = uid >= 0 ? getpwuid((uid_t)uid) : getpwnam(user))) { + if (!(pwd = uid >= 0 ? getpwuid((uid_t)uid) : getpwnam(user))) + { LOGE("run_as user %s could not be found.", user); return 0; } /* setgid first, because we may not allowed to do it anymore after setuid */ - if (setgid(pwd->pw_gid) != 0) { + if (setgid(pwd->pw_gid) != 0) + { LOGE("Could not change group id to that of run_as user '%s': %s", pwd->pw_name, strerror(errno)); return 0; } - if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) { + if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) + { LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name); return 0; } - if (setuid(pwd->pw_uid) != 0) { + if (setuid(pwd->pw_uid) != 0) + { LOGE("Could not change user id to that of run_as user '%s': %s", pwd->pw_name, strerror(errno)); return 0; @@ -200,7 +221,8 @@ ss_strndup(const char *s, size_t n) size_t len = strlen(s); char *ret; - if (len <= n) { + if (len <= n) + { return strdup(s); } @@ -210,59 +232,7 @@ ss_strndup(const char *s, size_t n) return ret; } -void * -ss_malloc(size_t size) -{ - void *tmp = malloc(size); - if (tmp == NULL) - exit(EXIT_FAILURE); - return tmp; -} - -void * -ss_aligned_malloc(size_t size) -{ - int err; - void *tmp = NULL; -#ifdef HAVE_POSIX_MEMALIGN - /* ensure 16 byte alignment */ - err = posix_memalign(&tmp, 16, size); -#elif __MINGW32__ - tmp = _aligned_malloc(size, 16); - err = tmp == NULL; -#else - err = -1; -#endif - if (err) { - return ss_malloc(size); - } else { - return tmp; - } -} - -void * -ss_realloc(void *ptr, size_t new_size) -{ - void *new = realloc(ptr, new_size); - if (new == NULL) { - free(ptr); - ptr = NULL; - exit(EXIT_FAILURE); - } - return new; -} - -void * -ss_calloc(size_t num, size_t size) -{ - void *tmp = calloc(num, size); - if (tmp == NULL) - exit(EXIT_FAILURE); - return tmp; -} - -int -ss_is_ipv6addr(const char *addr) +int ss_is_ipv6addr(const char *addr) { return strcmp(addr, ":") > 0; } @@ -279,7 +249,7 @@ trim_whitespace(char *str) while (isspace((unsigned char)*str)) str++; - if (*str == 0) // All spaces? + if (*str == 0) // All spaces? return str; // Trim trailing space @@ -293,8 +263,7 @@ trim_whitespace(char *str) return str; } -void -usage() +void usage() { printf("\n"); printf("shadowsocks-libev %s\n\n", VERSION); @@ -432,8 +401,7 @@ usage() printf("\n"); } -void -daemonize(const char *path) +void daemonize(const char *path) { #ifndef __MINGW32__ /* Our process ID and Session ID */ @@ -441,15 +409,18 @@ daemonize(const char *path) /* Fork off the parent process */ pid = fork(); - if (pid < 0) { + if (pid < 0) + { exit(EXIT_FAILURE); } /* If we got a good PID, then * we can exit the parent process. */ - if (pid > 0) { + if (pid > 0) + { FILE *file = fopen(path, "w"); - if (file == NULL) { + if (file == NULL) + { FATAL("Invalid pid file\n"); } @@ -465,23 +436,28 @@ daemonize(const char *path) /* Create a new SID for the child process */ sid = setsid(); - if (sid < 0) { + if (sid < 0) + { /* Log the failure */ exit(EXIT_FAILURE); } /* Change the current working directory */ - if ((chdir("/")) < 0) { + if ((chdir("/")) < 0) + { /* Log the failure */ exit(EXIT_FAILURE); } int dev_null = open("/dev/null", O_WRONLY); - if (dev_null) { + if (dev_null) + { /* Redirect to null device */ dup2(dev_null, STDOUT_FILENO); dup2(dev_null, STDERR_FILENO); - } else { + } + else + { /* Close the standard file descriptors */ close(STDOUT_FILENO); close(STDERR_FILENO); @@ -495,24 +471,30 @@ daemonize(const char *path) } #ifdef HAVE_SETRLIMIT -int -set_nofile(int nofile) +int set_nofile(int nofile) { - struct rlimit limit = { nofile, nofile }; /* set both soft and hard limit */ + struct rlimit limit = {nofile, nofile}; /* set both soft and hard limit */ - if (nofile <= 0) { + if (nofile <= 0) + { FATAL("nofile must be greater than 0\n"); } - if (setrlimit(RLIMIT_NOFILE, &limit) < 0) { - if (errno == EPERM) { + if (setrlimit(RLIMIT_NOFILE, &limit) < 0) + { + if (errno == EPERM) + { LOGE( "insufficient permission to change NOFILE, not starting as root?"); return -1; - } else if (errno == EINVAL) { + } + else if (errno == EINVAL) + { LOGE("invalid nofile, decrease nofile and try again"); return -1; - } else { + } + else + { LOGE("setrlimit failed: %s", strerror(errno)); return -1; } @@ -526,23 +508,25 @@ set_nofile(int nofile) size_t readoff_from(char **content, const char *file) { - FILE *f = strcmp(file, "-") == 0 ? - stdin : fopen(file, "r"); - if (f == NULL) { + FILE *f = strcmp(file, "-") == 0 ? stdin : fopen(file, "r"); + if (f == NULL) + { FATAL("Invalid file path %s", file); } size_t pos = 0; char buf[1024] = { 0 }; - while (fgets(buf, sizeof(buf), f)) { + while (fgets(buf, sizeof(buf), f)) + { size_t len = strlen(buf); *content = ss_realloc(*content, pos + len); strncpy(*content + pos, buf, len); pos += len; } - if (ferror(f)) { + if (ferror(f)) + { FATAL("Failed to read the file.") } @@ -557,22 +541,27 @@ get_default_conf(void) #ifndef __MINGW32__ static char sysconf[] = "/etc/shadowsocks-libev/config.json"; static char *userconf = NULL; - static int buf_size = 0; + static int buf_size = 0; char *conf_home; conf_home = getenv("XDG_CONFIG_HOME"); // Memory of userconf only gets allocated once, and will not be // freed. It is used as static buffer. - if (!conf_home) { - if (buf_size == 0) { + if (!conf_home) + { + if (buf_size == 0) + { buf_size = 50 + strlen(getenv("HOME")); userconf = malloc(buf_size); } snprintf(userconf, buf_size, "%s%s", getenv("HOME"), "/.config/shadowsocks-libev/config.json"); - } else { - if (buf_size == 0) { + } + else + { + if (buf_size == 0) + { buf_size = 50 + strlen(conf_home); userconf = malloc(buf_size); } @@ -590,3 +579,43 @@ get_default_conf(void) return "config.json"; #endif } + +/** + * strtotime + * --------------------- + * Convert a string to a time value in seconds. + * written by Thomas Moestl. + rewritten in C from scratch by Paul A. Rombouts + * + */ +time_t +strtotime(char *str) +{ + time_t retval = 0, t; + char c; + + while (isalnum(c = *str)) + { + if (!isdigit(c)) + break; + + t = strtol(str, &str, 10); + + struct { char c; time_t t; } tmtable[] = { + { 's', 1 }, { 'm', 60 }, { 'h', 60 }, + { 'd', 24 }, { 'w', 7 } + }; + + if (isalpha(c = *str)) + { + for (int i = 0; i < nelem(tmtable) && tmtable[i++].c != c;) { + t *= tmtable[i].t; + } + ++str; + } + + retval += t; + } + + return retval; +} diff --git a/src/utils.h b/src/utils.h index b7668c68..d7c57c6d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -176,35 +176,22 @@ void ss_color_reset(void); exit(-1); \ } -char *ss_itoa(int i); -int ss_isnumeric(const char *s); int run_as(const char *user); void usage(void); void daemonize(const char *path); + +char *ss_itoa(int i); +int ss_isnumeric(const char *s); char *ss_strndup(const char *s, size_t n); #ifdef HAVE_SETRLIMIT int set_nofile(int nofile); #endif -void *ss_malloc(size_t size); -void *ss_aligned_malloc(size_t size); -void *ss_realloc(void *ptr, size_t new_size); -void *ss_calloc(size_t num, size_t size); - #define ss_free(ptr) { \ free(ptr); \ ptr = NULL; \ } -static inline char * -currtime_readable() -{ - time_t now = time(NULL); - static char timestr[20] = {}; - strftime(timestr, 20, TIME_FORMAT, localtime(&now)); - return timestr; -} - #ifdef __MINGW32__ #define ss_aligned_free(ptr) { \ _aligned_free(ptr); \ @@ -214,10 +201,92 @@ currtime_readable() #define ss_aligned_free(ptr) ss_free(ptr) #endif +inline void * +ss_malloc(size_t size) +{ + void *tmp = malloc(size); + if (tmp == NULL) + exit(EXIT_FAILURE); + return tmp; +} + +inline void * +ss_aligned_malloc(size_t size) +{ + int err; + void *tmp = NULL; +#ifdef HAVE_POSIX_MEMALIGN + /* ensure 16 byte alignment */ + err = posix_memalign(&tmp, 16, size); +#elif __MINGW32__ + tmp = _aligned_malloc(size, 16); + err = tmp == NULL; +#else + err = -1; +#endif + return err ? ss_malloc(size) : tmp; +} + +inline void * +ss_realloc(void *ptr, size_t new_size) +{ + void *new = realloc(ptr, new_size); + if (new == NULL) + { + free(ptr); + ptr = NULL; + exit(EXIT_FAILURE); + } + return new; +} + +inline void * +ss_calloc(size_t num, size_t size) +{ + void *tmp = calloc(num, size); + if (tmp == NULL) + exit(EXIT_FAILURE); + return tmp; +} + int ss_is_ipv6addr(const char *addr); char *trim_whitespace(char *str); +// file operations +#define current_dir(path) \ + realpath(dirname(strdup(path)), NULL) +#define setcwd(path) \ + if (chdir(path) != 0) \ + ERROR("setcwd"); + size_t readoff_from(char **content, const char *file); char *get_default_conf(void); +// time functions +time_t strtotime(char *str); +static inline char * +currtime_readable() +{ + time_t now = time(NULL); + static char timestr[20] = {}; + strftime(timestr, 20, TIME_FORMAT, localtime(&now)); + return timestr; +} + +// libcork extension functions +#include + +#define cork_array_merge(dst, src) \ + for (int i = 0; i < cork_array_size(src); i++) { \ + cork_array_append(dst, cork_array_at(src, i)); \ + } + +inline void +cork_dllist_merge(struct cork_dllist *dst, struct cork_dllist *src) +{ + struct cork_dllist_item *curr, *next; + cork_dllist_foreach_void(src, curr, next) + cork_dllist_add(dst, curr); +} + #endif // _UTILS_H