Browse Source

Merge pull request #124 from kanoi/master

API commits - IP 0/0 means all, add "Log Interval" to config, restrict access to modify commands
Con Kolivas 14 years ago
parent
commit
2ca8d38e0e
4 changed files with 161 additions and 84 deletions
  1. 49 17
      README
  2. 110 65
      api.c
  3. 1 1
      cgminer.c
  4. 1 1
      miner.h

+ 49 - 17
README

@@ -119,10 +119,13 @@ 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]
+--api-allow         Allow API access (if enabled) only to the given list of [W:]IP[/Prefix] address[/subnets]
                     This overrides --api-network and you must specify 127.0.0.1 if it is required
+                    W: in front of the IP address gives that address privileged access to all api commands
 --api-description   Description placed in the API status header (default: cgminer version)
 --api-listen        Listen for API requests (default: disabled)
+                    By default any command that does not just display data returns access denied
+                    See --api-allow to overcome this
 --api-network       Allow API (if enabled) to listen on/for any address (default: only 127.0.0.1)
 --api-port          Port number of miner API (default: 4028)
 --auto-fan          Automatically adjust all GPU fan speeds to maintain a target temperature
@@ -537,11 +540,22 @@ running cgminer and reply with a string and then close the socket each time
 If you add the "--api-network" option, it will accept API requests from any
 network attached computer.
 
+You can only access the comands that reply with data in this mode.
+By default, you cannot access any privileged command that affects the miner -
+you will receive an access denied status message see --api-access below.
+
 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
+with the "--api-access" option e.g. --api-access W: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
+0/0 means all IP addresses.
+The 'W:' on the front gives that address/subnet privileged access to commands
+that modify cgminer.
+Without it those commands return an access denied status.
+Privileged access is checked in the order the IP addresses were supplied to
+"--api-access"
+The first match determines the privilege level.
 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
@@ -584,7 +598,9 @@ The STATUS section is:
    This defaults to the cgminer version but is the value of --api-description
    if it was specified at runtime.
 
-The list of requests and replies are:
+For API version 1.2:
+
+The list of requests - a (*) means it requires privileged access - and replies are:
 
  Request       Reply Section  Details
  -------       -------------  -------
@@ -597,7 +613,8 @@ The list of requests and replies are:
                               Pool Count=N, <- the number of Pools
                               ADL=X, <- Y or N if ADL is compiled in the code
                               ADL in use=X, <- Y or N if any GPU has ADL
-                              Strategy=Name| <- the current pool strategy
+                              Strategy=Name, <- the current pool strategy
+                              Log Interval=N| <- log interval (--log N)
 
  summary       SUMMARY        The status summary of the miner
                               e.g. Elapsed=NNN,Found Blocks=N,Getworks=N,...|
@@ -625,42 +642,58 @@ The list of requests and replies are:
  cpucount      CPUS           Count=N| <- the number of CPUs
                               Always returns 0 if CPU mining is disabled
 
- switchpool|N  none           There is no reply section just the STATUS section
+ switchpool|N (*)
+               none           There is no reply section just the STATUS section
                               stating the results of switching pool N to the
                               highest priority (the pool is also enabled)
                               The Msg includes the pool URL
 
- gpuenable|N   none           There is no reply section just the STATUS section
+ gpuenable|N (*)
+               none           There is no reply section just the STATUS section
                               stating the results of the enable request
 
- gpudisable|N  none           There is no reply section just the STATUS section
+ gpudisable|N (*)
+               none           There is no reply section just the STATUS section
                               stating the results of the disable request
 
- gpurestart|N  none           There is no reply section just the STATUS section
+ gpurestart|N (*)
+               none           There is no reply section just the STATUS section
                               stating the results of the restart request
 
- gpuintensity|N,I  none       There is no reply section just the STATUS section
+ gpuintensity|N,I (*)
+               none           There is no reply section just the STATUS section
                               stating the results of setting GPU N intensity to I
 
- gpumem|N,V    none           There is no reply section just the STATUS section
+ gpumem|N,V (*)
+               none           There is no reply section just the STATUS section
                               stating the results of setting GPU N memoryclock to V MHz
 
- gpuengine|N,V none           There is no reply section just the STATUS section
+ gpuengine|N,V (*)
+               none           There is no reply section just the STATUS section
                               stating the results of setting GPU N clock to V MHz
 
- gpufan|N,V    none           There is no reply section just the STATUS section
+ gpufan|N,V (*)
+                none           There is no reply section just the STATUS section
                               stating the results of setting GPU N fan speed to V%
 
- gpuvddc|N,V   none           There is no reply section just the STATUS section
+ gpuvddc|N,V (*)
+               none           There is no reply section just the STATUS section
                               stating the results of setting GPU N vddc to V
 
