Browse Source

Merge pull request #107 from kanoi/master

Allow API to restrict access by IP address + other commits
nfactor-troky
Con Kolivas 13 years ago
parent
commit
8c609579f4
  1. 24
      README
  2. 145
      api.c
  3. 13
      cgminer.c
  4. 2
      device-cpu.c
  5. 1
      miner.h
  6. 31
      miner.php

24
README

@ -119,6 +119,8 @@ Usage instructions: Run "cgminer --help" to see options:
Usage: . [-atDdGCgIKklmpPQqrRsTouvwOchnV] Usage: . [-atDdGCgIKklmpPQqrRsTouvwOchnV]
Options for both config file and command line: 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-description Description placed in the API status header (default: cgminer version)
--api-listen Listen for API requests (default: disabled) --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) --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 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 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 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 If you add the "--api-network" option, it will accept API requests from any
from any network attached computer. 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 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. 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. where "CMD" is from the "Request" column below and "PARAM" would be e.g.
the CPU/GPU number if required. 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 gpufan|0,80
{"command":"gpufan","parameter":"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 When you enable, disable or restart a GPU, you will also get Thread messages in
the cgminer status window 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 '=' Obviously, the JSON format is simply just the names as given before the '='
with the values after the '=' with the values after the '='

145
api.c

@ -126,6 +126,10 @@
#ifndef SHUT_RDWR #ifndef SHUT_RDWR
#define SHUT_RDWR SD_BOTH #define SHUT_RDWR SD_BOTH
#endif #endif
#ifndef in_addr_t
#define in_addr_t uint32_t
#endif
#endif #endif
// Big enough for largest API request // Big enough for largest API request
@ -343,6 +347,14 @@ struct CODES {
static int bye = 0; static int bye = 0;
static bool ping = true; 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 // All replies (except BYE) start with a message
// thus for JSON, message() inserts JSON_START at the front // thus for JSON, message() inserts JSON_START at the front
// and send_result() adds JSON_END at the end // and send_result() adds JSON_END at the end
@ -1205,6 +1217,11 @@ static void tidyup()
sock = INVSOCK; sock = INVSOCK;
} }
if (ipaccess != NULL) {
free(ipaccess);
ipaccess = NULL;
}
if (msg_buffer != NULL) { if (msg_buffer != NULL) {
free(msg_buffer); free(msg_buffer);
msg_buffer = NULL; 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) void api(void)
{ {
char buf[BUFSIZ]; char buf[BUFSIZ];
@ -1248,6 +1348,15 @@ void api(void)
return; 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); sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVSOCK) { if (sock == INVSOCK) {
applog(LOG_ERR, "API1 initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE); applog(LOG_ERR, "API1 initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE);
@ -1258,9 +1367,9 @@ void api(void)
serv.sin_family = AF_INET; serv.sin_family = AF_INET;
if (!opt_api_network) { if (!opt_api_allow && !opt_api_network) {
serv.sin_addr.s_addr = inet_addr(localaddr); 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); applog(LOG_ERR, "API2 initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE);
return; return;
} }
@ -1296,10 +1405,14 @@ void api(void)
return; return;
} }
if (opt_api_network) if (opt_api_allow)
applog(LOG_WARNING, "API running in UNRESTRICTED access mode"); applog(LOG_WARNING, "API running in IP access mode");
else else {
applog(LOG_WARNING, "API running in restricted access mode"); 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); io_buffer = malloc(MYBUFSIZ+1);
msg_buffer = malloc(MYBUFSIZ+1); msg_buffer = malloc(MYBUFSIZ+1);
@ -1311,11 +1424,21 @@ void api(void)
goto die; goto die;
} }
if (opt_api_network) addrok = false;
addrok = true; if (opt_api_allow) {
else { for (i = 0; i < ips; i++) {
connectaddr = inet_ntoa(cli.sin_addr); if ((cli.sin_addr.s_addr & ipaccess[i].mask) == ipaccess[i].ip) {
addrok = (strcmp(connectaddr, localaddr) == 0); 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) { if (opt_debug) {

13
cgminer.c

@ -127,6 +127,7 @@ static bool opt_fail_only;
bool opt_autofan; bool opt_autofan;
bool opt_autoengine; bool opt_autoengine;
bool opt_noadl; bool opt_noadl;
char *opt_api_allow = NULL;
char *opt_api_description = PACKAGE_STRING; char *opt_api_description = PACKAGE_STRING;
int opt_api_port = 4028; int opt_api_port = 4028;
bool opt_api_listen = false; bool opt_api_listen = false;
@ -536,6 +537,13 @@ static char *set_schedtime(const char *arg, struct schedtime *st)
return NULL; 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) static char *set_api_description(const char *arg)
{ {
opt_set_charp(arg, &opt_api_description); opt_set_charp(arg, &opt_api_description);
@ -575,6 +583,9 @@ static struct opt_table opt_config_table[] = {
#endif #endif
), ),
#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", OPT_WITH_ARG("--api-description",
set_api_description, NULL, NULL, set_api_description, NULL, NULL,
"Description placed in the API status header, default: cgminer version"), "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++) for (i = 0; i < nDevs; i++)
if (gpus[i].enabled) if (gpus[i].enabled)
fprintf(fcfg, ",\n\"device\" : \"%d\"", i); 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) if (strcmp(opt_api_description, PACKAGE_STRING) != 0)
fprintf(fcfg, ",\n\"api-description\" : \"%s\"", opt_api_description); fprintf(fcfg, ",\n\"api-description\" : \"%s\"", opt_api_description);
fputs("\n}", fcfg); fputs("\n}", fcfg);

2
device-cpu.c

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

1
miner.h

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

31
miner.php

@ -17,8 +17,9 @@ td.sta { color:green; font-family:verdana,arial,sans; font-size:14pt; }
</head><body bgcolor=#ecffff> </head><body bgcolor=#ecffff>
<script type='text/javascript'> <script type='text/javascript'>
function pr(a,m){if(m!=null){if(!confirm(m+'?'))return}window.location="<? echo $here ?>"+a} 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 prc(a,m){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 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> </script>
<table width=100% height=100% border=0 cellpadding=0 cellspacing=0 summary='Mine'> <table width=100% height=100% border=0 cellpadding=0 cellspacing=0 summary='Mine'>
<tr><td align=center valign=top> <tr><td align=center valign=top>
@ -201,7 +202,7 @@ function fmt($section, $name, $value)
return $value; return $value;
} }
# #
function details($list) function details($cmd, $list)
{ {
$stas = array('S' => 'Success', 'W' => 'Warning', 'I' => 'Informational', 'E' => 'Error', 'F' => 'Fatal'); $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>"; echo "<td valign=bottom class=h>$name</td>";
} }
if ($cmd == 'pools')
echo "<td valign=bottom class=h>Switch</td>";
echo '</tr>'; echo '</tr>';
break; break;
@ -257,6 +261,23 @@ function details($list)
foreach ($values as $name => $value) foreach ($values as $name => $value)
echo '<td>'.fmt($section, $name, $value).'</td>'; 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 '</tr>';
} }
echo $te; echo $te;
@ -369,7 +390,7 @@ function process($cmds, $rd, $ro)
} }
else else
{ {
details($process); details($cmd, $process);
echo '<tr><td><br><br></td></tr>'; echo '<tr><td><br><br></td></tr>';
if ($cmd == 'devs') if ($cmd == 'devs')
$devs = $process; $devs = $process;
@ -389,7 +410,7 @@ function display()
echo "<tr><td><table cellpadding=0 cellspacing=0 border=0><tr><td>"; echo "<tr><td><table cellpadding=0 cellspacing=0 border=0><tr><td>";
echo "<input type=button value='Refresh' onclick='pr(\"\",null)'>"; echo "<input type=button value='Refresh' onclick='pr(\"\",null)'>";
echo "</td><td width=100%>&nbsp;</td><td>"; 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>"; echo "</td></tr></table></td></tr>";
$arg = trim(getparam('arg', true)); $arg = trim(getparam('arg', true));

Loading…
Cancel
Save