clixon/apps/restconf/restconf_main.c
2016-05-20 18:33:48 +02:00

1386 lines
37 KiB
C

/*
*
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
CLIXON is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
CLIXON is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CLIXON; see the file LICENSE. If not, see
<http://www.gnu.org/licenses/>.
*/
/*
* See draft-ietf-netconf-restconf-13.txt
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
* This is the interface:
* api/cli
* api/netconf
* api/login
* api/logout
* api/signup
* api/settings nyi
* api/callhome
* api/metric_rules
* api/metrics
* api/metric_spec
* api/data/profile=<name>/metric=<name> PUT data:enable=<flag>
* api/user
* api/test
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h>
#include <fcgi_stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <curl/curl.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "restconf_lib.h"
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hDf:"
/* Should be discovered via "/.well-known/host-meta"
resource ([RFC6415]) */
#define RESTCONF_API_ROOT "/restconf/"
/*=======================================================================
* API code
*=======================================================================*/
/*! Send a CLI command via GET, POST or PUT
* POST or PUT:
* URI: /api/cli/configure
* data: show version
* GET:
* URI: /api/cli/configure/show/version
* Yes, the GET syntax is ugly but it can be nice to have in eg a browser.
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] qn Length of qvec
* @param[in] data Stream input data
*/
static int
api_cli(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
char *data)
{
int retval = -1;
char *cmd;
char *mode;
char *request;
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n\r\n");
mode = pvec[0];
request = FCGX_GetParam("REQUEST_METHOD", r->envp);
if (strcmp(request, "GET")==0){
pvec++; pn--;
if ((cmd = clicon_strjoin(pn, pvec, " ", __FUNCTION__)) == NULL)
goto done;
}
else
if (strcmp(request, "PUT")==0 || strcmp(request, "POST")==0)
cmd = data;
else
goto done;
if (cli_cmd(r, mode, cmd) < 0)
goto done;
retval = 0;
done:
unchunk_group (__FUNCTION__);
return retval;
}
/*!
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
*/
static int
api_netconf(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
char *data)
{
int retval = -1;
char *request;
cbuf *cb = NULL;
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n\r\n");
request = FCGX_GetParam("REQUEST_METHOD", r->envp);
if (strcmp(request, "PUT")!=0 && strcmp(request, "POST")!=0)
goto done;
if (netconf_cmd(r, data) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Register new user
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
* In: user, passwd [fullname, organization, sender-template]
* Create: configuration user/passwd, influxdb database, user
*/
static int
api_signup(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
char *request;
char *server_addr;
char *root;
char *name;
char *dashboard_db_name;
char *passw;
char *fullname;
char *organization;
char *profile;
cbuf *resbuf = NULL;
cbuf *cb = NULL;
char *id;
cxobj *cx = NULL;
cxobj *x;
clicon_debug(1, "%s", __FUNCTION__);
request = FCGX_GetParam("REQUEST_METHOD", r->envp);
server_addr = FCGX_GetParam("SERVER_ADDR", r->envp);
root = FCGX_GetParam("DOCUMENT_ROOT", r->envp);
if (strcmp(request, "POST")!=0)
goto done;
FCGX_SetExitStatus(201, r->out);
if ((name = cvec_find_str(dvec, "email")) == NULL)
goto done;
clicon_debug(1, "%s name=%s", __FUNCTION__, name);
if ((passw = cvec_find_str(dvec, "passw")) == NULL)
goto done;
fullname = cvec_find_str(dvec, "fullname");
organization = cvec_find_str(dvec, "organization");
profile = cvec_find_str(dvec, "profile");
clicon_debug(1, "%s passw=%s", __FUNCTION__, passw);
if (strlen(name)==0){
errorfn(r, root, "No Name given");
goto done;
}
if (strlen(passw)==0){
errorfn(r, root, "No Password given");
goto done;
}
if ((resbuf = cbuf_new()) == NULL)
goto done;
/* Create user in gridye/clicon */
if (cli_rpc(resbuf, "configure", "user %s password %s", name, passw) < 0)
goto done;
if (fullname && strlen(fullname))
if (cli_rpc(resbuf, "configure", "user %s fullname %s", name, fullname) < 0)
goto done;
if (organization && strlen(organization))
if (cli_rpc(resbuf, "configure", "user %s organization %s", name, organization) < 0)
goto done;
if (profile && strlen(profile))
if (cli_rpc(resbuf, "configure", "user %s profile %s", name, profile) < 0)
goto done;
/* Create influxdb data database and user
(XXX create same user/passwd as server, may want to keep them separate)
*/
if (create_database(server_addr, name, WWW_USER, WWW_PASSWD) < 0)
goto done;
if (create_db_user(server_addr, name, name, passw, WWW_USER, WWW_PASSWD) < 0)
goto done;
/* */
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "%s_db", name);
dashboard_db_name = cbuf_get(cb);
/* Create influxdb dashboard database and user */
if (create_database(server_addr, dashboard_db_name, WWW_USER, WWW_PASSWD) < 0)
goto done;
if (create_db_user(server_addr, dashboard_db_name,
name, passw, WWW_USER, WWW_PASSWD) < 0)
goto done;
/* Create influxdb entry in gridye/clicon */
if (cli_rpc(resbuf, "configure", "user %s resultdb url http://%s:8086/db/%s/series",
name, server_addr, name) < 0)
goto done;
if (cli_rpc(resbuf, "configure", "user %s resultdb minute true", name) < 0)
goto done;
if (cli_rpc(resbuf, "configure", "user %s resultdb username %s", name, name) < 0)
goto done;
if (cli_rpc(resbuf, "configure", "user %s resultdb password %s", name, passw) < 0)
goto done;
if (cli_rpc(resbuf, "configure", "commit") < 0)
goto done;
/* Get database entry for user from name to get id */
if (get_db_entry("user", "name", name, &cx) < 0)
goto done;
if ((x = xpath_first(cx, "//user/id")) == NULL)
goto done;
id = xml_body(x);
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n");
FCGX_FPrintF(r->out, "Location: /www/grideye.html\r\n");
FCGX_FPrintF(r->out, "Set-Cookie: %s=%s; path=/; HttpOnly\r\n",
USER_COOKIE, id);
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
clicon_debug(1, "%s end %d", __FUNCTION__, retval);
if (resbuf)
cbuf_free(resbuf);
if (cb)
cbuf_free(cb);
return retval;
}
static int
api_settings(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
/* NYI */
return 0;
}
/*! User pressed login submit button -> check user/passwd and set connect session cookie
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
*/
static int
api_login(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
char *request;
char *root;
char *name;
char *passw;
cxobj *cx = NULL;
cxobj *x;
char *id;
clicon_debug(1, "%s", __FUNCTION__);
request = FCGX_GetParam("REQUEST_METHOD", r->envp);
root = FCGX_GetParam("DOCUMENT_ROOT", r->envp);
if (strcmp(request, "POST")!=0 && strcmp(request, "PUT")!=0)
goto done;
FCGX_SetExitStatus(201, r->out);
if ((name = cvec_find_str(dvec, "email")) == NULL)
goto done;
clicon_debug(1, "%s name=%s", __FUNCTION__, name);
if ((passw = cvec_find_str(dvec, "passw")) == NULL)
goto done;
clicon_debug(1, "%s passw=%s", __FUNCTION__, passw);
if (strlen(name)==0){
errorfn(r, root, "No Name given");
goto done;
}
if (strlen(passw)==0){
errorfn(r, root, "No Password given");
goto done;
}
/* Get database entry for user from name */
if (get_db_entry("user", "name", name, &cx) < 0)
goto done;
if (check_credentials(passw, cx) == 0){
clicon_debug(1, "%s wrong password or user", __FUNCTION__);
errorfn(r, root, "Wrong password or user");
goto done;
}
clicon_debug(1, "%s login credentials ok", __FUNCTION__);
if ((x = xpath_first(cx, "//user/id")) == NULL)
goto done;
id = xml_body(x);
/* Set connected-user cookie */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n");
FCGX_FPrintF(r->out, "Location: /www/grideye.html\r\n");
FCGX_FPrintF(r->out, "Set-Cookie: %s=%s; path=/; HttpOnly\r\n",
USER_COOKIE, id);
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
if (cx)
xml_free(cx);
return retval;
}
/*!
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
*/
static int
api_logout(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
clicon_debug(1, "%s", __FUNCTION__);
/* Set connected-user cookie */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n");
FCGX_FPrintF(r->out, "Location: /www/grideye.html\r\n");
FCGX_FPrintF(r->out, "Set-Cookie: %s=; path=/; HttpOnly; Expires=Sun, 06 Nov 1994 08:49:37 GMT\r\n",
USER_COOKIE);
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
return retval;
}
/*! Get remote-addr, get name as param, get project from config.
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
* in: id, name, agent_address
* create: sender and start it
*/
static int
api_callhome(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
cxobj *cx = NULL;
cxobj *cs = NULL;
cxobj *x;
cxobj *y;
char *agent_addr;
char *agent_name;
char *request;
char *id;
char *template = DEFAULT_TEMPLATE; /* sender template */
cbuf *resbuf = NULL;
clicon_debug(1, "%s", __FUNCTION__);
request = FCGX_GetParam("REQUEST_METHOD", r->envp);
if (strcmp(request, "POST")!=0 && strcmp(request, "PUT")!=0)
goto done;
agent_addr = FCGX_GetParam("REMOTE_ADDR", r->envp);
clicon_debug(1, "%s agent_addr:%s", __FUNCTION__, agent_addr);
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
/* Get user id=<id>& agent name=<name> from POST data stream (required) */
if ((id = cvec_find_str(dvec, "id")) == NULL)
goto done;
clicon_debug(1, "%s id:%s", __FUNCTION__, id);
if ((agent_name = cvec_find_str(dvec, "name")) == NULL)
goto done;
clicon_debug(1, "%s agent_name:%s", __FUNCTION__, agent_name);
/* Get user configuration from cookie (=id) */
if (get_db_entry("user", "id", id, &cx) < 0)
goto done;
if (check_credentials(NULL, cx) == 0)
goto done;
if ((x = xpath_first(cx, "//user/template")) != NULL)
template = xml_body(x);
clicon_debug(1, "%s: template=%s", __FUNCTION__, template);
/* Check if sender = agent_name exists, if not create it using template, addr, name */
if (get_db_entry("sender", "name", agent_name, &cs) < 0)
goto done;
if ((x = xpath_first(cx, "//sender/name")) == NULL){
clicon_debug(1, "%s: create sender %s", __FUNCTION__, agent_name);
if (cli_rpc(resbuf, "configure", "sender %s template %s", agent_name, template) < 0)
goto done;
if (cli_rpc(resbuf, "configure", "sender %s ipv4_daddr %s", agent_name, agent_addr) < 0)
goto done;
if (cli_rpc(resbuf, "configure", "sender %s userid %s", agent_name, id) < 0)
goto done;
if (cli_rpc(resbuf, "configure", "sender %s start true", agent_name) < 0)
goto done;
if (cli_rpc(resbuf, "configure", "commit") < 0)
goto done;
}
else {
/* Check if userid exists */
if ((y = xpath_first(x, "//sender/userid")) == NULL)
goto done;
/* Check if userid matches our id */
if (strcmp(id, xml_body(y)))
goto done;
/* Check if started */
if ((y = xpath_first(x, "//sender/start")) == NULL)
goto done;
if (strcmp("true", xml_body(y)))
goto done;
/* Check if IP address changed */
if ((y = xpath_first(x, "//sender/ipv4_daddr")) == NULL)
goto done;
if (strcmp(agent_addr, xml_body(y)))
goto done;
}
/* We should be hunky dory */
FCGX_FPrintF(r->out, "template = '%s'\n", template?template:"(null)");
FCGX_FPrintF(r->out, "agent_addr = '%s'\n", agent_addr);
FCGX_FPrintF(r->out, "agent_name = '%s'\n", agent_name);
retval = 0;
done:
if (cx)
xml_free(cx);
if (cs)
xml_free(cs);
if (resbuf)
cbuf_free(resbuf);
return retval;
}
/*! Get list of metric rules in a profile
* GET api/metric_rules/<profile>[/<metric>]
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
* @retval [
{metric:"rtt",
perc: 1,
warningval:"john",
errorval: "au",
op: "eq"}
];
* XXX a fifth element in this record type defined in java script contains sender/host list
XXX Assume if anything in query_string, it is ?enable=true
*/
static int
api_metric_rules(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
char *sender;
char *metric0 = NULL;
cbuf *resbuf = NULL;
cxobj *ax = NULL;
cxobj *x;
char *xmlstr;
cxobj **xv = NULL;
size_t xlen;
int i;
int j;
char *metric;
cxobj *percentile;
cxobj *warningval;
cxobj *errorval;
cxobj *enableval;
cxobj *op;
clicon_debug(1, "%s", __FUNCTION__);
if (pn < 1)
goto done;
sender = pvec[0];
if (pn < 2)
metric0 = pvec[1];
if ((resbuf = cbuf_new()) == NULL)
goto done;
if (netconf_rpc(resbuf,
"<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"//profile[name=%s]\" /></get-config></rpc>]]>]]>", sender) < 0){
clicon_debug(1, "%s error", __FUNCTION__);
goto done;
}
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
xmlstr = cbuf_get(resbuf);
if (clicon_xml_parse_string(&xmlstr, &ax) < 0){
clicon_debug(1, "err:%d reason:%s", clicon_errno, clicon_err_reason);
goto done;
}
if (cvec_len(qvec)){
/* XXX Assume qvec == enable=true*/
if (xpath_vec(ax, "//profile/metric/*[enable=true]", &xv, &xlen) < 0)
goto done;
}
else
if (xpath_vec(ax, "//profile/metric/*", &xv, &xlen) < 0)
goto done;
FCGX_FPrintF(r->out, "[");
j = 0;
for (i=0; i<xlen; i++){
x = xv[i];
percentile = xpath_first(x, "percentile");
warningval = xpath_first(x, "warningval");
errorval = xpath_first(x, "errorval");
enableval = xpath_first(x, "enable");
metric = xml_name(x);
op = xpath_first(x, "op");
if (metric0 && (strcmp(metric0, metric)))
continue;
if (metric){
if (j++)
FCGX_FPrintF(r->out, ",");
FCGX_FPrintF(r->out, "{\"metric\":\"%s\",\"perc\":\"%s\",\"warningval\":\"%s\",\"errorval\":\"%s\",\"op\":\"%s\", \"enable\":\"%s\"}",
metric,
percentile?xml_body(percentile):"95",
warningval?xml_body(warningval):"0",
errorval?xml_body(errorval):"0",
xml_body(op),
enableval?xml_body(enableval):"false"
);
}
}
FCGX_FPrintF(r->out, "]");
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
if (ax)
xml_free(ax);
if (xv)
free(xv);
if (resbuf)
cbuf_free(resbuf);
return retval;
}
/*! Get list of metrics from yang spec
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
* GET api/metrics
* @retval ["rtt", "tior", ...];
*/
static int
api_metrics(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
cbuf *resbuf = NULL;
char *xmlstr;
cxobj *mx = NULL;
cxobj *dx;
cxobj *x = NULL;
int i;
if ((resbuf = cbuf_new()) == NULL)
goto done;
if (netconf_rpc(resbuf,
"<rpc><grideye><metrics/></grideye></rpc>]]>]]>") < 0){
clicon_debug(1, "%s error", __FUNCTION__);
goto done;
}
xmlstr = cbuf_get(resbuf);
if (clicon_xml_parse_string(&xmlstr, &mx) < 0){
clicon_debug(1, "err:%d reason:%s", clicon_errno, clicon_err_reason);
goto done;
}
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
// FCGX_FPrintF(r->out, "{ \"metrics\":[");
FCGX_FPrintF(r->out, "[");
if ((dx = xpath_first(mx, "//data")) != NULL){
x = NULL;
i = 0;
while ((x = xml_child_each(dx, x, -1)) != NULL) {
if (i++)
FCGX_FPrintF(r->out, ", ");
FCGX_FPrintF(r->out, "\"%s\"", xml_name(x));
}
}
FCGX_FPrintF(r->out, "]\r\n");
retval = 0;
done:
if (mx)
xml_free(mx);
if (resbuf)
cbuf_free(resbuf);
return retval;
}
/*!
* @note XXX hardcoded that 'nordunet' is template and not included
*/
static int
api_agents(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
cxobj *ax = NULL;
cxobj **xvec = NULL;
cxobj *x = NULL;
int i;
int j;
size_t xlen;
if (get_db_entry("sender", NULL, NULL, &ax) < 0)
goto done;
if (xpath_vec(ax, "//sender", &xvec, &xlen) < 0)
goto done;
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
// FCGX_FPrintF(r->out, "{ \"agents\":[");
FCGX_FPrintF(r->out, "[");
x = NULL;
j = 0;
for (i=0; i< xlen; i++){
x = xvec[i];
/* XXX: 'nordunet' hardcoded */
if (strcmp("nordunet", xml_find_body(x, "name")) == 0)
continue;
if (j++)
FCGX_FPrintF(r->out, ", ");
FCGX_FPrintF(r->out, "\"%s\"", xml_find_body(x, "name"));
}
FCGX_FPrintF(r->out, "]\r\n");
retval = 0;
done:
if (xvec)
free(xvec);
if (ax)
xml_free(ax);
return retval;
}
/*! Get metric specification from yang
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
*
* GET api/metric_spec?name=rtt
* @retval [ {
* "rtt": {
* "description": "Round-trip latency",
* "type": "uint32",
* "units": "µs"
* }
* }]
*/
static int
api_metric_spec(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
cbuf *resbuf = NULL;
char *xmlstr;
cxobj *mx = NULL;
cxobj *dx;
cxobj *sx;
char *metric;
cxobj *x = NULL;
int i;
int j;
clicon_debug(1, "%s", __FUNCTION__);
if ((resbuf = cbuf_new()) == NULL)
goto done;
if ((metric = cvec_find_str(qvec, "name")) == NULL){
if (netconf_rpc(resbuf,
"<rpc><grideye><metric_spec/></grideye></rpc>]]>]]>") < 0){
clicon_debug(1, "%s error", __FUNCTION__);
goto done;
}
}
else {
if (netconf_rpc(resbuf,
"<rpc><grideye><metric_spec>%s</metric_spec></grideye></rpc>]]>]]>",
metric) < 0){
clicon_debug(1, "%s error", __FUNCTION__);
goto done;
}
}
xmlstr = cbuf_get(resbuf);
clicon_debug(1, "%s xmlstr: %s", __FUNCTION__, xmlstr);
if (clicon_xml_parse_string(&xmlstr, &mx) < 0){
clicon_debug(1, "err:%d reason:%s", clicon_errno, clicon_err_reason);
goto done;
}
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
// FCGX_FPrintF(r->out, "{ \"%s\":[", );
FCGX_FPrintF(r->out, "[{");
/*
{
"rtt":{
"description":"Round-trip latency",
"type":"uint32",
"units":"µs"
}} */
if ((dx = xpath_first(mx, "//data")) != NULL){
j = 0;
x = NULL;
while ((x = xml_child_each(dx, x, -1)) != NULL) {
if (j++)
FCGX_FPrintF(r->out, ", ");
FCGX_FPrintF(r->out, "\"%s\":{", xml_name(x));
sx = NULL;
i = 0;
while ((sx = xml_child_each(x, sx, -1)) != NULL) {
if (i++)
FCGX_FPrintF(r->out, ", ");
FCGX_FPrintF(r->out, "\"%s\":\"%s\"",
xml_name(sx), xml_body(sx));
}
FCGX_FPrintF(r->out, "}");
}
}
FCGX_FPrintF(r->out, "}]\r\n");
retval = 0;
done:
if (mx)
xml_free(mx);
if (resbuf)
cbuf_free(resbuf);
return retval;
}
/*! Generic REST GET method
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] dvec Stream input data
* @param[in] qvec Vector of query string (QUERY_STRING)
* Example:
* curl -G http://localhost/api/data/profile/name=default/metric/rtt
*/
static int
api_data_get(FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec)
{
int retval = -1;
cg_var *cv;
char *val;
int i;
cbuf *res = NULL;
cbuf *path = NULL;
cbuf *path1 = NULL;
char *xmlstr;
cxobj *xt = NULL;
cxobj *xg = NULL;
cbuf *cbx = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((path = cbuf_new()) == NULL)
goto done;
if ((path1 = cbuf_new()) == NULL) /* without [] qualifiers */
goto done;
cv = NULL;
cprintf(path1, "/");
/* translate eg a/b=c -> a/[b=c] */
for (i=pi; i<cvec_len(pcvec); i++){
cv = cvec_i(pcvec, i);
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
cprintf(path, "[%s=%s]", cv_name_get(cv), val);
free(val);
}
else{
cprintf(path, "%s%s", (i==pi?"":"/"), cv_name_get(cv));
cprintf(path1, "/%s", cv_name_get(cv));
}
}
clicon_debug(1, "path:%s", cbuf_get(path));
if ((res = cbuf_new()) == NULL)
goto done;
if (netconf_rpc(res,
"<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"%s\" /></get-config></rpc>]]>]]>",
cbuf_get(path)) < 0){
clicon_debug(1, "%s error", __FUNCTION__);
goto done;
}
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+xml\r\n");
FCGX_FPrintF(r->out, "\r\n");
xmlstr = cbuf_get(res);
if (clicon_xml_parse_string(&xmlstr, &xt) < 0){
clicon_debug(1, "err:%d reason:%s", clicon_errno, clicon_err_reason);
goto done;
}
if ((xg = xpath_first(xt, cbuf_get(path1))) != NULL){
if ((cbx = cbuf_new()) == NULL)
goto done;
if (clicon_xml2cbuf(cbx, xg, 0, 0) < 0)
goto done;
FCGX_FPrintF(r->out, "%s\r\n", cbuf_get(cbx));
}
retval = 0;
done:
if (cbx)
cbuf_free(cbx);
if (xt)
xml_free(xt);
if (res)
cbuf_free(res);
if (path)
cbuf_free(path);
if (path1)
cbuf_free(path1);
return retval;
}
/*! Generic REST PUT method
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* Example:
* curl -X PUT -d enable=true http://localhost/api/data/profile=default/metric=rtt
*/
static int
api_data_put(FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
cg_var *cv;
int i;
char *val;
cbuf *cmd = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((cmd = cbuf_new()) == NULL)
goto done;
if (pi > cvec_len(pcvec)){
retval = notfound(r);
goto done;
}
cv = NULL;
for (i=pi; i<cvec_len(pcvec); i++){
cv = cvec_i(pcvec, i);
cprintf(cmd, "%s ", cv_name_get(cv));
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
if (strlen(val))
cprintf(cmd, "%s ", val);
free(val);
}
}
if (cvec_len(dvec)==0)
goto done;
cv = cvec_i(dvec, 0);
cprintf(cmd, "%s ", cv_name_get(cv));
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
if (strlen(val))
cprintf(cmd, "%s ", val);
free(val);
}
clicon_debug(1, "cmd:%s", cbuf_get(cmd));
if (cli_cmd(r, "configure", cbuf_get(cmd)) < 0)
goto done;
if (cli_cmd(r, "configure", "commit") < 0)
goto done;
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
done:
if (cmd)
cbuf_free(cmd);
return retval;
}
/*! Generic REST DELETE method
* Example:
* curl -X DELETE http://localhost/api/data/profile=default/metric/rtt
* @note cant do leafs
*/
static int
api_data_delete(FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec)
{
int retval = -1;
cg_var *cv;
int i;
char *val;
cbuf *cmd = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((cmd = cbuf_new()) == NULL)
goto done;
if (pi >= cvec_len(pcvec)){
retval = notfound(r);
goto done;
}
cprintf(cmd, "no ");
cv = NULL;
for (i=pi; i<cvec_len(pcvec); i++){
cv = cvec_i(pcvec, i);
cprintf(cmd, "%s ", cv_name_get(cv));
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
if (strlen(val))
cprintf(cmd, "%s ", val);
free(val);
}
}
clicon_debug(1, "cmd:%s", cbuf_get(cmd));
if (cli_cmd(r, "configure", cbuf_get(cmd)) < 0)
goto done;
if (cli_cmd(r, "configure", "commit") < 0)
goto done;
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
done:
if (cmd)
cbuf_free(cmd);
return retval;
}
/*! Generic REST method, GET, PUT, DELETE
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] dvec Stream input data
* @param[in] qvec Vector of query string (QUERY_STRING)
* data - implement restconf
* Eg:
* curl -X PUT -d enable=true http://localhost/api/data/profile=default/metric=rtt
* Uses cli, could have used netconf with some yang help.
* XXX But really this module should be a restconf module to clixon
*/
static int
api_data(FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
char *request_method;
clicon_debug(1, "%s", __FUNCTION__);
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
if (strcmp(request_method, "GET")==0)
retval = api_data_get(r, pcvec, pi, qvec);
else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(r, pcvec, pi, qvec, dvec);
else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(r, pcvec, pi, qvec);
else
retval = notfound(r);
return retval;
}
/*! Get user (from cookie)
* @param[in] r Fastcgi request handle
* @param[in] pvec Vector of path (DOCUMENT_URI)
* @param[in] pn Length of path
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
* @retval {
name:"kalle",
template: "asender",
profile: "default",
dbuser:"john",
dbpasswd: "au",
agents: ["a1", "a2",..]
}
*/
static int
api_user(FCGX_Request *r,
char **pvec,
int pn,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
char *cookie;
char *cval = NULL;
cxobj *ux = NULL;
cxobj *sx = NULL;
cxobj *x = NULL;
char *user;
char *id;
char *dbuser;
char *dbpasswd;
char *plot_ival;
char *plot_dur;
char *profile = NULL;
cxobj *profx = NULL;
char *template = DEFAULT_TEMPLATE;
cxobj **xv = NULL;
size_t xlen;
int i;
clicon_debug(1, "%s", __FUNCTION__);
if ((cookie = FCGX_GetParam("HTTP_COOKIE", r->envp)) == NULL){
FCGX_SetExitStatus(403, r->out);
goto done;
}
if (get_user_cookie(cookie, USER_COOKIE, &cval) <0)
goto done;
if (cval == NULL){ /* Cookie not found, same as no cookie string found */
FCGX_SetExitStatus(403, r->out);
goto done;
}
if (get_db_entry("user", "id", cval, &ux) < 0)
goto done;
if (check_credentials(NULL, ux) == 0){
FCGX_SetExitStatus(403, r->out);
goto done;
}
if ((x = xpath_first(ux, "//user/name")) == NULL)
goto done;
user = xml_body(x);
if ((x = xpath_first(ux, "//user/template")) != NULL)
template = xml_body(x);
if ((x = xpath_first(ux, "//user/profile")) != NULL)
profile = xml_body(x);
if ((x = xpath_first(ux, "//user/id")) == NULL)
goto done;
id = xml_body(x);
if ((x = xpath_first(ux, "//user/resultdb/username")) == NULL)
goto done;
dbuser = xml_body(x);
if ((x = xpath_first(ux, "//user/resultdb/password")) == NULL)
goto done;
dbpasswd = xml_body(x);
/* Get profile including plot settings and metric rules */
if (get_db_entry("profile", "name", profile, &profx) < 0)
goto done;
if ((x = xpath_first(profx, "//profile/interval")) == NULL)
goto done;
plot_ival = xml_body(x);
if ((x = xpath_first(profx, "//profile/duration")) == NULL)
goto done;
plot_dur = xml_body(x);
/* Get all senders with matching id */
if (get_db_entry("sender", "userid", cval, &sx) < 0)
goto done;
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "{\"name\": \"%s\","
"\"id\":\"%s\","
"\"template\":\"%s\","
"\"profile\":\"%s\","
"\"dbuser\":\"%s\","
"\"dbpasswd\":\"%s\","
"\"plot_ival\":\"%s\","
"\"plot_dur\":\"%s\","
"\"agents\":[",
user, id, template, profile, dbuser, dbpasswd,
plot_ival, plot_dur);
if (xpath_vec(sx, "//sender", &xv, &xlen) < 0)
goto done;
for (i=0; i<xlen; i++){
if ((x = xpath_first(xv[i], "name")) == NULL)
continue;
if (i)
FCGX_FPrintF(r->out, ",");
FCGX_FPrintF(r->out, "\"%s\"", xml_body(x));
}
FCGX_FPrintF(r->out, "]}");
retval = 0;
done:
clicon_debug(1, "%s end %d", __FUNCTION__, retval);
if (xv)
free(xv);
if (ux)
xml_free(ux);
if (sx)
xml_free(sx);
return retval;
}
/*! Process a FastCGI request
* @param[in] r Fastcgi request handle
*/
static int
request_process(FCGX_Request *r)
{
int retval = -1;
char *path;
char *query;
char *method;
char **pvec;
int pn;
cvec *qvec = NULL;
cvec *dvec = NULL;
cvec *pcvec = NULL; /* for rest api */
cbuf *cb = NULL;
char *data;
clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
query = FCGX_GetParam("QUERY_STRING", r->envp);
if ((pvec = clicon_strsplit(path, "/", &pn, __FUNCTION__)) == NULL)
goto done;
if (str2cvec(query, '&', '=', &qvec) < 0)
goto done;
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = readdata(r)) == NULL)
goto done;
data = cbuf_get(cb);
clicon_debug(1, "DATA=%s", data);
if (str2cvec(data, '&', '=', &dvec) < 0)
goto done;
method = pvec[2];
retval = 0;
test(r, 1);
if (strcmp(method, "cli") == 0)
retval = api_cli(r, pvec+3, pn-3,
qvec, data);
else if (strcmp(method, "netconf") == 0)
retval = api_netconf(r, pvec+3, pn-3,
qvec, data);
else if (strcmp(method, "login") == 0)
retval = api_login(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "logout") == 0)
retval = api_logout(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "signup") == 0)
retval = api_signup(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "settings") == 0)
retval = api_settings(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "callhome") == 0)
retval = api_callhome(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "metric_rules") == 0)
retval = api_metric_rules(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "metrics") == 0)
retval = api_metrics(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "agents") == 0)
retval = api_agents(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "metric_spec") == 0)
retval = api_metric_spec(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "data") == 0) /* restconf, skip /api/data */
retval = api_data(r, pcvec, 2, qvec, dvec);
else if (strcmp(method, "user") == 0)
retval = api_user(r, pvec+3, pn-3,
qvec, dvec);
else if (strcmp(method, "test") == 0)
retval = test(r, 0);
else
retval = notfound(r);
done:
if (dvec)
cvec_free(dvec);
if (qvec)
cvec_free(qvec);
if (pcvec)
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Usage help routine
* @param[in] argv0 command line
* @param[in] h Clicon handle
*/
static void
usage(clicon_handle h,
char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D \t\tDebug. Log to syslog\n"
"\t-f <file>\tConfiguration file (mandatory)\n",
argv0
);
}
/*! Main routine for grideye fastcgi API
*/
int
main(int argc,
char **argv)
{
int retval = -1;
int sock;
FCGX_Request request;
FCGX_Request *r = &request;
char c;
char *sockpath;
char *path;
clicon_handle h;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_SYSLOG);
/* Create handle */
if ((h = clicon_handle_init()) == NULL)
goto done;
while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1)
switch (c) {
case 'h':
usage(h, argv[0]);
break;
case 'D' : /* debug */
debug = 1;
break;
case 'f': /* override config file */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
default:
usage(h, argv[0]);
break;
}
argc -= optind;
argv += optind;
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
clicon_debug_init(debug, NULL);
/* Find and read configfile */
if (clicon_options_main(h) < 0)
goto done;
clicon_debug(1, "%s", argv[0]);
if ((sockpath = clicon_option_str(h, "CLICON_RESTCONF_PATH")) == NULL){
clicon_err(OE_CFG, errno, "No CLICON_RESTCONF_PATH in clixon configure file");
goto done;
}
if ((sock = FCGX_OpenSocket(sockpath, 10)) < 0){
clicon_err(OE_CFG, errno, "FCGX_OpenSocket");
goto done;
}
if (FCGX_Init() != 0){
clicon_err(OE_CFG, errno, "FCGX_Init");
goto done;
}
if (FCGX_InitRequest(r, sock, 0) != 0){
clicon_err(OE_CFG, errno, "FCGX_InitRequest");
goto done;
}
while (1) {
if (FCGX_Accept_r(r) < 0) {
clicon_err(OE_CFG, errno, "FCGX_Accept_r");
goto done;
}
clicon_debug(1, "------------");
if ((path = FCGX_GetParam("DOCUMENT_URI", r->envp)) != NULL){
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0 ||
strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)-1) == 0)
request_process(r);
else{
clicon_debug(1, "top-level not found");
notfound(r);
}
}
else
clicon_debug(1, "NULL URI");
FCGX_Finish_r(r);
}
retval = 0;
done:
return retval;
}