- save|filename none           There is no reply section just the STATUS section
+ save|filename (*)
+               none           There is no reply section just the STATUS section
                               stating success or failure saving the cgminer config
                               to filename
 
- quit          none           There is no status section but just a single "BYE|"
+ quit (*)      none           There is no status section but just a single "BYE|"
                               reply before cgminer quits
 
+ privileged (*)
+               none           There is no reply section just the STATUS section
+                              stating an error if you do not have privileged access
+                              to the API and success if you do have privilege
+                              The command doesn't change anything in cgminer
+
 When you enable, disable or restart a GPU, you will also get Thread messages in
 the cgminer status window
 
@@ -694,8 +727,7 @@ api-example.c - a 'C' program to access the API (with source code)
   api-example summary 127.0.0.1 4028
 
 miner.php - an example web page to access the API
- This includes buttons to enable, disable and restart the GPUs and also to
- quit cgminer
+ This includes buttons and inputs to attempt access to the privileged commands
  You must modify the 2 lines near the top to change where it looks for cgminer
   $miner = '127.0.0.1'; # hostname or IP address
   $port = 4028;

+ 110 - 65
api.c

@@ -11,6 +11,7 @@
 #include "config.h"
 
 #include <stdio.h>
+#include <ctype.h>
 #include <stdlib.h>
 #include <string.h>
 #include <stdbool.h>
@@ -151,7 +152,7 @@ static const char *COMMA = ",";
 static const char SEPARATOR = '|';
 static const char GPUSEP = ',';
 
-static const char *APIVERSION = "1.1";
+static const char *APIVERSION = "1.2";
 static const char *DEAD = "Dead";
 static const char *SICK = "Sick";
 static const char *NOSTART = "NoStart";
@@ -256,6 +257,8 @@ static const char *JSON_PARAMETER = "parameter";
 #define MSG_MISFN 42
 #define MSG_BADFN 43
 #define MSG_SAVED 44
+#define MSG_ACCDENY 45
+#define MSG_ACCOK 46
 
 enum code_severity {
 	SEVERITY_ERR,
@@ -341,15 +344,19 @@ struct CODES {
  { SEVERITY_ERR,   MSG_MISFN,	PARAM_NONE,	"Missing save filename parameter" },
  { SEVERITY_ERR,   MSG_BADFN,	PARAM_STR,	"Can't open or create save file '%s'" },
  { SEVERITY_ERR,   MSG_SAVED,	PARAM_STR,	"Configuration saved to file '%s'" },
+ { SEVERITY_ERR,   MSG_ACCDENY,	PARAM_STR,	"Access denied to '%s' command" },
+ { SEVERITY_SUCC,  MSG_ACCOK,	PARAM_NONE,	"Privileged access OK" },
  { SEVERITY_FAIL, 0, 0, NULL }
 };
 
+static int my_thr_id = 0;
 static int bye = 0;
 static bool ping = true;
 
 struct IP4ACCESS {
 	in_addr_t ip;
 	in_addr_t mask;
+	bool writemode;
 };
 
 static struct IP4ACCESS *ipaccess = NULL;
@@ -496,9 +503,9 @@ static void minerconfig(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
 	strcpy(io_buffer, message(MSG_MINECON, 0, NULL, isjson));
 
 	if (isjson)
-		sprintf(buf, "," JSON_MINECON "{\"GPU Count\":%d,\"CPU Count\":%d,\"Pool Count\":%d,\"ADL\":\"%s\",\"ADL in use\":\"%s\",\"Strategy\":\"%s\"}" JSON_CLOSE, nDevs, cpucount, total_pools, adl, adlinuse, strategies[pool_strategy].s);
+		sprintf(buf, "," JSON_MINECON "{\"GPU Count\":%d,\"CPU Count\":%d,\"Pool Count\":%d,\"ADL\":\"%s\",\"ADL in use\":\"%s\",\"Strategy\":\"%s\",\"Log Interval\":\"%d\"}" JSON_CLOSE, nDevs, cpucount, total_pools, adl, adlinuse, strategies[pool_strategy].s, opt_log_interval);
 	else
-		sprintf(buf, _MINECON ",GPU Count=%d,CPU Count=%d,Pool Count=%d,ADL=%s,ADL in use=%s,Strategy=%s%c", nDevs, cpucount, total_pools, adl, adlinuse, strategies[pool_strategy].s, SEPARATOR);
+		sprintf(buf, _MINECON ",GPU Count=%d,CPU Count=%d,Pool Count=%d,ADL=%s,ADL in use=%s,Strategy=%s,Log Interval=%d%c", nDevs, cpucount, total_pools, adl, adlinuse, strategies[pool_strategy].s, opt_log_interval, SEPARATOR);
 
 	strcat(io_buffer, buf);
 }
@@ -1129,9 +1136,17 @@ void doquit(SOCKETTYPE c, __maybe_unused char *param, bool isjson)
 	send_result(c, isjson);
 	*io_buffer = '\0';
 	bye = 1;
+
+        PTH(&thr_info[my_thr_id]) = 0L;
+
 	kill_work();
 }
 
