diff --git a/api.cpp b/api.cpp index 7ae02d5..b08938f 100644 --- a/api.cpp +++ b/api.cpp @@ -83,17 +83,23 @@ static struct IP4ACCESS *ipaccess = NULL; #define SOCK_REC_BUFSZ 1024 #define QUEUE 10 -#define ALLIP4 "0.0.0.0" +//#define ALLIP4 "0.0.0.0" static const char *localaddr = "127.0.0.1"; static const char *UNAVAILABLE = " - API will not be available"; +static const char *MUNAVAILABLE = " - API multicast listener will not be available"; static char *buffer = NULL; static time_t startup = 0; static int bye = 0; extern char *opt_api_bind; +extern int opt_api_port; extern char *opt_api_allow; -extern int opt_api_listen; /* port */ -extern int opt_api_remote; +extern char *opt_api_groups; +extern bool opt_api_mcast; +extern char *opt_api_mcast_addr; +extern char *opt_api_mcast_code; +extern char *opt_api_mcast_des; +extern int opt_api_mcast_port; // current stratum... extern struct stratum_ctx stratum; @@ -416,15 +422,6 @@ static char *getmeminfo(char *params) /*****************************************************************************/ -/** - * Remote control allowed ? - * TODO: ip filters - */ -static bool check_remote_access(void) -{ - return (opt_api_remote > 0); -} - /** * Set pool by index (pools array in json config) * switchpool|1| @@ -433,8 +430,6 @@ static char *remote_switchpool(char *params) { bool ret = false; *buffer = '\0'; - if (!check_remote_access()) - return buffer; if (!params || strlen(params) == 0) { // rotate pool test ret = pool_switch_next(-1); @@ -457,8 +452,6 @@ static char *remote_seturl(char *params) { bool ret; *buffer = '\0'; - if (!check_remote_access()) - return buffer; if (!params || strlen(params) == 0) { // rotate pool test ret = pool_switch_next(-1); @@ -475,8 +468,6 @@ static char *remote_seturl(char *params) static char *remote_quit(char *params) { *buffer = '\0'; - if (!check_remote_access()) - return buffer; bye = 1; sprintf(buffer, "%s", "bye|"); return buffer; @@ -488,22 +479,23 @@ static char *gethelp(char *params); struct CMDS { const char *name; char *(*func)(char *); + bool iswritemode; } cmds[] = { - { "summary", getsummary }, - { "threads", getthreads }, - { "pool", getpoolnfo }, - { "histo", gethistory }, - { "hwinfo", gethwinfos }, - { "meminfo", getmeminfo }, - { "scanlog", getscanlog }, + { "summary", getsummary, false }, + { "threads", getthreads, false }, + { "pool", getpoolnfo, false }, + { "histo", gethistory, false }, + { "hwinfo", gethwinfos, false }, + { "meminfo", getmeminfo, false }, + { "scanlog", getscanlog, false }, /* remote functions */ - { "seturl", remote_seturl }, /* prefer switchpool, deprecated */ - { "switchpool", remote_switchpool }, - { "quit", remote_quit }, + { "seturl", remote_seturl, true }, /* prefer switchpool, deprecated */ + { "switchpool", remote_switchpool, true }, + { "quit", remote_quit, true }, /* keep it the last */ - { "help", gethelp }, + { "help", gethelp, false }, }; #define CMDMAX ARRAY_SIZE(cmds) @@ -675,6 +667,147 @@ static int websocket_handshake(SOCKETTYPE c, char *result, char *clientkey) return 0; } +/* + * Interpret --api-groups G:cmd1:cmd2:cmd3,P:cmd4,*,... + */ +static void setup_groups() +{ + const char *api_groups = opt_api_groups ? opt_api_groups : ""; + char *buf, *ptr, *next, *colon; + char group; + char commands[512]; + char cmdbuf[100]; + char *cmd; + bool addstar, did; + int i; + + buf = (char *)malloc(strlen(api_groups) + 1); + if (unlikely(!buf)) + proper_exit(1); //, "Failed to malloc ipgroups buf"); + + strcpy(buf, api_groups); + + next = buf; + // for each group defined + while (next && *next) { + ptr = next; + next = strchr(ptr, ','); + if (next) + *(next++) = '\0'; + + // Validate the group + if (*(ptr+1) != ':') { + colon = strchr(ptr, ':'); + if (colon) + *colon = '\0'; + proper_exit(1); //, "API invalid group name '%s'", ptr); + } + + group = GROUP(*ptr); + if (!VALIDGROUP(group)) + proper_exit(1); //, "API invalid group name '%c'", *ptr); + + if (group == PRIVGROUP) + proper_exit(1); //, "API group name can't be '%c'", PRIVGROUP); + + if (group == NOPRIVGROUP) + proper_exit(1); //, "API group name can't be '%c'", NOPRIVGROUP); + + if (apigroups[GROUPOFFSET(group)].commands != NULL) + proper_exit(1); //, "API duplicate group name '%c'", *ptr); + + ptr += 2; + + // Validate the command list (and handle '*') + cmd = &(commands[0]); + *(cmd++) = '|'; + *cmd = '\0'; + addstar = false; + while (ptr && *ptr) { + colon = strchr(ptr, ':'); + if (colon) + *(colon++) = '\0'; + + if (strcmp(ptr, "*") == 0) + addstar = true; + else { + did = false; + for (i = 0; cmds[i].name != NULL; i++) { + if (strcasecmp(ptr, cmds[i].name) == 0) { + did = true; + break; + } + } + if (did) { + // skip duplicates + sprintf(cmdbuf, "|%s|", cmds[i].name); + if (strstr(commands, cmdbuf) == NULL) { + strcpy(cmd, cmds[i].name); + cmd += strlen(cmds[i].name); + *(cmd++) = '|'; + *cmd = '\0'; + } + } else { + proper_exit(1); //, "API unknown command '%s' in group '%c'", ptr, group); + } + } + + ptr = colon; + } + + // * = allow all non-iswritemode commands + if (addstar) { + for (i = 0; cmds[i].name != NULL; i++) { + if (cmds[i].iswritemode == false) { + // skip duplicates + sprintf(cmdbuf, "|%s|", cmds[i].name); + if (strstr(commands, cmdbuf) == NULL) { + strcpy(cmd, cmds[i].name); + cmd += strlen(cmds[i].name); + *(cmd++) = '|'; + *cmd = '\0'; + } + } + } + } + + ptr = apigroups[GROUPOFFSET(group)].commands = (char *)malloc(strlen(commands) + 1); + if (unlikely(!ptr)) + proper_exit(1); //, "Failed to malloc group commands buf"); + + strcpy(ptr, commands); + } + + // Now define R (NOPRIVGROUP) as all non-iswritemode commands + cmd = &(commands[0]); + *(cmd++) = '|'; + *cmd = '\0'; + for (i = 0; cmds[i].name != NULL; i++) { + if (cmds[i].iswritemode == false) { + strcpy(cmd, cmds[i].name); + cmd += strlen(cmds[i].name); + *(cmd++) = '|'; + *cmd = '\0'; + } + } + + ptr = apigroups[GROUPOFFSET(NOPRIVGROUP)].commands = (char *)malloc(strlen(commands) + 1); + if (unlikely(!ptr)) + proper_exit(1); //, "Failed to malloc noprivgroup commands buf"); + + strcpy(ptr, commands); + + // W (PRIVGROUP) is handled as a special case since it simply means all commands + + free(buf); + return; +} + +/* + * Interpret [W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option + * special case of 0/0 allows /0 (means all IP addresses) + */ +#define ALLIPS "0/0" /* * N.B. IP4 addresses are by Definition 32bit big endian on all platforms */ @@ -724,7 +857,7 @@ static void setup_ipaccess() ipaccess[ips].group = group; - if (strcmp(ptr, ALLIP4) == 0) + if (strcmp(ptr, ALLIPS) == 0) ipaccess[ips].ip = ipaccess[ips].mask = 0; else { @@ -793,10 +926,178 @@ static bool check_connect(struct sockaddr_in *cli, char **connectaddr, char *gro return addrok; } +static void mcast() +{ + struct sockaddr_in listen; + struct ip_mreq grp; + struct sockaddr_in came_from; + time_t bindstart; + char *binderror; + SOCKETTYPE mcast_sock; + SOCKETTYPE reply_sock; + socklen_t came_from_siz; + char *connectaddr; + ssize_t rep; + int bound; + int count; + int reply_port; + bool addrok; + char group; + + char expect[] = "ccminer-"; // first 8 bytes constant + char *expect_code; + size_t expect_code_len; + char buf[1024]; + char replybuf[1024]; + + memset(&grp, 0, sizeof(grp)); + grp.imr_multiaddr.s_addr = inet_addr(opt_api_mcast_addr); + if (grp.imr_multiaddr.s_addr == INADDR_NONE) + proper_exit(1); //, "Invalid Multicast Address"); + grp.imr_interface.s_addr = INADDR_ANY; + + mcast_sock = socket(AF_INET, SOCK_DGRAM, 0); + + int optval = 1; + if (SOCKETFAIL(setsockopt(mcast_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)(&optval), sizeof(optval)))) { + applog(LOG_ERR, "API mcast setsockopt SO_REUSEADDR failed (%s)%s", strerror(errno), MUNAVAILABLE); + goto die; + } + + memset(&listen, 0, sizeof(listen)); + listen.sin_family = AF_INET; + listen.sin_addr.s_addr = INADDR_ANY; + listen.sin_port = htons(opt_api_mcast_port); + + // try for more than 1 minute ... in case the old one hasn't completely gone yet + bound = 0; + bindstart = time(NULL); + while (bound == 0) { + if (SOCKETFAIL(bind(mcast_sock, (struct sockaddr *)(&listen), sizeof(listen)))) { + binderror = strerror(errno);; + if ((time(NULL) - bindstart) > 61) + break; + else + sleep(30); + } + else + bound = 1; + } + + if (bound == 0) { + applog(LOG_ERR, "API mcast bind to port %d failed (%s)%s", opt_api_port, binderror, MUNAVAILABLE); + goto die; + } + + if (SOCKETFAIL(setsockopt(mcast_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)(&grp), sizeof(grp)))) { + applog(LOG_ERR, "API mcast join failed (%s)%s", strerror(errno), MUNAVAILABLE); + goto die; + } + + expect_code_len = sizeof(expect) + strlen(opt_api_mcast_code); + expect_code = (char *)malloc(expect_code_len + 1); + if (!expect_code) + proper_exit(1); //, "Failed to malloc mcast expect_code"); + snprintf(expect_code, expect_code_len + 1, "%s%s-", expect, opt_api_mcast_code); + + count = 0; + while (80085) { + sleep(1); + + count++; + came_from_siz = sizeof(came_from); + if (SOCKETFAIL(rep = recvfrom(mcast_sock, buf, sizeof(buf) - 1, + 0, (struct sockaddr *)(&came_from), &came_from_siz))) { + applog(LOG_DEBUG, "API mcast failed count=%d (%s) (%d)", + count, strerror(errno), (int)mcast_sock); + continue; + } + + addrok = check_connect(&came_from, &connectaddr, &group); + applog(LOG_DEBUG, "API mcast from %s - %s", + connectaddr, addrok ? "Accepted" : "Ignored"); + if (!addrok) + continue; + + buf[rep] = '\0'; + if (rep > 0 && buf[rep - 1] == '\n') + buf[--rep] = '\0'; + + applog(LOG_DEBUG, "API mcast request rep=%d (%s) from %s:%d", + (int)rep, buf, + inet_ntoa(came_from.sin_addr), + ntohs(came_from.sin_port)); + + if ((size_t)rep > expect_code_len && memcmp(buf, expect_code, expect_code_len) == 0) { + reply_port = atoi(&buf[expect_code_len]); + if (reply_port < 1 || reply_port > 65535) { + applog(LOG_DEBUG, "API mcast request ignored - invalid port (%s)", + &buf[expect_code_len]); + } + else { + applog(LOG_DEBUG, "API mcast request OK port %s=%d", + &buf[expect_code_len], reply_port); + + came_from.sin_port = htons(reply_port); + reply_sock = socket(AF_INET, SOCK_DGRAM, 0); + + snprintf(replybuf, sizeof(replybuf), + "ccm-%s-%d-%s", opt_api_mcast_code, opt_api_port, opt_api_mcast_des); + + rep = sendto(reply_sock, replybuf, strlen(replybuf) + 1, + 0, (struct sockaddr *)(&came_from), + sizeof(came_from)); + if (SOCKETFAIL(rep)) { + applog(LOG_DEBUG, "API mcast send reply failed (%s) (%d)", + strerror(errno), (int)reply_sock); + } + else { + applog(LOG_DEBUG, "API mcast send reply (%s) succeeded (%d) (%d)", + replybuf, (int)rep, (int)reply_sock); + } + + CLOSESOCKET(reply_sock); + } + } + else + applog(LOG_DEBUG, "API mcast request was no good"); + } + +die: + + CLOSESOCKET(mcast_sock); +} + +static void *mcast_thread(void *userdata) +{ + struct thr_info *mythr = (struct thr_info *)userdata; + + pthread_detach(pthread_self()); + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + mcast(); + + //PTH(mythr) = 0L; + + return NULL; +} + +void mcast_init() +{ + struct thr_info *thr; + + thr = (struct thr_info *)calloc(1, sizeof(*thr)); + if (!thr) + proper_exit(1); //, "Failed to calloc mcast thr"); + + if (unlikely(pthread_create(&thr->pth, NULL, mcast_thread, thr))) + proper_exit(1); //, "API mcast thread create failed"); +} + static void api() { const char *addr = opt_api_bind; - unsigned short port = (unsigned short) opt_api_listen; // 4068 + unsigned short port = (unsigned short) opt_api_port; // 4068 char buf[MYBUFSIZ]; int n, bound; char *connectaddr; @@ -814,11 +1115,13 @@ static void api() SOCKETTYPE c; SOCKETTYPE *apisock; - if (!opt_api_listen && opt_debug) { + if (!opt_api_port && opt_debug) { applog(LOG_DEBUG, "API disabled"); return; } + setup_groups(); + if (opt_api_allow) { setup_ipaccess(); if (ips == 0) { @@ -839,9 +1142,10 @@ static void api() memset(&serv, 0, sizeof(serv)); serv.sin_family = AF_INET; - serv.sin_addr.s_addr = inet_addr(addr); + serv.sin_addr.s_addr = inet_addr(addr); // TODO: allow bind to ip/interface if (serv.sin_addr.s_addr == (in_addr_t)INVINETADDR) { applog(LOG_ERR, "API initialisation 2 failed (%s)%s", strerror(errno), UNAVAILABLE); + // free(apisock); FIXME!! return; } @@ -869,7 +1173,7 @@ static void api() binderror = strerror(errno); if ((time(NULL) - bindstart) > 61) break; - else if (opt_api_listen == 4068) { + else if (opt_api_port == 4068) { /* when port is default one, use first available */ if (opt_debug) applog(LOG_DEBUG, "API bind to port %d failed, trying port %u", @@ -886,10 +1190,10 @@ static void api() } else { bound = 1; - if (opt_api_listen != port) { + if (opt_api_port != port) { applog(LOG_WARNING, "API bind to port %d failed - using port %u", - opt_api_listen, (uint32_t) port); - opt_api_listen = port; + opt_api_port, (uint32_t)port); + opt_api_port = port; } } } @@ -907,6 +1211,18 @@ static void api() return; } + if (opt_api_allow) + applog(LOG_WARNING, "API running in IP access mode on port %d (%d)", port, (int)*apisock); + else { + if (strcmp(opt_api_bind, "127.0.0.1")) + applog(LOG_WARNING, "API running in UNRESTRICTED read access mode on port %d (%d)", port, (int)*apisock); + else + applog(LOG_WARNING, "API running in local read access mode on port %d (%d)", port, (int)*apisock); + } + + if (opt_api_mcast) + mcast_init(); + buffer = (char *) calloc(1, MYBUFSIZ + 1); counter = 0; diff --git a/ccminer.cpp b/ccminer.cpp index bbdd3da..aba98d8 100644 --- a/ccminer.cpp +++ b/ccminer.cpp @@ -211,12 +211,20 @@ double opt_resume_rate = -1.; int opt_statsavg = 30; +#define API_MCAST_CODE "FTW" +#define API_MCAST_ADDR "224.0.0.75" + // strdup on char* to allow a common free() if used static char* opt_syslog_pfx = strdup(PROGRAM_NAME); char *opt_api_bind = strdup("127.0.0.1"); /* 0.0.0.0 for all ips */ -char *opt_api_allow = NULL; /* unimplemented */ -int opt_api_remote = 0; -int opt_api_listen = 4068; /* 0 to disable */ +int opt_api_port = 4068; /* 0 to disable */ +char *opt_api_allow = NULL; +char *opt_api_groups = NULL; +bool opt_api_mcast = false; +char *opt_api_mcast_addr = strdup(API_MCAST_ADDR); +char *opt_api_mcast_code = strdup(API_MCAST_CODE); +char *opt_api_mcast_des = strdup(""); +int opt_api_mcast_port = 4068; bool opt_stratum_stats = false; @@ -361,6 +369,13 @@ struct option options[] = { { "algo", 1, NULL, 'a' }, { "api-bind", 1, NULL, 'b' }, { "api-remote", 0, NULL, 1030 }, + { "api-allow", 1, NULL, 1031 }, + { "api-groups", 1, NULL, 1032 }, + { "api-mcast", 0, NULL, 1033 }, + { "api-mcast-addr", 1, NULL, 1034 }, + { "api-mcast-code", 1, NULL, 1035 }, + { "api-mcast-port", 1, NULL, 1036 }, + { "api-mcast-des", 1, NULL, 1037 }, { "background", 0, NULL, 'B' }, { "benchmark", 0, NULL, 1005 }, { "cert", 1, NULL, 1001 }, @@ -2977,7 +2992,7 @@ void parse_arg(int key, char *arg) opt_api_bind = strdup(arg); opt_api_bind[p - arg] = '\0'; } - opt_api_listen = atoi(p + 1); + opt_api_port = atoi(p + 1); } else if (arg && strstr(arg, ".")) { /* ip only */ @@ -2986,12 +3001,39 @@ void parse_arg(int key, char *arg) } else if (arg) { /* port or 0 to disable */ - opt_api_listen = atoi(arg); + opt_api_port = atoi(arg); } break; case 1030: /* --api-remote */ - opt_api_remote = 1; + opt_api_allow = strdup("0/0"); + break; + case 1031: /* --api-allow */ + free(opt_api_allow); + opt_api_allow = strdup(arg); + break; + case 1032: /* --api-groups */ + free(opt_api_groups); + opt_api_groups = strdup(arg); + break; + case 1033: /* --api-mcast */ + opt_api_mcast = true; break; + case 1034: /* --api-mcast-addr */ + free(opt_api_mcast_addr); + opt_api_mcast_addr = strdup(arg); + case 1035: /* --api-mcast-code */ + free(opt_api_mcast_code); + opt_api_mcast_code = strdup(arg); + break; + case 1036: /* --api-mcast-des */ + free(opt_api_mcast_des); + opt_api_mcast_des = strdup(arg); + break; + case 1037: /* --api-mcast-port */ + v = atoi(arg); + if (v < 1 || v > 65535) // sanity check + show_usage_and_exit(1); + opt_api_mcast_port = v; case 'B': opt_background = true; break; @@ -4002,7 +4044,7 @@ int main(int argc, char *argv[]) } #endif - if (opt_api_listen) { + if (opt_api_port) { /* api thread */ api_thr_id = opt_n_threads + 3; thr = &thr_info[api_thr_id];