Commit f19e67fe3a4d06871853a8b2e38c4ce7813a8431

Kano 2012-02-08T19:06:24

Allow API to restrict access by IP address

diff --git a/README b/README
index 47e0c78..b592c4b 100644
--- a/README
+++ b/README
@@ -119,6 +119,8 @@ Usage instructions:  Run "cgminer --help" to see options:
 
 Usage: . [-atDdGCgIKklmpPQqrRsTouvwOchnV] 
 Options for both config file and command line:
+--api-allow         Allow API access (if enabled) only to the given list of IP[/Prefix] address[/subnets]
+                    This overrides --api-network and you must specify 127.0.0.1 if it is required
 --api-description   Description placed in the API status header (default: cgminer version)
 --api-listen        Listen for API requests (default: disabled)
 --api-network       Allow API (if enabled) to listen on/for any address (default: only 127.0.0.1)
@@ -526,15 +528,24 @@ cgminer shuts down because of this.
 
 ---
 
-API
+RPC API
 
 If you start cgminer with the "--api-listen" option, it will listen on a
 simple TCP/IP socket for single string API requests from the same machine
 running cgminer and reply with a string and then close the socket each time
-Also, if you add the "--api-network" option, it will accept API requests
-from any network attached computer.
+If you add the "--api-network" option, it will accept API requests from any
+network attached computer.
 
-The request can be either simple text or JSON.
+You can specify IP addresses/prefixes that are only allowed to access the API
+with the "--api-access" option e.g. --api-access 192.168.0.1,10.0.0/24
+will allow 192.168.0.1 or any address matching 10.0.0.*, but nothing else
+IP addresses are automatically padded with extra '.0's as needed
+Without a /prefix is the same as specifying /32
+Using the "--api-access" option overides the "--api-network" option if they
+are both specified
+With "--api-access", 127.0.0.1 is not by default given access unless specified
+
+The RPC API request can be either simple text or JSON.
 
 If the request is JSON (starts with '{'), it will reply with a JSON formatted
 response, otherwise it replies with text formatted as described further below.
@@ -544,7 +555,7 @@ The JSON request format required is '{"command":"CMD","parameter":"PARAM"}'
 where "CMD" is from the "Request" column below and "PARAM" would be e.g.
 the CPU/GPU number if required.
 
-An example request in both formats:
+An example request in both formats to set GPU 0 fan to 80%:
   gpufan|0,80
   {"command":"gpufan","parameter":"0,80"}
 
@@ -652,6 +663,9 @@ The list of requests and replies are:
 When you enable, disable or restart a GPU, you will also get Thread messages in
 the cgminer status window
 
+When you switch to a different pool to the current one, you will get a
+'Switching to URL' message in the cgminer status windows
+
 Obviously, the JSON format is simply just the names as given before the '='
 with the values after the '='
 
diff --git a/api.c b/api.c
index a65c906..6682873 100644
--- a/api.c
+++ b/api.c
@@ -342,6 +342,14 @@ struct CODES {
 static int bye = 0;
 static bool ping = true;
 
+struct IP4ACCESS {
+	in_addr_t ip;
+	in_addr_t mask;
+};
+
+static struct IP4ACCESS *ipaccess = NULL;
+static int ips = 0;
+
 // All replies (except BYE) start with a message
 //  thus for JSON, message() inserts JSON_START at the front
 //  and send_result() adds JSON_END at the end
@@ -1204,6 +1212,11 @@ static void tidyup()
 		sock = INVSOCK;
 	}
 
+	if (ipaccess != NULL) {
+		free(ipaccess);
+		ipaccess = NULL;
+	}
+
 	if (msg_buffer != NULL) {
 		free(msg_buffer);
 		msg_buffer = NULL;
@@ -1215,6 +1228,89 @@ static void tidyup()
 	}
 }
 
+/*
+ * Interpret IP[/Prefix][,IP2[/Prefix2][,...]] --api-allow option
+ *
+ * N.B. IP4 addresses are by Definition 32bit big endian on all platforms
+ */
+static void setup_ipaccess()
+{
+	char *buf, *ptr, *comma, *slash, *dot;
+	int ipcount, mask, octet, i;
+
+	buf = malloc(strlen(opt_api_allow) + 1);
+	if (unlikely(!buf))
+		quit(1, "Failed to malloc ipaccess buf");
+
+	strcpy(buf, opt_api_allow);
+
+	ipcount = 1;
+	ptr = buf;
+	while (*ptr)
+		if (*(ptr++) == ',')
+			ipcount++;
+
+	// possibly more than needed, but never less
+	ipaccess = calloc(ipcount, sizeof(struct IP4ACCESS));
+	if (unlikely(!ipaccess))
+		quit(1, "Failed to calloc ipaccess");
+
+	ips = 0;
+	ptr = buf;
+	while (ptr && *ptr) {
+		while (*ptr == ' ' || *ptr == '\t')
+			ptr++;
+
+		if (*ptr == ',') {
+			ptr++;
+			continue;
+		}
+
+		comma = strchr(ptr, ',');
+		if (comma)
+			*(comma++) = '\0';
+
+		slash = strchr(ptr, '/');
+		if (!slash)
+			ipaccess[ips].mask = 0xffffffff;
+		else {
+			*(slash++) = '\0';
+			mask = atoi(slash);
+			if (mask < 1 || mask > 32)
+				goto popipo; // skip invalid/zero
+
+			ipaccess[ips].mask = 0;
+			while (mask-- >= 0) {
+				octet = 1 << (mask % 8);
+				ipaccess[ips].mask |= (octet << (8 * (mask >> 3)));
+			}
+		}
+
+		ipaccess[ips].ip = 0; // missing default to '.0'
+		for (i = 0; ptr && (i < 4); i++) {
+			dot = strchr(ptr, '.');
+			if (dot)
+				*(dot++) = '\0';
+
+			octet = atoi(ptr);
+			if (octet < 0 || octet > 0xff)
+				goto popipo; // skip invalid
+
+			ipaccess[ips].ip |= (octet << (i * 8));
+
+			ptr = dot;
+		}
+
+		ipaccess[ips].ip &= ipaccess[ips].mask;
+
+		ips++;
+popipo:
+		ptr = comma;
+	}
+
+	free(buf);
+}
+
 void api(void)
 {
 	char buf[BUFSIZ];
@@ -1247,6 +1343,15 @@ void api(void)
 		return;
 	}
 