+void privileged(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+{
+	strcpy(io_buffer, message(MSG_ACCOK, 0, NULL, isjson));
+}
+
 void dosave(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 {
 	FILE *fcfg;
@@ -1156,30 +1171,32 @@ void dosave(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 struct CMDS {
 	char *name;
 	void (*func)(SOCKETTYPE, char *, bool);
+	bool requires_writemode;
 } cmds[] = {
-	{ "version",		apiversion },
-	{ "config",		minerconfig },
-	{ "devs",		devstatus },
-	{ "pools",		poolstatus },
-	{ "summary",		summary },
-	{ "gpuenable",		gpuenable },
-	{ "gpudisable",		gpudisable },
-	{ "gpurestart",		gpurestart },
-	{ "gpu",		gpudev },
+	{ "version",		apiversion,	false },
+	{ "config",		minerconfig,	false },
+	{ "devs",		devstatus,	false },
+	{ "pools",		poolstatus,	false },
+	{ "summary",		summary,	false },
+	{ "gpuenable",		gpuenable,	true },
+	{ "gpudisable",		gpudisable,	true },
+	{ "gpurestart",		gpurestart,	true },
+	{ "gpu",		gpudev,		false },
 #ifdef WANT_CPUMINE
-	{ "cpu",		cpudev },
+	{ "cpu",		cpudev,		false },
 #endif
-	{ "gpucount",		gpucount },
-	{ "cpucount",		cpucount },
-	{ "switchpool",		switchpool },
-	{ "gpuintensity",	gpuintensity },
-	{ "gpumem",		gpumem},
-	{ "gpuengine",		gpuengine},
-	{ "gpufan",		gpufan},
-	{ "gpuvddc",		gpuvddc},
-	{ "save",		dosave },
-	{ "quit",		doquit },
-	{ NULL,			NULL }
+	{ "gpucount",		gpucount,	false },
+	{ "cpucount",		cpucount,	false },
+	{ "switchpool",		switchpool,	true },
+	{ "gpuintensity",	gpuintensity,	true },
+	{ "gpumem",		gpumem,		true },
+	{ "gpuengine",		gpuengine,	true },
+	{ "gpufan",		gpufan,		true },
+	{ "gpuvddc",		gpuvddc,	true },
+	{ "save",		dosave,		true },
+	{ "quit",		doquit,		true },
+	{ "privileged",		privileged,	true },
+	{ NULL,			NULL,		false }
 };
 
 static void send_result(SOCKETTYPE c, bool isjson)
@@ -1192,16 +1209,16 @@ static void send_result(SOCKETTYPE c, bool isjson)
 
 	len = strlen(io_buffer);
 
-	applog(LOG_DEBUG, "DBG: send reply: (%d) '%.10s%s'", len+1, io_buffer, len > 10 ? "..." : "");
+	applog(LOG_DEBUG, "API: send reply: (%d) '%.10s%s'", len+1, io_buffer, len > 10 ? "..." : "");
 
 	// ignore failure - it's closed immediately anyway
 	n = send(c, io_buffer, len+1, 0);
 
 	if (opt_debug) {
 		if (SOCKETFAIL(n))
-			applog(LOG_DEBUG, "DBG: send failed: %s", SOCKERRMSG);
+			applog(LOG_DEBUG, "API: send failed: %s", SOCKERRMSG);
 		else
-			applog(LOG_DEBUG, "DBG: sent %d", n);
+			applog(LOG_DEBUG, "API: sent %d", n);
 	}
 
 }
@@ -1233,14 +1250,18 @@ static void tidyup()
 }
 
 /*
- * Interpret IP[/Prefix][,IP2[/Prefix2][,...]] --api-allow option
- *
+ * Interpret [R|W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option
+ *	special case of 0/0 allows /0 (means all IP addresses)
+ */
+#define ALLIP4 "0/0"
+/*
  * 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;
+	bool writemode;
 
 	buf = malloc(strlen(opt_api_allow) + 1);
 	if (unlikely(!buf))
@@ -1274,38 +1295,53 @@ static void setup_ipaccess()
 		if (comma)
 			*(comma++) = '\0';
 
-		slash = strchr(ptr, '/');
-		if (!slash)
-			ipaccess[ips].mask = 0xffffffff;
+		writemode = false;
+
+		if (isalpha(*ptr) && *(ptr+1) == ':') {
+			if (tolower(*ptr) == 'w')
+				writemode = true;
+
+			ptr += 2;
+		}
+
+		ipaccess[ips].writemode = writemode;
+
+		if (strcmp(ptr, ALLIP4) == 0)
+			ipaccess[ips].ip = ipaccess[ips].mask = 0;
 		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)));
+			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';
+			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
+				octet = atoi(ptr);
+				if (octet < 0 || octet > 0xff)
+					goto popipo; // skip invalid
 
-			ipaccess[ips].ip |= (octet << (i * 8));
+				ipaccess[ips].ip |= (octet << (i * 8));
 
-			ptr = dot;
-		}
+				ptr = dot;
+			}
 
-		ipaccess[ips].ip &= ipaccess[ips].mask;
+			ipaccess[ips].ip &= ipaccess[ips].mask;
+		}
 
 		ips++;
 popipo:
@@ -1315,7 +1351,7 @@ popipo:
 	free(buf);
 }
 
-void api(void)
+void api(int api_thr_id)
 {
 	char buf[BUFSIZ];
 	char param_buf[BUFSIZ];
@@ -1332,6 +1368,7 @@ void api(void)
 	char *cmd;
 	char *param;
 	bool addrok;
+	bool writemode;
 	json_error_t json_err;
 	json_t *json_config;
 	json_t *json_val;
@@ -1339,6 +1376,8 @@ void api(void)
 	bool did;
 	int i;
 
+	my_thr_id = api_thr_id;
+
 	/* This should be done first to ensure curl has already called WSAStartup() in windows */
 	sleep(opt_log_interval);
 
@@ -1423,27 +1462,27 @@ void api(void)
 			goto die;
 		}
 
+		connectaddr = inet_ntoa(cli.sin_addr);
+
 		addrok = false;
+		writemode = 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;
+					writemode = ipaccess[i].writemode;
 					break;
 				}
 			}
 		} else {
 			if (opt_api_network)
 				addrok = true;
-			else {
-				connectaddr = inet_ntoa(cli.sin_addr);
+			else
 				addrok = (strcmp(connectaddr, localaddr) == 0);
-			}
 		}
 
