Browse Source

Merge commit '4c5d41a' into bfgminer

Conflicts:
	API-README
	README
	api.c
	miner.c
Luke Dashjr 13 years ago
parent
commit
5aa70aa898
5 changed files with 326 additions and 60 deletions
  1. 36 2
      API-README
  2. 2 0
      README
  3. 273 57
      api.c
  4. 14 1
      miner.c
  5. 1 0
      miner.h

+ 36 - 2
API-README

@@ -19,8 +19,9 @@ 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 BFGMiner.
+that modify BFGMiner (thus all API commands)
 Without it those commands return an access denied status.
+See --api-groups below to define other groups like W:
 Privileged access is checked in the order the IP addresses were supplied to
 "--api-allow"
 The first match determines the privilege level.
@@ -28,6 +29,26 @@ Using the "--api-allow" option overides the "--api-network" option if they
 are both specified
 With "--api-allow", 127.0.0.1 is not by default given access unless specified
 
+More groups (like the privileged group W:) can be defined using the
+--api-groups command
+Valid groups are only the letters A-Z (except R & W are predefined) and are
+not case sensitive
+The R: group is the same as not privileged access
+The W: group is (as stated) privileged access (thus all API commands)
+To give an IP address/subnet access to a group you use the group letter
+in front of the IP address instead of W: e.g. P:192.168.0/32
+An IP address/subnet can only be a member of one group
+A sample API group would be:
+ --api-groups P:switchpool:enablepool:addpool:disablepool:removepool:*
+This would create a group 'P' that can do all current pool commands and all
+non-priviliged commands - the '*' means all non-priviledged commands
+Without the '*' the group would only have access to the pool commands
+Defining multiple groups example:
+ --api-groups Q:quit:restart:*,S:save
+This would define 2 groups:
+ Q: that can 'quit' and 'restart' as well as all non-priviledged commands
+ S: that can only 'save' and no other commands
+
 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
@@ -244,6 +265,9 @@ The list of requests - a (*) means it requires privileged access - and replies a
                               Device drivers are also able to add stats to the
                               end of the details returned
 
+ check|cmd     COMMAND        Exists=Y/N, <- 'cmd' exists in this version
+                              Access=Y/N| <- you have access to use 'cmd'
+
 When you enable, disable or restart a GPU or PGA, you will also get Thread messages
 in the BFGMiner status window
 
@@ -286,7 +310,17 @@ miner.php - an example web page to access the API
 Feature Changelog for external applications using the API:
 
 
-API V1.12
+API V1.13
+
+Added API commands:
+ 'check'
+
+Support was added to cgminer for API access groups with the --api-groups option
+It's 100% backwards compatible with previous --api-access commands
+
+----------
+
+API V1.12 (cgminer v2.4.3)
 
 Modified API commands:
  'stats' - more pool stats added

+ 2 - 0
README

@@ -119,6 +119,8 @@ Options for both config file and command line:
                     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: BFGMiner version)
+--api-groups        API one letter groups G:cmd:cmd[,P:cmd:*...]
+                    See API-README for usage
 --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

+ 273 - 57
api.c

@@ -159,6 +159,7 @@ static char *msg_buffer = NULL;
 static SOCKETTYPE sock = INVSOCK;
 
 static const char *UNAVAILABLE = " - API will not be available";
+static const char *GROUPDIS = " - groups will be disabled";
 
 static const char *BLANK = "";
 static const char *COMMA = ",";
@@ -166,7 +167,7 @@ static const char SEPARATOR = '|';
 #define SEPSTR "|"
 static const char GPUSEP = ',';
 
-static const char *APIVERSION = "1.12";
+static const char *APIVERSION = "1.13";
 static const char *DEAD = "Dead";
 static const char *SICK = "Sick";
 static const char *NOSTART = "NoStart";
@@ -244,6 +245,7 @@ static const char *OSINFO =
 #define _BYE		"BYE"
 #define _RESTART	"RESTART"
 #define _MINESTATS	"STATS"
+#define _CHECK		"CHECK"
 
 static const char ISJSON = '{';
 #define JSON0		"{"
