diff --git a/apps/Makefile.in b/apps/Makefile.in
index cb3093fc..f1aa87b4 100644
--- a/apps/Makefile.in
+++ b/apps/Makefile.in
@@ -27,7 +27,7 @@ LIBS = @LIBS@
SHELL = /bin/sh
-SUBDIRS = cli backend dbctrl netconf xmldb
+SUBDIRS = cli backend dbctrl netconf xmldb restconf
.PHONY: all clean depend install $(SUBDIRS)
diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c
index 6967b029..de1c3a91 100644
--- a/apps/netconf/netconf_main.c
+++ b/apps/netconf/netconf_main.c
@@ -284,11 +284,14 @@ terminate(clicon_handle h)
}
-/*
- * usage
+/*! Usage help routine
+ * @param[in] h Clicon handle
+ * @param[in] argv0 command line
*/
static void
-usage(char *argv0, clicon_handle h)
+usage(clicon_handle h,
+ char *argv0,
+
{
char *netconfdir = clicon_netconf_dir(h);
@@ -328,14 +331,14 @@ main(int argc, char **argv)
while ((c = getopt(argc, argv, NETCONF_OPTS)) != -1)
switch (c) {
case 'h' : /* help */
- usage(argv[0], h);
+ usage(h, argv[0]);
break;
case 'D' : /* debug */
debug = 1;
break;
case 'f': /* override config file */
if (!strlen(optarg))
- usage(argv[0], h);
+ usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
case 'S': /* Log on syslog */
@@ -369,11 +372,11 @@ main(int argc, char **argv)
break;
case 'd': /* Plugin directory */
if (!strlen(optarg))
- usage(argv[0], h);
+ usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_NETCONF_DIR", optarg);
break;
default:
- usage(argv[0], h);
+ usage(h, argv[0]);
break;
}
argc -= optind;
diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in
new file mode 100644
index 00000000..da28536d
--- /dev/null
+++ b/apps/restconf/Makefile.in
@@ -0,0 +1,96 @@
+#
+# 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
+# .
+#
+VPATH = @srcdir@
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+CC = @CC@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+wwwdir = /www-data
+libexecdir = @libexecdir@
+localstatedir = @localstatedir@
+sysconfdir = @sysconfdir@
+
+SH_SUFFIX = @SH_SUFFIX@
+CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
+CLIXON_MINOR = @CLIXON_VERSION_MINOR@
+
+# Use this clixon lib for linking
+CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR)
+
+# For dependency
+LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB)
+
+LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLIXON_LIB)
+
+CPPFLAGS = @CPPFLAGS@
+
+INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
+
+SRC = restconf_lib.c
+
+OBJS = $(SRC:.c=.o)
+
+APPSRC = restconf_main.c
+APPOBJ = $(APPSRC:.c=.o)
+APPL = clixon_restconf
+
+all: $(APPL)
+
+clean:
+ rm -f $(OBJS) *.core $(APPL) $(APPOBJ)
+
+distclean: clean
+ rm -f Makefile *~ .depend
+
+# Put demon in bin
+# Put other executables in libexec/
+# Also create a libexec/ directory for writeable/temporary files.
+# Put config file in etc/
+install: $(APPL)
+ install -d $(DESTDIR)$(wwwdir)
+ install $(APPL) $(DESTDIR)$(wwwdir)
+
+install-include:
+
+uninstall:
+ rm -f $(wwwdir)/$(APPL)
+
+.SUFFIXES:
+.SUFFIXES: .c .o
+
+.c.o:
+ $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $<
+
+$(APPL) : $(APPOBJ) $(OBJS) $(LIBDEPS)
+ $(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) $(LIBS) -o $@
+
+TAGS:
+ find . -name '*.[chyl]' -print | etags -
+
+depend:
+ $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
+
+#include .depend
+
diff --git a/apps/restconf/README b/apps/restconf/README
new file mode 100644
index 00000000..66fd1a38
--- /dev/null
+++ b/apps/restconf/README
@@ -0,0 +1,32 @@
+Work-in-progress restconf server
+
+See draft-ietf-netconf-restconf-13.txt
+
+Example nginx-default file:
+server {
+ listen 80 default_server;
+ listen [::]:80 default_server ipv6only=on;
+
+ index index.html index.htm;
+
+ # Make site accessible from http://localhost/
+ server_name localhost;
+
+ location / {
+ root /usr/share/nginx/html;
+
+ # First attempt to serve request as file, then
+ # as directory, then fall back to displaying a 404.
+ try_files $uri $uri/ =404;
+ # Uncomment to enable naxsi on this location
+ # include /etc/nginx/naxsi.rules
+
+ }
+
+ # pass the REST API to FastCGI server
+ location /restconf {
+ root /usr/share/nginx/html/restconf;
+ fastcgi_pass unix:/www-data/fastcgi_api.sock;
+ include fastcgi_params;
+ }
+}
\ No newline at end of file
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
new file mode 100644
index 00000000..22a670b4
--- /dev/null
+++ b/apps/restconf/restconf_lib.c
@@ -0,0 +1,1133 @@
+/*
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "restconf_lib.h"
+
+#define signal_set_mask(set) sigprocmask(SIG_SETMASK, (set), NULL)
+#define signal_get_mask(set) sigprocmask (0, NULL, (set))
+
+/* Some hardcoded paths */
+#define CLI_BIN "/usr/local/bin/clixon_cli"
+#define CLI_OPTS "-1 -q"
+
+/*
+ * Types (curl)
+ */
+struct curlbuf{
+ size_t b_len;
+ char *b_buf;
+};
+
+#ifdef notused
+static int _log = 0; /* to log or not to log */
+/*!
+ */
+int
+dbg_init(char *ident)
+{
+ openlog(ident, LOG_PID, LOG_USER);
+ _log++;
+ return 0;
+}
+
+
+/*!
+ */
+int
+dbg(char *format, ...)
+{
+ va_list args;
+ int len;
+ int retval = -1;
+ char *msg = NULL;
+
+ if (_log == 0)
+ return 0;
+ /* first round: compute length of debug message */
+ va_start(args, format);
+ len = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ /* allocate a message string exactly fitting the message length */
+ if ((msg = malloc(len+1)) == NULL){
+ fprintf(stderr, "malloc: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
+ goto done;
+ }
+ /* second round: compute write message from format and args */
+ va_start(args, format);
+ if (vsnprintf(msg, len+1, format, args) < 0){
+ va_end(args);
+ fprintf(stderr, "vsnprintf: %s\n", strerror(errno));
+ goto done;
+ }
+ va_end(args);
+ syslog(LOG_MAKEPRI(LOG_USER, LOG_DEBUG), "%s", msg);
+ retval = 0;
+ done:
+ if (msg)
+ free(msg);
+ return retval;
+}
+#endif /* notused */
+/*!
+ */
+int
+notfound(FCGX_Request *r)
+{
+ char *path;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ path = FCGX_GetParam("DOCUMENT_URI", r->envp);
+ FCGX_FPrintF(r->out, "Status: 404\r\n"); /* 404 not found */
+ FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
+ FCGX_FPrintF(r->out, "Grideye Not Found
\n");
+ FCGX_FPrintF(r->out, "The requested URL %s was not found on this server.\n",
+ path);
+ return 0;
+}
+
+
+
+/*! Map from keywords grideye config to influxdb interval format
+*/
+char *
+ival2influxdb(char *ival)
+{
+ if (strcmp(ival, "minute")==0)
+ return "1m";
+ else if (strcmp(ival, "hour")==0)
+ return "1h";
+ else if (strcmp(ival, "day")==0)
+ return "1d";
+ else if (strcmp(ival, "week")==0)
+ return "7d"; /* 1w is sunday to sunday */
+ else if (strcmp(ival, "month")==0)
+ return "30d";
+ else if (strcmp(ival, "year")==0 || strcmp(ival, "all")==0)
+ return "365d";
+ return "1w";
+}
+
+/* ripped from clicon_proc.c: clicon_proc_run()
+ * @inparam[in] instr pipe to process stdin.
+ */
+static int
+proc_run(char *cmd,
+ char *instr,
+ void (outcb)(char *, void *),
+ void *arg)
+{
+ char **argv;
+ char buf[512];
+ int outfd[2] = { -1, -1 };
+ int infd[2] = { -1, -1 };
+ int n;
+ int argc;
+ int status;
+ int retval = -1;
+ pid_t child;
+ sigfn_t oldhandler = NULL;
+ sigset_t oset;
+
+ clicon_debug(1, "%s %s", __FUNCTION__, cmd);
+ argv = clicon_sepsplit (cmd, " \t", &argc, __FUNCTION__);
+ if (!argv)
+ return -1;
+
+ if (pipe (outfd) == -1)
+ goto done;
+ if (pipe (infd) == -1)
+ goto done;
+
+ signal_get_mask(&oset);
+ // set_signal(SIGINT, clicon_proc_sigint, &oldhandler);
+
+ if ((child = fork ()) < 0) {
+ retval = -1;
+ goto done;
+ }
+
+ if (child == 0) { /* Child */
+
+ /* Unblock all signals except TSTP */
+ clicon_signal_unblock (0);
+ signal (SIGTSTP, SIG_IGN);
+
+ close (outfd[0]); /* Close unused read ends */
+ outfd[0] = -1;
+
+ close (infd[1]); /* Close unused read ends */
+ infd[1] = -1;
+
+ /* Divert stdout and stderr to pipes */
+ dup2 (outfd[1], STDOUT_FILENO);
+ if (0)
+ dup2 (outfd[1], STDERR_FILENO);
+
+ dup2 (infd[0], STDIN_FILENO);
+ execvp (argv[0], argv);
+ perror("execvp");
+ _exit(-1);
+ }
+
+ /* Parent */
+ /* Close unused read ends */
+ close (infd[0]);
+ infd[0] = -1;
+ if (instr){
+ if (write(infd[1], instr, strlen(instr)) < 0){
+ perror("write");
+ goto done;
+ }
+ close(infd[1]);
+ }
+
+ /* Close unused write ends */
+ close (outfd[1]);
+ outfd[1] = -1;
+ /* Read from pipe */
+ while ((n = read (outfd[0], buf, sizeof (buf)-1)) != 0) {
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ break;
+ }
+ buf[n] = '\0';
+ /* Pass read data to callback function is defined */
+ if (outcb)
+ outcb (buf, arg);
+ }
+ /* Wait for child to finish */
+ if(waitpid (child, &status, 0) == child)
+ retval = WEXITSTATUS(status);
+ else
+ retval = -1;
+ done:
+
+ /* Clean up all pipes */
+ if (outfd[0] != -1)
+ close (outfd[0]);
+ if (outfd[1] != -1)
+ close (outfd[1]);
+
+ /* Restore sigmask and fn */
+ signal_set_mask (&oset);
+ set_signal(SIGINT, oldhandler, NULL);
+
+ unchunk_group (__FUNCTION__);
+ clicon_debug(1, "%s end %d", __FUNCTION__, retval);
+ return retval;
+}
+
+/*! Open dir/filename and pipe to fast-cgi output
+ * @param[in] r Fastcgi request handle
+ * Either absolute path in filename _or_ concatenation of dir/filename.
+ */
+int
+openfile(FCGX_Request *r,
+ char *dir,
+ char *filename)
+{
+ int retval = -1;
+ char buf[512];
+ int f;
+ int n;
+ cbuf *cb = NULL;
+
+ if ((cb = cbuf_new()) == NULL)
+ goto done;
+ if (dir)
+ cprintf(cb, "%s/%s", dir, filename);
+ else
+ cprintf(cb, "%s", filename);
+ clicon_debug(1, "%s: %s", __FUNCTION__, cbuf_get(cb));
+ if ((f = open(cbuf_get(cb), O_RDONLY)) < 0){
+ clicon_debug(1, "open error: %s", strerror(errno));
+ perror("open");
+ goto done;
+ }
+ while ((n = read(f, buf, sizeof (buf)-1)) != 0) {
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("read");
+ goto done;
+ }
+ buf[n] = '\0';
+ FCGX_FPrintF(r->out, "%s", buf);
+ }
+ retval = 0;
+ done:
+ cbuf_free(cb);
+ return retval;
+}
+
+/*!
+ * @param[in] r Fastcgi request handle
+ */
+int
+errorfn(FCGX_Request *r,
+ char *root,
+ char *reason)
+{
+ FCGX_FPrintF(r->out, "Content-Type: text/html\r\n");
+ FCGX_FPrintF(r->out, "\r\n");
+ openfile(r, root, "www/login.html"); /* "user not specified" */
+ FCGX_FPrintF(r->out, "%s
\r\n", reason);
+ return 0;
+}
+
+/*! Split a string into a cligen variable vector using 1st and 2nd delimiter
+ * Split a string first into elements delimited by delim1, then into
+ * pairs delimited by delim2.
+ * @param[in] string String to split
+ * @param[in] delim1 First delimiter char that delimits between elements
+ * @param[in] delim2 Second delimiter char for pairs within an element
+ * @param[out] cvp Created cligen variable vector, NOTE: can be NULL
+ * @retval 0 on OK
+ * @retval -1 error
+ *
+ * Example, assuming delim1 = '&' and delim2 = '='
+ * a=b&c=d -> [[a,"b"][c="d"]
+ * kalle&c=d -> [[c="d"]] # Discard elements with no delim2
+ * XXX differentiate between error and null cvec.
+ */
+int
+str2cvec(char *string,
+ char delim1,
+ char delim2,
+ cvec **cvp)
+{
+ int retval = -1;
+ char *s;
+ char *s0 = NULL;;
+ char *val; /* value */
+ char *valu; /* unescaped value */
+ char *snext; /* next element in string */
+ cvec *cvv = NULL;
+ cg_var *cv;
+
+ clicon_debug(1, "%s %s", __FUNCTION__, string);
+ if ((s0 = strdup(string)) == NULL){
+ clicon_debug(1, "error strdup %s", strerror(errno));
+ goto err;
+ }
+ s = s0;
+ if ((cvv = cvec_new(0)) ==NULL){
+ clicon_debug(1, "error cvec_new %s", strerror(errno));
+ goto err;
+ }
+ while (s != NULL) {
+ /*
+ * In the pointer algorithm below:
+ * name1=val1; name2=val2;
+ * ^ ^ ^
+ * | | |
+ * s val snext
+ */
+ if ((snext = index(s, delim1)) != NULL)
+ *(snext++) = '\0';
+ if ((val = index(s, delim2)) != NULL){
+ *(val++) = '\0';
+ if ((valu = curl_easy_unescape(NULL, val, 0, NULL)) == NULL){
+ clicon_debug(1, "curl_easy_unescape %s", strerror(errno));
+ goto err;
+ }
+ if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){
+ clicon_debug(1, "error cvec_add %s", strerror(errno));
+ goto err;
+ }
+ cv_name_set(cv, s);
+ cv_string_set(cv, valu);
+ free(valu);
+ }
+ else{
+ if (strlen(s)){
+ if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){
+ clicon_debug(1, "error cvec_add %s", strerror(errno));
+ goto err;
+ }
+ cv_name_set(cv, s);
+ cv_string_set(cv, "");
+ }
+ }
+ s = snext;
+ }
+ retval = 0;
+ done:
+ *cvp = cvv;
+ if (s0)
+ free(s0);
+ return retval;
+ err:
+ if (cvv){
+ cvec_free(cvv);
+ cvv = NULL;
+ }
+ goto done;
+}
+
+/*!
+ */
+static void
+appendfn(char *str,
+ void *arg)
+{
+ cbuf *cb = (cbuf *)arg;
+
+ cprintf(cb, "%s", str);
+}
+
+/*!
+ */
+static void
+outputfn(char *str,
+ void *arg)
+{
+ FCGX_Request *r = (FCGX_Request *)arg;
+
+ FCGX_FPrintF(r->out, "%s", str);
+ clicon_debug(1, "%s: %s", __FUNCTION__, str);
+}
+
+/*! Send an RPC to netconf client and return result as string
+ * param[out] result output from cli as cligen buf
+ * param[in] format stdarg variable list format a la printf followed by args
+ */
+int
+netconf_rpc(cbuf *result,
+ char *format, ...)
+{
+ int retval = -1;
+ cbuf *cb = NULL;
+ va_list args;
+ int len;
+ char *data = NULL;
+ char *endtag;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ if ((cb = cbuf_new()) == NULL)
+ goto done;
+ va_start(args, format);
+ len = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if ((data = malloc(len+1)) == NULL){
+ fprintf(stderr, "malloc: %s\n", strerror(errno));
+ goto done;
+ }
+ va_start(args, format);
+ vsnprintf(data, len+1, format, args);
+ va_end(args);
+ cprintf(cb, "%s -f %s %s",
+ NETCONF_BIN, CONFIG_FILE, NETCONF_OPTS);
+ clicon_debug(1, "%s: cmd:%s", __FUNCTION__, cbuf_get(cb));
+ clicon_debug(1, "%s: data=%s", __FUNCTION__, data);
+ if (proc_run(cbuf_get(cb), data, appendfn, result) < 0)
+ goto done;
+ if ((endtag = strstr(cbuf_get(result), "]]>]]>")) != NULL)
+ *endtag = '\0';
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (data)
+ free(data);
+ return retval;
+}
+
+/*! send netconf command and return output to fastcgi request
+ * @param[in] r Fastcgi request handle
+ */
+int
+netconf_cmd(FCGX_Request *r,
+ char *data)
+{
+ int retval = -1;
+ cbuf *cb = NULL;
+
+ if ((cb = cbuf_new()) == NULL)
+ goto done;
+ cprintf(cb, "%s -f %s %s", NETCONF_BIN, CONFIG_FILE, NETCONF_OPTS);
+ if (proc_run(cbuf_get(cb), data, outputfn, r) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ return retval;
+}
+
+/*! Send an rpc to cli client and return result as string
+ * param[out] result output from cli as cligen buf
+ * param[in] mode CLI mode, typically "configure" or "operation"
+ * param[in] format stdarg variable list format a la printf followed by args
+ */
+int
+cli_rpc(cbuf *result,
+ char *mode,
+ char *format, ...)
+{
+ int retval = -1;
+ cbuf *cb = NULL;
+ va_list args;
+ int len;
+ char *cmd = NULL;
+
+ if ((cb = cbuf_new()) == NULL)
+ goto done;
+ va_start(args, format);
+ len = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if ((cmd = malloc(len+1)) == NULL){
+ fprintf(stderr, "malloc: %s\n", strerror(errno));
+ goto done;
+ }
+ va_start(args, format);
+ vsnprintf(cmd, len+1, format, args);
+ va_end(args);
+ cprintf(cb, "%s -f %s -m %s %s -- %s",
+ CLI_BIN, CONFIG_FILE, mode, CLI_OPTS, cmd);
+ if (proc_run(cbuf_get(cb), NULL, appendfn, result) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ if (cmd)
+ free(cmd);
+ return retval;
+}
+
+/*! send cli command and return output to fastcgi request
+ * @param[in] r Fastcgi request handle
+ * @param[in] mode Command mode, eg configure, operation
+ * @param[in] cmd Command to run in CLI
+ */
+int
+cli_cmd(FCGX_Request *r,
+ char *mode,
+ char *cmd)
+{
+ int retval = -1;
+ cbuf *cb = NULL;
+
+ if ((cb = cbuf_new()) == NULL)
+ goto done;
+ cprintf(cb, "%s -f %s -m %s %s -- %s",
+ CLI_BIN, CONFIG_FILE, mode, CLI_OPTS, cmd);
+ if (proc_run(cbuf_get(cb), NULL, outputfn, r) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ return retval;
+}
+
+/*! Check in db if user exists, then if passwd matches or if passwd == null
+ * @param[in] passwd Passwd. If NULL dont do passwd check
+ * @param[in] cx Parse-tree with matching user
+ */
+int
+check_credentials(char *passwd,
+ cxobj *cx)
+{
+ int retval = 0;
+ cxobj *x;
+
+ if ((x = xpath_first(cx, "//user/resultdb/password")) == NULL)
+ goto done;
+ clicon_debug(1, "%s passwd=%s", __FUNCTION__, xml_body(x));
+ if (passwd == NULL || strcmp(passwd, xml_body(x))==0)
+ retval = 1;
+ done:
+ return retval;
+}
+
+/*! Get matching top-level list entry given an attribute value pair, eg name="foo"
+ * Construct an XPATH query and send to netconf clixon client to get config xml.
+ * The xpath is canonically formed as: //[=]
+ * @param[in] entry XPATH base path
+ * @param[in] attr Attribute name, if not given skip the [addr=val]
+ * @param[in] val Value of attribute
+ * @param[out] cx This is an xml tree containing the result
+ */
+int
+get_db_entry(char *entry,
+ char *attr,
+ char *val,
+ cxobj **cx)
+{
+ int retval = -1;
+ cbuf *resbuf = NULL;
+ char *xmlstr;
+
+ clicon_debug(1, "%s //%s[%s=%s]", __FUNCTION__, entry, attr, val);
+ if ((resbuf = cbuf_new()) == NULL)
+ goto done;
+ if (attr){
+ if (netconf_rpc(resbuf,
+ "]]>]]>",
+ entry, attr, val) < 0){
+ clicon_debug(1, "%s error", __FUNCTION__);
+ goto done;
+ }
+ }
+ else
+ if (netconf_rpc(resbuf,
+ "]]>]]>",
+ entry) < 0){
+ clicon_debug(1, "%s error", __FUNCTION__);
+ goto done;
+ }
+ xmlstr = cbuf_get(resbuf);
+ // clicon_debug(1, "%s: %s\n", __FUNCTION__, xmlstr);
+ if (clicon_xml_parse_string(&xmlstr, cx) < 0){
+ clicon_debug(1, "err:%d reason:%s", clicon_errno, clicon_err_reason);
+ goto done;
+ }
+ if (*cx == NULL)
+ goto done;
+ retval = 0;
+ done:
+ if (resbuf)
+ cbuf_free(resbuf);
+ return retval;
+}
+
+/*! Parse a cookie string and return value of cookie attribute
+ * @param[in] cookiestr cookie string according to rfc6265 (modified)
+ * @param[in] attribute cookie attribute
+ * @param[out] val malloced cookie value, free with free()
+ */
+int
+get_user_cookie(char *cookiestr,
+ char *attribute,
+ char **val)
+{
+ int retval = -1;
+ cvec *cvv = NULL;
+ char *c;
+
+ clicon_debug(1, "%s cookiestr=%s", __FUNCTION__, cookiestr);
+ if (str2cvec(cookiestr, ';', '=', &cvv) < 0)
+ goto done;
+ if ((c = cvec_find_str(cvv, attribute)) != NULL){
+ if ((*val = strdup(c)) == NULL)
+ goto done;
+ }
+ retval = 0;
+ done:
+ if (cvv)
+ cvec_free(cvv);
+ clicon_debug(1, "%s end %d", __FUNCTION__, retval);
+ return retval;
+}
+
+/*!
+ * @param[in] r Fastcgi request handle
+ */
+static int
+printparam(FCGX_Request *r,
+ char *e,
+ int dbgp)
+{
+ char *p = FCGX_GetParam(e, r->envp);
+
+ if (dbgp)
+ clicon_debug(1, "%s = '%s'", e, p?p:"");
+ else
+ FCGX_FPrintF(r->out, "%s = '%s'\n", e, p?p:"");
+ return 0;
+}
+
+/*!
+ * @param[in] r Fastcgi request handle
+ */
+int
+test(FCGX_Request *r,
+ int dbg)
+{
+ printparam(r, "QUERY_STRING", dbg);
+ printparam(r, "REQUEST_METHOD", dbg);
+ printparam(r, "CONTENT_TYPE", dbg);
+ printparam(r, "CONTENT_LENGTH", dbg);
+ printparam(r, "SCRIPT_FILENAME", dbg);
+ printparam(r, "SCRIPT_NAME", dbg);
+ printparam(r, "REQUEST_URI", dbg);
+ printparam(r, "DOCUMENT_URI", dbg);
+ printparam(r, "DOCUMENT_ROOT", dbg);
+ printparam(r, "SERVER_PROTOCOL", dbg);
+ printparam(r, "GATEWAY_INTERFACE", dbg);
+ printparam(r, "SERVER_SOFTWARE", dbg);
+ printparam(r, "REMOTE_ADDR", dbg);
+ printparam(r, "REMOTE_PORT", dbg);
+ printparam(r, "SERVER_ADDR", dbg);
+ printparam(r, "SERVER_PORT", dbg);
+ printparam(r, "SERVER_NAME", dbg);
+ printparam(r, "HTTP_COOKIE", dbg);
+ printparam(r, "HTTPS", dbg);
+
+ return 0;
+}
+
+/*!
+ * @param[in] r Fastcgi request handle
+ */
+cbuf *
+readdata(FCGX_Request *r)
+{
+ int c;
+ cbuf *cb;
+
+ if ((cb = cbuf_new()) == NULL)
+ return NULL;
+ while ((c = FCGX_GetChar(r->in)) != -1)
+ cprintf(cb, "%c", c);
+ return cb;
+}
+
+
+/*! Create influxdb database
+ * @param[in] server_addr Name of http server. dns or ip
+ * @param[in] database Name of database to create
+ * @param[in] www_user Root admin user
+ * @param[in] www_passwd Password of root user
+ */
+int
+create_database(char *server_addr,
+ char *database,
+ char *www_user,
+ char *www_passwd)
+{
+ int retval = -1;
+ cbuf *curl = NULL;
+ cbuf *cdata = NULL;
+
+ if ((curl = cbuf_new()) == NULL) /* url */
+ goto done;
+ if ((cdata = cbuf_new()) == NULL) /* data */
+ goto done;
+ cprintf(curl, "http://%s:8086/db", server_addr);
+ cprintf(cdata, "{\"name\": \"%s\"}", database);
+ clicon_debug(1, "%s: %s %s", __FUNCTION__, cbuf_get(curl), cbuf_get(cdata));
+ if (url_post(cbuf_get(curl), www_user, www_passwd, cbuf_get(cdata), NULL, NULL) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (curl)
+ cbuf_free(curl);
+ if (cdata)
+ cbuf_free(cdata);
+ return retval;
+}
+
+/*! Create user passwd in database
+ * @param[in] server_addr Name of http server. dns or ip
+ * @param[in] database Name of database to create
+ * @param[in] user User to create for database (not root)
+ * @param[in] passwd Password of user
+ * @param[in] www_user Root user to create databases
+ * @param[in] www_passwd Password of root user
+ */
+int
+create_db_user(char *server_addr,
+ char *database,
+ char *user,
+ char *password,
+ char *www_user,
+ char *www_passwd)
+{
+ int retval = -1;
+ cbuf *curl = NULL;
+ cbuf *cdata = NULL;
+
+ if ((curl = cbuf_new()) == NULL) /* url */
+ goto done;
+ if ((cdata = cbuf_new()) == NULL) /* data */
+ goto done;
+ cprintf(curl, "http://%s:8086/db/%s/users", server_addr, database);
+ cprintf(cdata, "{\"name\": \"%s\", \"password\": \"%s\"}",
+ user, password);
+ if (url_post(cbuf_get(curl), www_user, www_passwd, cbuf_get(cdata), NULL, NULL) < 0)
+ goto done;
+ clicon_debug(1, "%s: %s %s", __FUNCTION__, cbuf_get(curl), cbuf_get(cdata));
+ cbuf_reset(curl);
+ cbuf_reset(cdata);
+ cprintf(curl, "http://%s:8086/db/%s/users/%s", server_addr, database, user);
+ cprintf(cdata, "{\"admin\": true}", password);
+ if (url_post(cbuf_get(curl), www_user, www_passwd, cbuf_get(cdata), NULL, NULL) < 0)
+ goto done;
+ clicon_debug(1, "%s: %s %s", __FUNCTION__, cbuf_get(curl), cbuf_get(cdata));
+ retval = 0;
+ done:
+ if (curl)
+ cbuf_free(curl);
+ if (cdata)
+ cbuf_free(cdata);
+ return retval;
+}
+
+/*! Send a curl POST request
+ * @retval -1 fatal error
+ * @retval 0 expect set but did not expected return or other non-fatal error
+ * @retval 1 ok
+ * Note: curl_easy_perform blocks
+ * Note: New handle is created every time, the handle can be re-used for better TCP performance
+ * @see same function (url_post) in grideye_curl.c
+ */
+int
+url_post(char *url,
+ char *username,
+ char *passwd,
+ char *putdata,
+ char *expect,
+ char **getdata)
+{
+ CURL *curl = NULL;
+ char *err;
+ int retval = -1;
+ cxobj *xr = NULL; /* reply xml */
+ struct curlbuf b = {0, };
+ CURLcode errcode;
+ char *output = NULL;
+
+ /* Try it with curl -X PUT -d '*/
+ clicon_debug(1, "%s: curl -X POST -d '%s' %s (%s:%s)",
+ __FUNCTION__, putdata, url, username, passwd);
+ /* Set up curl for doing the communication with the controller */
+ if ((curl = curl_easy_init()) == NULL) {
+ clicon_debug(1, "curl_easy_init");
+ goto done;
+ }
+ if ((output = curl_easy_escape(curl, putdata, 0)) == NULL){
+ clicon_debug(1, "curl_easy_escape");
+ goto done;
+ }
+ if ((err = chunk(CURL_ERROR_SIZE, __FUNCTION__)) == NULL) {
+ clicon_debug(1, "%s: chunk", __FUNCTION__);
+ goto done;
+ }
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ if (username)
+ curl_easy_setopt(curl, CURLOPT_USERNAME, username);
+ if (passwd)
+ curl_easy_setopt(curl, CURLOPT_PASSWORD, passwd);
+ curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err);
+ curl_easy_setopt(curl, CURLOPT_POST, 1);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, putdata);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(putdata));
+ if (debug)
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+ if ((errcode = curl_easy_perform(curl)) != CURLE_OK){
+ clicon_debug(1, "%s: curl: %s(%d)", __FUNCTION__, err, errcode);
+ retval = 0;
+ goto done;
+ }
+ if (expect){
+ if (b.b_buf == NULL){
+ clicon_debug(1, "%s: no match", __FUNCTION__);
+ retval = 0;
+ goto done;
+ }
+ else{
+ clicon_debug(1, "%s: reply:%s", __FUNCTION__, b.b_buf);
+ if (clicon_xml_parse_string(&b.b_buf, &xr) < 0)
+ goto done;
+ if (xpath_first(xr, expect) == NULL){
+ clicon_debug(1, "%s: no match", __FUNCTION__);
+ retval = 0;
+ goto done;
+ }
+ }
+ }
+ if (getdata && b.b_buf){
+ *getdata = b.b_buf;
+ b.b_buf = NULL;
+ }
+ retval = 1;
+ done:
+ unchunk_group(__FUNCTION__);
+ if (output)
+ curl_free(output);
+ if (xr != NULL)
+ xml_free(xr);
+ if (b.b_buf)
+ free(b.b_buf);
+ if (curl)
+ curl_easy_cleanup(curl); /* cleanup */
+ return retval;
+}
+
+static const char Base64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char Pad64 = '=';
+
+#if notdef
+/*! return length of dst */
+int
+b64_encode(const char *src,
+ char *dst,
+ int dlen)
+{
+ int i;
+ int s = 0;
+ int j = 0;
+ uint32_t data = 0;
+ int slen;
+
+ slen = strlen(src);
+ /*
+ * The encoded length will "grow" 33% in respect to the original length,
+ * i.e. 3 character will be 4 characters
+ */
+ if (((slen * 7) / 5) > dlen) {
+ clicon_debug(1, "Destination buffer to small.\n");
+ return -1;
+ }
+ for (i = 0; i < slen; i++) {
+ data <<= 8;
+ data |= ((uint32_t)src[i] & 0x000000ff);
+ s++;
+ if (s == 3) {
+ dst[j++] = Base64[((data >> 18) & 0x3f)];
+ dst[j++] = Base64[((data >> 12) & 0x3f)];
+ dst[j++] = Base64[((data >> 6) & 0x3f)];
+ dst[j++] = Base64[(data & 0x3f)];
+ dst[j] = '\0';
+ data = 0;
+ s = 0;
+ }
+ }
+ switch (s) {
+ case 0:
+ break;
+ case 1:
+ data <<= 4;
+ dst[j++] = Base64[((data >> 6) & 0x3f)];
+ dst[j++] = Base64[(data & 0x3f)];
+ dst[j++] = Pad64;
+ dst[j++] = Pad64;
+ break;
+ case 2:
+ data <<= 2;
+ dst[j++] = Base64[((data >> 12) & 0x3f)];
+ dst[j++] = Base64[((data >> 6) & 0x3f)];
+ dst[j++] = Base64[(data & 0x3f)];
+ dst[j++] = Pad64;
+ break;
+ }
+ dst[j] = '\0';
+ return j;
+}
+#endif
+
+/* skips all whitespace anywhere.
+ converts characters, four at a time, starting at (or after)
+ src from base - 64 numbers into three 8 bit bytes in the target area.
+ it returns the number of data bytes stored at the target, or -1 on error.
+ */
+int
+b64_decode(const char *src,
+ char *target,
+ size_t targsize)
+{
+ int tarindex, state, ch;
+ char *pos;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ state = 0;
+ tarindex = 0;
+
+ while ((ch = *src++) != '\0') {
+ if (isspace(ch)) /* Skip whitespace anywhere. */
+ continue;
+
+ if (ch == Pad64)
+ break;
+
+ pos = strchr(Base64, ch);
+ if (pos == 0) /* A non-base64 character. */
+ return (-1);
+
+ switch (state) {
+ case 0:
+ if (target) {
+ if ((size_t)tarindex >= targsize)
+ return (-1);
+ target[tarindex] = (pos - Base64) << 2;
+ }
+ state = 1;
+ break;
+ case 1:
+ if (target) {
+ if ((size_t)tarindex + 1 >= targsize)
+ return (-1);
+ target[tarindex] |= (pos - Base64) >> 4;
+ target[tarindex+1] = ((pos - Base64) & 0x0f)
+ << 4 ;
+ }
+ tarindex++;
+ state = 2;
+ break;
+ case 2:
+ if (target) {
+ if ((size_t)tarindex + 1 >= targsize)
+ return (-1);
+ target[tarindex] |= (pos - Base64) >> 2;
+ target[tarindex+1] = ((pos - Base64) & 0x03)
+ << 6;
+ }
+ tarindex++;
+ state = 3;
+ break;
+ case 3:
+ if (target) {
+ if ((size_t)tarindex >= targsize)
+ return (-1);
+ target[tarindex] |= (pos - Base64);
+ }
+ tarindex++;
+ state = 0;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ /*
+ * We are done decoding Base-64 chars. Let's see if we ended
+ * on a byte boundary, and/or with erroneous trailing characters.
+ */
+
+ if (ch == Pad64) { /* We got a pad char. */
+ ch = *src++; /* Skip it, get next. */
+ switch (state) {
+ case 0: /* Invalid = in first position */
+ case 1: /* Invalid = in second position */
+ return (-1);
+
+ case 2: /* Valid, means one byte of info */
+ /* Skip any number of spaces. */
+ for ((void)NULL; ch != '\0'; ch = *src++)
+ if (!isspace(ch))
+ break;
+ /* Make sure there is another trailing = sign. */
+ if (ch != Pad64)
+ return (-1);
+ ch = *src++; /* Skip the = */
+ /* Fall through to "single trailing =" case. */
+ /* FALLTHROUGH */
+
+ case 3: /* Valid, means two bytes of info */
+ /*
+ * We know this char is an =. Is there anything but
+ * whitespace after it?
+ */
+ for ((void)NULL; ch != '\0'; ch = *src++)
+ if (!isspace(ch))
+ return (-1);
+
+ /*
+ * Now make sure for cases 2 and 3 that the "extra"
+ * bits that slopped past the last full byte were
+ * zeros. If we don't check them, they become a
+ * subliminal channel.
+ */
+ if (target && target[tarindex] != 0)
+ return (-1);
+ }
+ } else {
+ /*
+ * We ended by seeing the end of the string. Make sure we
+ * have no partial bytes lying around.
+ */
+ if (state != 0)
+ return (-1);
+ }
+
+ return (tarindex);
+}
+
+/*! Get field from metric yang spec
+ * @param[in] metric Metric
+ * @param[in] fiels Which field in metric (eg type, units)
+ * @param[out] result Allocated string. Free after use
+ */
+static int
+get_metric_spec(char *metric,
+ char *field,
+ char **result)
+{
+ int retval = -1;
+ cbuf *resbuf = NULL;
+ char *xmlstr;
+ cxobj *mx;
+ cxobj *dx;
+ cxobj *sx;
+ char *val;
+
+ clicon_debug(1, "%s metric:%s", __FUNCTION__, metric);
+ *result = NULL;
+ if ((resbuf = cbuf_new()) == NULL)
+ goto done;
+ if (netconf_rpc(resbuf,
+ "%s]]>]]>",
+ metric) < 0){
+ clicon_debug(1, "%s error", __FUNCTION__);
+ goto done;
+ }
+ xmlstr = cbuf_get(resbuf);
+ clicon_debug(1, "xmlstr: %s", xmlstr);
+ if (clicon_xml_parse_string(&xmlstr, &mx) < 0){
+ clicon_debug(1, "err:%d reason:%s", clicon_errno, clicon_err_reason);
+ goto done;
+ }
+ if ((dx = xpath_first(mx, "//data")) != NULL){
+ if ((sx = xpath_first(dx, metric)) != NULL){
+ if ((val = xml_find_body(sx, field)) != NULL)
+ *result = strdup(val);
+ }
+ }
+ retval = 0;
+ done:
+ if (resbuf)
+ cbuf_free(resbuf);
+ if (mx)
+ xml_free(mx);
+ return retval;
+}
+
+int
+metric_spec_description(char *metric,
+ char **result)
+{
+ return get_metric_spec(metric, "description", result);
+}
+
+int
+metric_spec_units(char *metric,
+ char **result)
+{
+ return get_metric_spec(metric, "units", result);
+}
diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h
new file mode 100644
index 00000000..d465fa9c
--- /dev/null
+++ b/apps/restconf/restconf_lib.h
@@ -0,0 +1,50 @@
+/*
+ *
+ * $COPYRIGHTSTATEMENT$
+ *
+ * $LICENSE$
+ * This is backend headend sender code, ie communication with a pmagent
+ */
+
+#ifndef _RESTCONF_LIB_H_
+#define _RESTCONF_LIB_H_
+
+/*
+ * Constants
+ */
+#define USER_COOKIE "c-user" /* connected user cookie */
+#define WWW_USER "root"
+#define WWW_PASSWD "9rundpaj" // XXX
+#define DEFAULT_TEMPLATE "nordunet" /* XXX Default sender template must be in conf */
+#define CONFIG_FILE "/usr/local/etc/grideye.conf"
+#define NETCONF_BIN "/usr/local/bin/clixon_netconf"
+#define NETCONF_OPTS "-qS"
+
+/*
+ * Prototypes
+ */
+int notfound(FCGX_Request *r);
+
+char *ival2influxdb(char *ival);
+int openfile(FCGX_Request *r, char *dir, char *filename);
+int errorfn(FCGX_Request *r, char *root, char *reason);
+int str2cvec(char *string, char delim1, char delim2, cvec **cvp);
+int netconf_rpc(cbuf *result, char *format, ...);
+int netconf_cmd(FCGX_Request *r, char *data);
+int cli_rpc(cbuf *result, char *mode, char *format, ...);
+int cli_cmd(FCGX_Request *r, char *mode, char *cmd);
+int check_credentials(char *passwd, cxobj *cx);
+int get_db_entry(char *entry, char *attr, char *val, cxobj **cx);
+int get_user_cookie(char *cookiestr, char *attribute, char **val);
+int test(FCGX_Request *r, int dbg);
+cbuf *readdata(FCGX_Request *r);
+
+int create_database(char *server_addr, char *database, char *www_user, char *www_passwd);
+int create_db_user(char *server_addr, char *database, char *user, char *password, char *www_user, char *www_passwd);
+int url_post(char *url, char *username, char *passwd, char *putdata,
+ char *expect, char **getdata);
+int b64_decode(const char *b64_buf, char *buf, size_t buf_len);
+int metric_spec_description(char *metric, char **result);
+int metric_spec_units(char *metric, char **result);
+
+#endif /* _RESTCONF_LIB_H_ */
diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
new file mode 100644
index 00000000..c2e5b63b
--- /dev/null
+++ b/apps/restconf/restconf_main.c
@@ -0,0 +1,1386 @@
+/*
+ *
+ 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
+ .
+
+ */
+
+/*
+ * 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=/metric= PUT data:enable=
+ * api/user
+ * api/test
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#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=& agent 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/[/]
+ * @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,
+ "]]>]]>", 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; iout, ",");
+ 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,
+ "]]>]]>") < 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,
+ "]]>]]>") < 0){
+ clicon_debug(1, "%s error", __FUNCTION__);
+ goto done;
+ }
+ }
+ else {
+ if (netconf_rpc(resbuf,
+ "%s]]>]]>",
+ 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 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,
+ "]]>]]>",
+ 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 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 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; iout, ",");
+ 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 \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;
+}
diff --git a/clixon.conf.cpp.cpp b/clixon.conf.cpp.cpp
index 94b588fc..dfb24e57 100644
--- a/clixon.conf.cpp.cpp
+++ b/clixon.conf.cpp.cpp
@@ -118,5 +118,8 @@ CLICON_XMLDB_DIR localstatedir/APPNAME
# Dont include keys in cvec in cli vars callbacks, ie a & k in 'a k ' ignored
# CLICON_CLI_VARONLY 1
+# FastCGI unix socket. Should be specified in webserver
+# Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock;
+CLICON_RESTCONF_PATH "/www-data/clicon_restconf.sock"
diff --git a/configure b/configure
index 1e890817..03fa00cd 100755
--- a/configure
+++ b/configure
@@ -4155,6 +4155,55 @@ _ACEOF
fi
+# restconf uses libcurl (I think?)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_global_init in -lcurl" >&5
+$as_echo_n "checking for curl_global_init in -lcurl... " >&6; }
+if ${ac_cv_lib_curl_curl_global_init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lcurl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char curl_global_init ();
+int
+main ()
+{
+return curl_global_init ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_curl_curl_global_init=yes
+else
+ ac_cv_lib_curl_curl_global_init=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_global_init" >&5
+$as_echo "$ac_cv_lib_curl_curl_global_init" >&6; }
+if test "x$ac_cv_lib_curl_curl_global_init" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBCURL 1
+_ACEOF
+
+ LIBS="-lcurl $LIBS"
+
+else
+ as_fn_error $? "libcurl missing" "$LINENO" 5
+fi
+
+
for ac_func in inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort strverscmp
do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
@@ -4168,6 +4217,55 @@ fi
done
+# Lives in libfcgi-dev
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for FCGX_Init in -lfcgi" >&5
+$as_echo_n "checking for FCGX_Init in -lfcgi... " >&6; }
+if ${ac_cv_lib_fcgi_FCGX_Init+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lfcgi $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char FCGX_Init ();
+int
+main ()
+{
+return FCGX_Init ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_fcgi_FCGX_Init=yes
+else
+ ac_cv_lib_fcgi_FCGX_Init=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_fcgi_FCGX_Init" >&5
+$as_echo "$ac_cv_lib_fcgi_FCGX_Init" >&6; }
+if test "x$ac_cv_lib_fcgi_FCGX_Init" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBFCGI 1
+_ACEOF
+
+ LIBS="-lfcgi $LIBS"
+
+else
+ as_fn_error $? "libfcgi-dev missing" "$LINENO" 5
+fi
+
+
# Check if extra keys inserted for database lists containing content. Eg A.n.foo = 3
# means A.3 $!a=foo exists
@@ -4193,7 +4291,7 @@ fi
-ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/dbctrl/Makefile apps/xmldb/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile doc/Makefile"
+ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile apps/dbctrl/Makefile apps/xmldb/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile doc/Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
@@ -4894,6 +4992,7 @@ do
"apps/cli/Makefile") CONFIG_FILES="$CONFIG_FILES apps/cli/Makefile" ;;
"apps/backend/Makefile") CONFIG_FILES="$CONFIG_FILES apps/backend/Makefile" ;;
"apps/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/netconf/Makefile" ;;
+ "apps/restconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/restconf/Makefile" ;;
"apps/dbctrl/Makefile") CONFIG_FILES="$CONFIG_FILES apps/dbctrl/Makefile" ;;
"apps/xmldb/Makefile") CONFIG_FILES="$CONFIG_FILES apps/xmldb/Makefile" ;;
"include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;;
diff --git a/configure.ac b/configure.ac
index 5acd6903..edb14de2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -132,8 +132,14 @@ AC_CHECK_LIB(socket, socket)
AC_CHECK_LIB(nsl, xdr_char)
AC_CHECK_LIB(dl, dlopen)
+# restconf uses libcurl (I think?)
+AC_CHECK_LIB(curl, curl_global_init,, AC_MSG_ERROR([libcurl missing]))
+
AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort strverscmp)
+# Lives in libfcgi-dev
+AC_CHECK_LIB(fcgi, FCGX_Init,, AC_MSG_ERROR([libfcgi-dev missing]))
+
# Check if extra keys inserted for database lists containing content. Eg A.n.foo = 3
# means A.3 $!a=foo exists
@@ -162,6 +168,7 @@ AC_OUTPUT(Makefile
apps/cli/Makefile
apps/backend/Makefile
apps/netconf/Makefile
+ apps/restconf/Makefile
apps/dbctrl/Makefile
apps/xmldb/Makefile
include/Makefile
diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in
index 0947033a..0c853a0b 100644
--- a/include/clixon_config.h.in
+++ b/include/clixon_config.h.in
@@ -40,6 +40,9 @@
/* Define to 1 if you have the `dl' library (-ldl). */
#undef HAVE_LIBDL
+/* Define to 1 if you have the `fcgi' library (-lfcgi). */
+#undef HAVE_LIBFCGI
+
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c
index 93c83bef..ad127d45 100644
--- a/lib/src/clixon_hash.c
+++ b/lib/src/clixon_hash.c
@@ -82,8 +82,7 @@
#define HASH_SIZE 1031 /* Number of hash buckets. Should be a prime */
-/*
- * A very simplistic algorithm to calculate a hash bucket index
+/*! A very simplistic algorithm to calculate a hash bucket index
*/
static uint32_t
hash_bucket(const char *str)
@@ -96,12 +95,9 @@ hash_bucket(const char *str)
return n % HASH_SIZE;
}
-/*
- * Initialize hash table.
+/*! Initialize hash table.
*
- * Arguments: none
- *
- * Returns: new pointer to hash table.
+ * @retval Pointer to new hash table.
*/
clicon_hash_t *
hash_init (void)
@@ -116,16 +112,13 @@ hash_init (void)
return hash;
}
-/*
- * Free hash table.
+/*! Free hash table.
*
- * Arguments:
- * hash - Hash table
- *
- * Returns: void
+ * @param[in] hash Hash table
+ * @retval void
*/
void
-hash_free (clicon_hash_t *hash)
+hash_free(clicon_hash_t *hash)
{
int i;
clicon_hash_t tmp;
@@ -142,15 +135,16 @@ hash_free (clicon_hash_t *hash)
}
-/*
- * Find keys.
+/*! Find hash key.
*
- * key - Variable name
- *
- * Returns: variable structure on success, NULL on failure
+ * @param[in] hash Hash table
+ * @param[in] key Variable name
+ * @retval variable Hash variable structure on success
+ * @retval NULL Error
*/
clicon_hash_t
-hash_lookup (clicon_hash_t *hash, const char *key)
+hash_lookup(clicon_hash_t *hash,
+ const char *key)
{
uint32_t bkt;
clicon_hash_t h;
@@ -164,15 +158,20 @@ hash_lookup (clicon_hash_t *hash, const char *key)
h = NEXTQ(clicon_hash_t, h);
} while (h != hash[bkt]);
}
-
return NULL;
}
-/*
- * Get value of hash
+/*! Get value of hash
+ * @param[in] hash Hash table
+ * @param[in] key Variable name
+ * @param[out] vlen Length of value (as returned by function)
+ * @retval value Hash value, length given in vlen
+ * @retval NULL Error
*/
void *
-hash_value(clicon_hash_t *hash, const char *key, size_t *vlen)
+hash_value(clicon_hash_t *hash,
+ const char *key,
+ size_t *vlen)
{
clicon_hash_t h;
@@ -185,19 +184,20 @@ hash_value(clicon_hash_t *hash, const char *key, size_t *vlen)
return h->h_val;
}
-
-/*
- * Copy value and add hash entry.
+/*! Copy value and add hash entry.
*
- * Arguments:
- * hash - Hash structure
- * key - New variable name
- * val - New variable value
- *
- * Returns: new variable on success, NULL on failure
+ * @param[in] hash Hash table
+ * @param[in] key New variable name
+ * @param[in] val New variable value
+ * @param[in] vlen Length of variable value
+ * @retval variable New hash structure on success
+ * @retval NULL Failure
*/
clicon_hash_t
-hash_add (clicon_hash_t *hash, const char *key, void *val, size_t vlen)
+hash_add(clicon_hash_t *hash,
+ const char *key,
+ void *val,
+ size_t vlen)
{
void *newval;
clicon_hash_t h, new = NULL;
@@ -250,17 +250,17 @@ catch:
return NULL;
}
-/*
- * Delete entry.
+/*! Delete hash entry.
*
- * Arguments:
- * hash - Hash structure
- * key - Variable name
+ * @param[in] hash Hash table
+ * @param[in] key Variable name
*
- * Returns: 0 on success, -1 on failure
+ * @retval 0 success
+ * @retval -1 failure
*/
int
-hash_del (clicon_hash_t *hash, const char *key)
+hash_del(clicon_hash_t *hash,
+ const char *key)
{
clicon_hash_t h;
@@ -277,8 +277,16 @@ hash_del (clicon_hash_t *hash, const char *key)
return 0;
}
+/*! Return vector of keys in has table
+ *
+ * @param[in] hash Hash table
+ * @param[out] nkeys Size of key vector
+ * @retval vector Vector of keys
+ * @retval NULL Error
+ */
char **
-hash_keys(clicon_hash_t *hash, size_t *nkeys)
+hash_keys(clicon_hash_t *hash,
+ size_t *nkeys)
{
int bkt;
clicon_hash_t h;
@@ -311,17 +319,15 @@ catch:
return NULL;
}
-/*
- * Dump contents of hash to FILE pointer.
+/*! Dump contents of hash to FILE pointer.
*
- * Arguments:
- * f - FILE pointer for print output
- * hash - Hash structure
- *
- * Returns: void
+ * @param[in] hash Hash structure
+ * @param[in] f FILE pointer for print output
+ * @retval void
*/
void
-hash_dump(clicon_hash_t *hash, FILE *f)
+hash_dump(clicon_hash_t *hash,
+ FILE *f)
{
int i;
char **keys;
diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c
index 442a8fd6..43307570 100644
--- a/lib/src/clixon_options.c
+++ b/lib/src/clixon_options.c
@@ -296,11 +296,12 @@ clicon_option_exists(clicon_handle h, const char *name)
* @retval NULL If option not found, or value of option is NULL
* @retval string value of option if found
* clicon options should be strings.
- * To differentiate the two reasons why NULL may be returned, use function
+ * @note To differentiate the two reasons why NULL may be returned, use function
* clicon_option_exists() before the call
*/
char *
-clicon_option_str(clicon_handle h, const char *name)
+clicon_option_str(clicon_handle h,
+ const char *name)
{
clicon_hash_t *copt = clicon_options(h);
@@ -310,9 +311,14 @@ clicon_option_str(clicon_handle h, const char *name)
}
/*! Set a single string option via handle
+ * @param[in] h clicon_handle
+ * @param[in] name option name
+ * @param[in] val option value, must be null-terminated string
*/
int
-clicon_option_str_set(clicon_handle h, const char *name, char *val)
+clicon_option_str_set(clicon_handle h,
+ const char *name,
+ char *val)
{
clicon_hash_t *copt = clicon_options(h);