-		if (opt_debug) {
-			connectaddr = inet_ntoa(cli.sin_addr);
-			applog(LOG_DEBUG, "DBG: connection from %s - %s", connectaddr, addrok ? "Accepted" : "Ignored");
-		}
+		if (opt_debug)
+			applog(LOG_DEBUG, "API: connection from %s - %s", connectaddr, addrok ? "Accepted" : "Ignored");
 
 		if (addrok) {
 			n = recv(c, &buf[0], BUFSIZ-1, 0);
@@ -1454,9 +1493,9 @@ void api(void)
 
 			if (opt_debug) {
 				if (SOCKETFAIL(n))
-					applog(LOG_DEBUG, "DBG: recv failed: %s", SOCKERRMSG);
+					applog(LOG_DEBUG, "API: recv failed: %s", SOCKERRMSG);
 				else
-					applog(LOG_DEBUG, "DBG: recv command: (%d) '%s'", n, buf);
+					applog(LOG_DEBUG, "API: recv command: (%d) '%s'", n, buf);
 			}
 
 			if (!SOCKETFAIL(n)) {
@@ -1522,7 +1561,13 @@ void api(void)
 				if (!did)
 					for (i = 0; cmds[i].name != NULL; i++) {
 						if (strcmp(cmd, cmds[i].name) == 0) {
-							(cmds[i].func)(c, param, isjson);
+							if (cmds[i].requires_writemode && !writemode) {
+								strcpy(io_buffer, message(MSG_ACCDENY, 0, cmds[i].name, isjson));
+								applog(LOG_DEBUG, "API: access denied to '%s' for '%s' command", connectaddr, cmds[i].name);
+							}
+							else
+								(cmds[i].func)(c, param, isjson);
+
 							send_result(c, isjson);
 							did = true;
 							break;

+ 1 - 1
cgminer.c

@@ -2802,7 +2802,7 @@ static void *api_thread(void *userdata)
 
 	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
 
-	api();
+	api(api_thr_id);
 
 	PTH(mythr) = 0L;
 

+ 1 - 1
miner.h

@@ -449,7 +449,7 @@ extern int set_engineclock(int gpu, int iEngineClock);
 extern int set_memoryclock(int gpu, int iMemoryClock);
 #endif
 
-extern void api(void);
+extern void api(int thr_id);
 
 #define MAX_GPUDEVICES 16
 #define MAX_DEVICES 32