From 0356b2225f8c80a6b23104991b389085f466f7b0 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 20 May 2016 18:33:48 +0200 Subject: [PATCH] experimental restconf --- apps/Makefile.in | 2 +- apps/netconf/netconf_main.c | 17 +- apps/restconf/Makefile.in | 96 +++ apps/restconf/README | 32 + apps/restconf/restconf_lib.c | 1133 +++++++++++++++++++++++++++ apps/restconf/restconf_lib.h | 50 ++ apps/restconf/restconf_main.c | 1386 +++++++++++++++++++++++++++++++++ clixon.conf.cpp.cpp | 3 + configure | 101 ++- configure.ac | 7 + include/clixon_config.h.in | 3 + lib/src/clixon_hash.c | 106 +-- lib/src/clixon_options.c | 12 +- 13 files changed, 2886 insertions(+), 62 deletions(-) create mode 100644 apps/restconf/Makefile.in create mode 100644 apps/restconf/README create mode 100644 apps/restconf/restconf_lib.c create mode 100644 apps/restconf/restconf_lib.h create mode 100644 apps/restconf/restconf_main.c 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);