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. 129
      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: @@ -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. @@ -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"}' @@ -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: @@ -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 '='

129
api.c

@ -126,6 +126,10 @@ @@ -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 { @@ -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() @@ -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() @@ -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) @@ -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) @@ -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) @@ -1296,10 +1405,14 @@ void api(void)
return;
}
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 restricted access mode");
applog(LOG_WARNING, "API running in local access mode");
}
io_buffer = malloc(MYBUFSIZ+1);
msg_buffer = malloc(MYBUFSIZ+1);
@ -1311,12 +1424,22 @@ void api(void) @@ -1311,12 +1424,22 @@ void api(void)
goto die;
}
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) {
connectaddr = inet_ntoa(cli.sin_addr);

13
cgminer.c

@ -127,6 +127,7 @@ static bool opt_fail_only; @@ -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) @@ -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[] = { @@ -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) @@ -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);

2
device-cpu.c

@ -23,9 +23,9 @@ @@ -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
miner.h

@ -446,6 +446,7 @@ extern char *cgminer_path; @@ -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;

31
miner.php

@ -17,8 +17,9 @@ td.sta { color:green; font-family:verdana,arial,sans; font-size:14pt; } @@ -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) @@ -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) @@ -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) @@ -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) @@ -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() @@ -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));

Loading…
Cancel
Save