@@ -278,6 +280,7 @@ static const char ISJSON = '{';
 #define JSON_RESTART	JSON1 _RESTART JSON1
 #define JSON_CLOSE	JSON3
 #define JSON_MINESTATS	JSON1 _MINESTATS JSON2
+#define JSON_CHECK	JSON1 _CHECK JSON2
 #define JSON_END	JSON4
 
 static const char *JSON_COMMAND = "command";
@@ -365,6 +368,8 @@ static const char *JSON_PARAMETER = "parameter";
 #define MSG_REMPOOL 68
 #define MSG_DEVDETAILS 69
 #define MSG_MINESTATS 70
+#define MSG_MISCHK 71
+#define MSG_CHECK 72
 
 enum code_severity {
 	SEVERITY_ERR,
@@ -508,6 +513,8 @@ struct CODES {
  { SEVERITY_SUCC,  MSG_NOTIFY,	PARAM_NONE,	"Notify" },
  { SEVERITY_SUCC,  MSG_DEVDETAILS,PARAM_NONE,	"Device Details" },
  { SEVERITY_SUCC,  MSG_MINESTATS,PARAM_NONE,	"CGMiner stats" },
+ { SEVERITY_ERR,   MSG_MISCHK,	PARAM_NONE,	"Missing check cmd" },
+ { SEVERITY_SUCC,  MSG_CHECK,	PARAM_NONE,	"Check command" },
  { SEVERITY_FAIL, 0, 0, NULL }
 };
 
@@ -526,9 +533,25 @@ static time_t when = 0;	// when the request occurred
 struct IP4ACCESS {
 	in_addr_t ip;
 	in_addr_t mask;
-	bool writemode;
+	char group;
 };
 
+#define GROUP(g) (toupper(g))
+#define PRIVGROUP GROUP('W')
+#define NOPRIVGROUP GROUP('R')
+#define ISPRIVGROUP(g) (GROUP(g) == PRIVGROUP)
+#define GROUPOFFSET(g) (GROUP(g) - GROUP('A'))
+#define VALIDGROUP(g) (GROUP(g) >= GROUP('A') && GROUP(g) <= GROUP('Z'))
+#define COMMANDS(g) (apigroups[GROUPOFFSET(g)].commands)
+#define DEFINEDGROUP(g) (ISPRIVGROUP(g) || COMMANDS(g) != NULL)
+
+struct APIGROUPS {
+	// This becomes a string like: "|cmd1|cmd2|cmd3|" so it's quick to search
+	char *commands;
+} apigroups['Z' - 'A' + 1]; // only A=0 to Z=25 (R: noprivs, W: allprivs)
+
+static bool groups_enabled = false;
+
 static struct IP4ACCESS *ipaccess = NULL;
 static int ips = 0;
 
@@ -797,7 +820,7 @@ static char *message(int messageid, int paramid, char *param2, bool isjson)
 	return msg_buffer;
 }
 
-static void apiversion(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void apiversion(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	sprintf(io_buffer, isjson
 		? "%s," JSON_VERSION "{\"CGMiner\":\"%s\",\"API\":\"%s\"}" JSON_CLOSE
@@ -806,7 +829,7 @@ static void apiversion(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
 		VERSION, APIVERSION);
 }
 
-static void minerconfig(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void minerconfig(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	char buf[TMPBUFSIZ];
 	int gpucount = 0;
@@ -1038,13 +1061,13 @@ devdetail(SOCKETTYPE c, char *param, bool isjson)
 }
 
 static void
-devstatus(SOCKETTYPE c, char *param, bool isjson)
+devstatus(SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	return devinfo_internal(devstatus_an, c, param, isjson);
 }
 
 #ifdef HAVE_OPENCL
-static void gpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	int id;
 
@@ -1078,7 +1101,7 @@ static void gpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 }
 #endif
 #ifdef HAVE_AN_FPGA
-static void pgadev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void pgadev(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	int numpga = numpgas();
 	int id;
@@ -1112,7 +1135,7 @@ static void pgadev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 		strcat(io_buffer, JSON_CLOSE);
 }
 
-static void pgaenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void pgaenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	int numpga = numpgas();
 	struct thr_info *thr;
@@ -1168,7 +1191,7 @@ static void pgaenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_PGAENA, id, NULL, isjson));
 }
 
