Browse Source

Merge pull request #107 from kanoi/master

Allow API to restrict access by IP address + other commits
Con Kolivas 14 years ago
parent
commit
8c609579f4
6 changed files with 194 additions and 22 deletions
  1. 19 5
      README
  2. 134 11
      api.c
  3. 13 0
      cgminer.c
  4. 1 1
      device-cpu.c
  5. 1 0
      miner.h
  6. 26 5
      miner.php

+ 19 - 5
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 '='
 

+ 134 - 11
api.c

@@ -126,6 +126,10 @@
 	#ifndef SHUT_RDWR
 	#define SHUT_RDWR SD_BOTH
 	#endif
+
+	#ifndef in_addr_t
+	#define in_addr_t uint32_t
+	#endif
 #endif
 
 // Big enough for largest API request
@@ -343,6 +347,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
@@ -1205,6 +1217,11 @@ static void tidyup()
 		sock = INVSOCK;
 	}
 
+	if (ipaccess != NULL) {
+		free(ipaccess);
+		ipaccess = NULL;
+	}
+
 	if (msg_buffer != NULL) {
 		free(msg_buffer);
 		msg_buffer = NULL;
@@ -1216,6 +1233,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];
@@ -1248,6 +1348,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);
@@ -1258,9 +1367,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;
 		}
@@ -1296,10 +1405,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);
@@ -1311,11 +1424,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) {

+ 13 - 0
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;
@@ -536,6 +537,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);
@@ -575,6 +583,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"),
@@ -2301,6 +2312,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);

+ 1 - 1
device-cpu.c

@@ -23,9 +23,9 @@
 
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <sys/wait.h>
 
 #ifndef WIN32
+#include <sys/wait.h>
 #include <sys/resource.h>
 #endif
 #include <libgen.h>

+ 1 - 0
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;

+ 26 - 5
miner.php

@@ -17,8 +17,9 @@ td.sta { color:green; font-family:verdana,arial,sans; font-size:14pt; }
 </head><body bgcolor=#ecffff>
 <script type='text/javascript'>
 function pr(a,m){if(m!=null){if(!confirm(m+'?'))return}window.location="<? echo $here ?>"+a}
-function prs(a){var c=a.substr(3);var z=c.split('|',2);var m=z[0].substr(0,1).toUpperCase()+z[0].substr(1)+' GPU '+z[1];pr('?arg='+a,m)}
-function prs2(a,n){var v=document.getElementById('gi'+n).value;var c=a.substr(3);var z=c.split('|',2);var m='Set GPU '+z[1]+' '+z[0].substr(0,1).toUpperCase()+z[0].substr(1)+' to '+v;pr('?arg='+a+','+v,m)}
+function prc(a,m){pr('?arg='+a,m)}
+function prs(a){var c=a.substr(3);var z=c.split('|',2);var m=z[0].substr(0,1).toUpperCase()+z[0].substr(1)+' GPU '+z[1];prc(a,m)}
+function prs2(a,n){var v=document.getElementById('gi'+n).value;var c=a.substr(3);var z=c.split('|',2);var m='Set GPU '+z[1]+' '+z[0].substr(0,1).toUpperCase()+z[0].substr(1)+' to '+v;prc(a+','+v,m)}
 </script>
 <table width=100% height=100% border=0 cellpadding=0 cellspacing=0 summary='Mine'>
 <tr><td align=center valign=top>
@@ -201,7 +202,7 @@ function fmt($section, $name, $value)
  return $value;
 }
 #
-function details($list)
+function details($cmd, $list)
 {
  $stas = array('S' => 'Success', 'W' => 'Warning', 'I' => 'Informational', 'E' => 'Error', 'F' => 'Fatal');
 
@@ -243,6 +244,9 @@ function details($list)
 			echo "<td valign=bottom class=h>$name</td>";
 		}
 
+		if ($cmd == 'pools')
+			echo "<td valign=bottom class=h>Switch</td>";
+
 		echo '</tr>';
 
 		break;
@@ -257,6 +261,23 @@ function details($list)
 	foreach ($values as $name => $value)
 		echo '<td>'.fmt($section, $name, $value).'</td>';
 
+	if ($cmd == 'pools')
+	{
+		echo '<td>';
+
+		reset($values);
+		$pool = current($values);
+		if ($pool === false)
+			echo '&nbsp;';
+		else
+		{
+			echo "<input type=button value='Pool $pool'";
+			echo " onclick='prc(\"switchpool|$pool\",\"Switch to Pool $pool\")'>";
+		}
+
+		echo '</td>';
+	}
+
 	echo '</tr>';
  }
  echo $te;
@@ -369,7 +390,7 @@ function process($cmds, $rd, $ro)
 	}
 	else
 	{
-		details($process);
+		details($cmd, $process);
 		echo '<tr><td><br><br></td></tr>';
 		if ($cmd == 'devs')
 			$devs = $process;
@@ -389,7 +410,7 @@ function display()
  echo "<tr><td><table cellpadding=0 cellspacing=0 border=0><tr><td>";
  echo "<input type=button value='Refresh' onclick='pr(\"\",null)'>";
  echo "</td><td width=100%>&nbsp;</td><td>";
- echo "<input type=button value='Quit' onclick='pr(\"?arg=quit\",\"Quit CGMiner\")'>";
+ echo "<input type=button value='Quit' onclick='prc(\"quit\",\"Quit CGMiner\")'>";
  echo "</td></tr></table></td></tr>";
 
  $arg = trim(getparam('arg', true));