+	if (opt_api_allow) {
+		setup_ipaccess();
+
+		if (ips == 0) {
+			applog(LOG_WARNING, "API not running (no valid IPs specified)%s", UNAVAILABLE);
+			return;
+		}
+	}
+
 	sock = socket(AF_INET, SOCK_STREAM, 0);
 	if (sock == INVSOCK) {
 		applog(LOG_ERR, "API1 initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE);
@@ -1257,9 +1362,9 @@ void api(void)
 
 	serv.sin_family = AF_INET;
 
-	if (!opt_api_network) {
+	if (!opt_api_allow && !opt_api_network) {
 		serv.sin_addr.s_addr = inet_addr(localaddr);
-		if (serv.sin_addr.s_addr == INVINETADDR) {
+		if (serv.sin_addr.s_addr == (in_addr_t)INVINETADDR) {
 			applog(LOG_ERR, "API2 initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE);
 			return;
 		}
@@ -1295,10 +1400,14 @@ void api(void)
 		return;
 	}
 
-	if (opt_api_network)
-		applog(LOG_WARNING, "API running in UNRESTRICTED access mode");
-	else
-		applog(LOG_WARNING, "API running in restricted access mode");
+	if (opt_api_allow)
+		applog(LOG_WARNING, "API running in IP access mode");
+	else {
+		if (opt_api_network)
+			applog(LOG_WARNING, "API running in UNRESTRICTED access mode");
+		else
+			applog(LOG_WARNING, "API running in local access mode");
+	}
 
 	io_buffer = malloc(MYBUFSIZ+1);
 	msg_buffer = malloc(MYBUFSIZ+1);
@@ -1310,11 +1419,21 @@ void api(void)
 			goto die;
 		}
 
-		if (opt_api_network)
-			addrok = true;
-		else {
-			connectaddr = inet_ntoa(cli.sin_addr);
-			addrok = (strcmp(connectaddr, localaddr) == 0);
+		addrok = false;
+		if (opt_api_allow) {
+			for (i = 0; i < ips; i++) {
+				if ((cli.sin_addr.s_addr & ipaccess[i].mask) == ipaccess[i].ip) {
+					addrok = true;
+					break;
+				}
+			}
+		} else {
+			if (opt_api_network)
+				addrok = true;
+			else {
+				connectaddr = inet_ntoa(cli.sin_addr);
+				addrok = (strcmp(connectaddr, localaddr) == 0);
+			}
 		}
 
 		if (opt_debug) {
diff --git a/cgminer.c b/cgminer.c
index cd79a19..2b8f3e9 100644
--- a/cgminer.c
+++ b/cgminer.c
@@ -127,6 +127,7 @@ static bool opt_fail_only;
 bool opt_autofan;
 bool opt_autoengine;
 bool opt_noadl;
+char *opt_api_allow = NULL;
 char *opt_api_description = PACKAGE_STRING;
 int opt_api_port = 4028;
 bool opt_api_listen = false;
@@ -535,6 +536,13 @@ static char *set_schedtime(const char *arg, struct schedtime *st)
 	return NULL;
 }
 
+static char *set_api_allow(const char *arg)
+{
+	opt_set_charp(arg, &opt_api_allow);
+
+	return NULL;
+}
+
 static char *set_api_description(const char *arg)
 {
 	opt_set_charp(arg, &opt_api_description);
@@ -574,6 +582,9 @@ static struct opt_table opt_config_table[] = {
 #endif
 		),
 #endif
+	OPT_WITH_ARG("--api-allow",
+		     set_api_allow, NULL, NULL,
+		     "Allow API access only to the given list of IP[/Prefix] addresses[/subnets]"),
 	OPT_WITH_ARG("--api-description",
 		     set_api_description, NULL, NULL,
 		     "Description placed in the API status header, default: cgminer version"),
@@ -2295,6 +2306,8 @@ void write_config(FILE *fcfg)
 		for (i = 0; i < nDevs; i++)
 			if (gpus[i].enabled)
 				fprintf(fcfg, ",\n\"device\" : \"%d\"", i);
+	if (opt_api_allow != NULL)
+		fprintf(fcfg, ",\n\"api-allow\" : \"%s\"", opt_api_allow);
 	if (strcmp(opt_api_description, PACKAGE_STRING) != 0)
 		fprintf(fcfg, ",\n\"api-description\" : \"%s\"", opt_api_description);
 	fputs("\n}", fcfg);
diff --git a/miner.h b/miner.h
index 44e6a53..7fbd843 100644
--- a/miner.h
+++ b/miner.h
@@ -446,6 +446,7 @@ extern char *cgminer_path;
 extern bool opt_autofan;
 extern bool opt_autoengine;
 extern bool use_curses;
+extern char *opt_api_allow;
 extern char *opt_api_description;
 extern int opt_api_port;
 extern bool opt_api_listen;