-static void pgadisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void pgadisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	int numpga = numpgas();
 	int id;
@@ -1209,7 +1232,7 @@ static void pgadisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 #endif
 
 #ifdef WANT_CPUMINE
-static void cpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void cpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	int id;
 
@@ -1243,7 +1266,7 @@ static void cpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 }
 #endif
 
-static void poolstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void poolstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	char buf[TMPBUFSIZ];
 	char *status, *lp;
@@ -1320,7 +1343,7 @@ static void poolstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
 		strcat(io_buffer, JSON_CLOSE);
 }
 
-static void summary(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void summary(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	double utility, mhs;
 
@@ -1354,7 +1377,7 @@ static void summary(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, boo
 #endif
 }
 #ifdef HAVE_OPENCL
-static void gpuenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpuenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	struct thr_info *thr;
 	int gpu;
@@ -1400,7 +1423,7 @@ static void gpuenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_GPUREN, id, NULL, isjson));
 }
 
-static void gpudisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpudisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	int id;
 
@@ -1430,7 +1453,7 @@ static void gpudisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_GPUDIS, id, NULL, isjson));
 }
 
-static void gpurestart(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpurestart(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	int id;
 
@@ -1455,7 +1478,7 @@ static void gpurestart(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_GPUREI, id, NULL, isjson));
 }
 #endif
-static void gpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	char buf[TMPBUFSIZ];
 	int numgpu = 0;
@@ -1474,8 +1497,7 @@ static void gpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bo
 	strcat(io_buffer, buf);
 }
 
-
-static void pgacount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void pgacount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	char buf[TMPBUFSIZ];
 	int count = 0;
@@ -1494,7 +1516,7 @@ static void pgacount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bo
 	strcat(io_buffer, buf);
 }
 
-static void cpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void cpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	char buf[TMPBUFSIZ];
 	int count = 0;
@@ -1513,7 +1535,7 @@ static void cpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bo
 	strcat(io_buffer, buf);
 }
 
-static void switchpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void switchpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	struct pool *pool;
 	int id;
@@ -1594,7 +1616,7 @@ exitsama:
 	return false;
 }
 
-static void addpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void addpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	char *url, *user, *pass;
 	char *ptr;
@@ -1622,7 +1644,7 @@ static void addpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	ptr = NULL;
 }
 
-static void enablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void enablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	struct pool *pool;
 	int id;
@@ -1656,7 +1678,7 @@ static void enablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_ENAPOOL, id, NULL, isjson));
 }
 
-static void disablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void disablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	struct pool *pool;
 	int id;
@@ -1695,7 +1717,7 @@ static void disablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_DISPOOL, id, NULL, isjson));
 }
 
-static void removepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void removepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	struct pool *pool;
 	char *rpc_url;
@@ -1781,7 +1803,7 @@ static bool splitgpuvalue(char *param, int *gpu, char **value, bool isjson)
 
 	return true;
 }
-static void gpuintensity(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpuintensity(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	int id;
 	char *value;
@@ -1810,7 +1832,7 @@ static void gpuintensity(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_GPUINT, id, intensitystr, isjson));
 }
 
-static void gpumem(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpumem(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 #ifdef HAVE_ADL
 	int id;
@@ -1831,7 +1853,7 @@ static void gpumem(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool
 #endif
 }
 
-static void gpuengine(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpuengine(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 #ifdef HAVE_ADL
 	int id;
@@ -1852,7 +1874,7 @@ static void gpuengine(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, b
 #endif
 }
 
-static void gpufan(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpufan(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 #ifdef HAVE_ADL
 	int id;
@@ -1873,7 +1895,7 @@ static void gpufan(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool
 #endif
 }
 
-static void gpuvddc(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpuvddc(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 #ifdef HAVE_ADL
 	int id;
@@ -1894,7 +1916,7 @@ static void gpuvddc(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, boo
 #endif
 }
 #endif
-void doquit(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+void doquit(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	if (isjson)
 		strcpy(io_buffer, JSON_START JSON_BYE);
@@ -1905,7 +1927,7 @@ void doquit(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson
 	do_a_quit = true;
 }
 
-void dorestart(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+void dorestart(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	if (isjson)
 		strcpy(io_buffer, JSON_START JSON_RESTART);
@@ -1916,12 +1938,12 @@ void dorestart(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isj
 	do_a_restart = true;
 }
 
-void privileged(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+void privileged(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	strcpy(io_buffer, message(MSG_ACCOK, 0, NULL, isjson));
 }
 
-void notifystatus(int device, struct cgpu_info *cgpu, bool isjson)
+void notifystatus(int device, struct cgpu_info *cgpu, bool isjson, __maybe_unused char group)
 {
 	char buf[TMPBUFSIZ];
 	char *reason;
@@ -1975,7 +1997,7 @@ void notifystatus(int device, struct cgpu_info *cgpu, bool isjson)
 	strcat(io_buffer, buf);
 }
 
-static void notify(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void notify(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	int i;
 
@@ -1992,13 +2014,13 @@ static void notify(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool
 	}
 
 	for (i = 0; i < total_devices; i++)
-		notifystatus(i, devices[i], isjson);
+		notifystatus(i, devices[i], isjson, group);
 
 	if (isjson)
 		strcat(io_buffer, JSON_CLOSE);
 }
 
-static void devdetails(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void devdetails(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	char buf[TMPBUFSIZ];
 	struct cgpu_info *cgpu;
@@ -2034,7 +2056,7 @@ static void devdetails(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
 		strcat(io_buffer, JSON_CLOSE);
 }
 
-void dosave(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+void dosave(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 	char filename[PATH_MAX];
 	FILE *fcfg;
@@ -2110,7 +2132,8 @@ static int itemstats(int i, char *id, struct cgminer_stats *stats, struct cgmine
 
 	return i;
 }
-static void minerstats(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+
+static void minerstats(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 	char extra[TMPBUFSIZ];
 	char id[20];
@@ -2149,10 +2172,12 @@ static void minerstats(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
 		strcat(io_buffer, JSON_CLOSE);
 }
 
+static void checkcommand(__maybe_unused SOCKETTYPE c, char *param, bool isjson, char group);
+
 struct CMDS {
 	char *name;
-	void (*func)(SOCKETTYPE, char *, bool);
-	bool requires_writemode;
+	void (*func)(SOCKETTYPE, char *, bool, char);
+	bool iswritemode;
 } cmds[] = {
 	{ "version",		apiversion,	false },
 	{ "config",		minerconfig,	false },
@@ -2196,9 +2221,47 @@ struct CMDS {
 	{ "devdetails",		devdetails,	false },
 	{ "restart",		dorestart,	true },
 	{ "stats",		minerstats,	false },
+	{ "check",		checkcommand,	false },
 	{ NULL,			NULL,		false }
 };
 
+static void checkcommand(__maybe_unused SOCKETTYPE c, char *param, bool isjson, char group)
+{
+	char buf[TMPBUFSIZ];
+	char cmdbuf[100];
+	bool found, access;
+	int i;
+
+	if (param == NULL || *param == '\0') {
+		strcpy(io_buffer, message(MSG_MISCHK, 0, NULL, isjson));
+		return;
+	}
+
+	found = false;
+	access = false;
+	for (i = 0; cmds[i].name != NULL; i++) {
+		if (strcmp(cmds[i].name, param) == 0) {
+			found = true;
+
+			sprintf(cmdbuf, "|%s|", param);
+			if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf))
+				access = true;
+
+			break;
+		}
+	}
+
+	strcpy(io_buffer, message(MSG_CHECK, 0, NULL, isjson));
+
+	sprintf(buf, isjson
+		? "," JSON_CHECK "{\"Exists\":\"%s\",\"Access\":\"%s\"}" JSON_CLOSE
+		: _CHECK ",Exists=%s,Access=%s" SEPSTR,
+		found ? YES : NO,
+		access ? YES : NO);
+
+	strcat(io_buffer, buf);
+}
+
 static void send_result(SOCKETTYPE c, bool isjson)
 {
 	int n;
@@ -2253,7 +2316,154 @@ static void tidyup(__maybe_unused void *arg)
 }
 
 /*
- * Interpret [R|W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option
+ * Interpret --api-groups G:cmd1:cmd2:cmd3,P:cmd4,*,...
+ */
+static void setup_groups()
+{
+	char *buf, *ptr, *next, *colon;
+	char group;
+	char commands[TMPBUFSIZ];
+	char cmdbuf[100];
+	char *cmd;
+	bool addstar, did;
+	int i;
+
+	buf = malloc(strlen(opt_api_groups) + 1);
+	if (unlikely(!buf))
+		quit(1, "Failed to malloc ipgroups buf");
+
+	strcpy(buf, opt_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';
+			applog(LOG_WARNING, "API invalid group name '%s'%s", ptr, GROUPDIS);
+			goto shin;
+		}
+
+		group = GROUP(*ptr);
+		if (!VALIDGROUP(group)) {
+			applog(LOG_WARNING, "API invalid group name '%c'%s", *ptr, GROUPDIS);
+			goto shin;
+		}
+
+		if (group == PRIVGROUP) {
+			applog(LOG_WARNING, "API group name can't be '%c'%s", PRIVGROUP, GROUPDIS);
+			goto shin;
+		}
+
+		if (group == NOPRIVGROUP) {
+			applog(LOG_WARNING, "API group name can't be '%c'%s", NOPRIVGROUP, GROUPDIS);
+			goto shin;
+		}
+
+		if (apigroups[GROUPOFFSET(group)].commands != NULL) {
+			applog(LOG_WARNING, "API duplicate group name '%c'%s", *ptr, GROUPDIS);
+			goto shin;
+		}
+
+		ptr += 2;
+
+		// Validate the command list (and handle '*')
+		cmd = &(commands[0]);
+		*(cmd++) = SEPARATOR;
+		*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++) = SEPARATOR;
+						*cmd = '\0';
+					}
+				} else {
+					applog(LOG_WARNING, "API unknown command '%s' in group '%c'%s", ptr, group, GROUPDIS);
+					goto shin;
+				}
+			}
+
+			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++) = SEPARATOR;
+						*cmd = '\0';
+					}
+				}
+			}
+		}
+
+		ptr = apigroups[GROUPOFFSET(group)].commands = malloc(strlen(commands) + 1);
+		if (unlikely(!ptr))
+			quit(1, "Failed to malloc group commands buf");
+
+		strcpy(ptr, commands);
+	}
+
+	// Now define R (NOPRIVGROUP) as all non-iswritemode commands
+	cmd = &(commands[0]);
+	*(cmd++) = SEPARATOR;
+	*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++) = SEPARATOR;
+			*cmd = '\0';
+		}
+	}
+
+	ptr = apigroups[GROUPOFFSET(NOPRIVGROUP)].commands = malloc(strlen(commands) + 1);
+	if (unlikely(!ptr))
+		quit(1, "Failed to malloc noprivgroup commands buf");
+
+	strcpy(ptr, commands);
+
+	// W (PRIVGROUP) is handled as a special case since it simply means all commands
+
+	groups_enabled = true;
+shin:
+	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 ALLIP4 "0/0"
@@ -2264,7 +2474,7 @@ static void setup_ipaccess()
 {
 	char *buf, *ptr, *comma, *slash, *dot;
 	int ipcount, mask, octet, i;
-	bool writemode;
+	char group;
 
 	buf = malloc(strlen(opt_api_allow) + 1);
 	if (unlikely(!buf))
@@ -2298,16 +2508,16 @@ static void setup_ipaccess()
 		if (comma)
 			*(comma++) = '\0';
 
-		writemode = false;
+		group = NOPRIVGROUP;
 
 		if (isalpha(*ptr) && *(ptr+1) == ':') {
-			if (tolower(*ptr) == 'w')
-				writemode = true;
+			if (DEFINEDGROUP(*ptr))
+				group = GROUP(*ptr);
 
 			ptr += 2;
 		}
 
-		ipaccess[ips].writemode = writemode;
+		ipaccess[ips].group = group;
 
 		if (strcmp(ptr, ALLIP4) == 0)
 			ipaccess[ips].ip = ipaccess[ips].mask = 0;
@@ -2397,10 +2607,11 @@ void api(int api_thr_id)
 	struct sockaddr_in serv;
 	struct sockaddr_in cli;
 	socklen_t clisiz;
+	char cmdbuf[100];
 	char *cmd;
 	char *param;
 	bool addrok;
-	bool writemode;
+	char group;
 	json_error_t json_err;
 	json_t *json_config;
 	json_t *json_val;
@@ -2421,6 +2632,9 @@ void api(int api_thr_id)
 		return;
 	}
 
+	if (opt_api_groups)
+		setup_groups();
+
 	if (opt_api_allow) {
 		setup_ipaccess();
 
@@ -2499,15 +2713,16 @@ void api(int api_thr_id)
 		connectaddr = inet_ntoa(cli.sin_addr);
 
 		addrok = false;
-		writemode = false;
+		group = NOPRIVGROUP;
 		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;
+			if (groups_enabled)
+				for (i = 0; i < ips; i++) {
+					if ((cli.sin_addr.s_addr & ipaccess[i].mask) == ipaccess[i].ip) {
+						addrok = true;
+						group = ipaccess[i].group;
+						break;
+					}
 				}
-			}
 		} else {
 			if (opt_api_network)
 				addrok = true;
@@ -2598,12 +2813,13 @@ void api(int api_thr_id)
 				if (!did)
 					for (i = 0; cmds[i].name != NULL; i++) {
 						if (strcmp(cmd, cmds[i].name) == 0) {
-							if (cmds[i].requires_writemode && !writemode) {
+							sprintf(cmdbuf, "|%s|", cmd);
+							if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf))
+								(cmds[i].func)(c, param, isjson, group);
+							else {
 								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;

+ 14 - 1
miner.c

@@ -132,6 +132,7 @@ bool opt_autofan;
 bool opt_autoengine;
 bool opt_noadl;
 char *opt_api_allow = NULL;
+char *opt_api_groups;
 char *opt_api_description = PACKAGE_STRING;
 int opt_api_port = 4028;
 bool opt_api_listen;
@@ -679,6 +680,13 @@ static char *set_api_allow(const char *arg)
 	return NULL;
 }
 
+static char *set_api_groups(const char *arg)
+{
+	opt_set_charp(arg, &opt_api_groups);
+
+	return NULL;
+}
+
 static char *set_api_description(const char *arg)
 {
 	opt_set_charp(arg, &opt_api_description);
@@ -729,10 +737,13 @@ static struct opt_table opt_config_table[] = {
 #endif
 	OPT_WITH_ARG("--api-allow",
 		     set_api_allow, NULL, NULL,
-		     "Allow API access only to the given list of IP[/Prefix] addresses[/subnets]"),
+		     "Allow API access only to the given list of [G:]IP[/Prefix] addresses[/subnets]"),
 	OPT_WITH_ARG("--api-description",
 		     set_api_description, NULL, NULL,
 		     "Description placed in the API status header, default: BFGMiner version"),
+	OPT_WITH_ARG("--api-groups",
+		     set_api_groups, NULL, NULL,
+		     "API one letter groups G:cmd:cmd[,P:cmd:*...] defining the cmds a groups can use"),
 	OPT_WITHOUT_ARG("--api-listen",
 			opt_set_bool, &opt_api_listen,
 			"Enable API, default: disabled"),
@@ -2900,6 +2911,8 @@ void write_config(FILE *fcfg)
 		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);
+	if (opt_api_groups)
+		fprintf(fcfg, ",\n\"api-groups\" : \"%s\"", opt_api_groups);
 	if (opt_icarus_timing)
 		fprintf(fcfg, ",\n\"icarus-timing\" : \"%s\"", opt_icarus_timing);
 	fputs("\n}", fcfg);

+ 1 - 0
miner.h

@@ -526,6 +526,7 @@ extern bool opt_autofan;
 extern bool opt_autoengine;
 extern bool use_curses;
 extern char *opt_api_allow;
+extern char *opt_api_groups;
 extern char *opt_api_description;
 extern int opt_api_port;
 extern bool opt_api_listen;