/* * 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 [draft] * sudo apt-get install libfcgi-dev * gcc -o fastcgi fastcgi.c -lfcgi * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.conf " -s /bin/sh www-data * This is the interface: * api/data/profile=/metric= PUT data:enable= * 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/" /*! Generic REST GET method * According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] h Clixon handle * @param[in] r Fastcgi request handle * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode * XXX: cant find a way to use Accept request field to choose Content-Type * I would like to support both xml and json. * Request may contain * Accept: application/yang.data+json,application/yang.data+xml * Response contains one of: * Content-Type: application/yang.data+xml * Content-Type: application/yang.data+json * NOTE: If a retrieval request for a data resource representing a YANG leaf- * list or list object identifies more than one instance, and XML * encoding is used in the response, then an error response containing a * "400 Bad Request" status-line MUST be returned by the server. */ static int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, cvec *qvec) { int retval = -1; cg_var *cv; char *val; char *v; int i; cbuf *path = NULL; cbuf *path1 = NULL; cxobj *xt = NULL; cbuf *cbx = NULL; cxobj **vec = NULL; size_t veclen; yang_spec *yspec; yang_stmt *y; yang_stmt *ykey; char *name; cvec *cvk = NULL; /* vector of index keys */ cg_var *cvi; yspec = clicon_dbspec_yang(h); 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; iys_argument); goto done; } clicon_debug(1, "ykey:%s", ykey->ys_argument); /* The value is a list of keys: [ ]* */ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) goto done; cvi = NULL; /* Iterate over individual yang keys */ cprintf(path, "/%s", name); v = val; while ((cvi = cvec_each(cvk, cvi)) != NULL){ cprintf(path, "[%s=%s]", cv_string_get(cvi), v); v += strlen(v)+1; } if (val) free(val); } else{ cprintf(path, "%s%s", (i==pi?"":"/"), name); cprintf(path1, "/%s", name); } } clicon_debug(1, "path:%s", cbuf_get(path)); clicon_debug(1, "path1:%s", cbuf_get(path1)); if (xmldb_get(h, "running", cbuf_get(path), 0, &xt, &vec, &veclen) < 0) goto done; FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang.data+xml\r\n"); FCGX_FPrintF(r->out, "\r\n"); #if 0 /* Iterate over result */ if (xpath_vec(xt, cbuf_get(path1), &vec, &veclen) < 0) goto done; #endif if ((cbx = cbuf_new()) == NULL) goto done; cprintf(cbx, "{\n"); if (xml2json_cbuf(cbx, xt, 1) < 0) goto done; cprintf(cbx, "}"); FCGX_FPrintF(r->out, "%s\r\n", cbuf_get(cbx)); retval = 0; done: if (vec) free(vec); if (cbx) cbuf_free(cbx); if (xt) xml_free(xt); if (path) cbuf_free(path); if (path1) cbuf_free(path1); return retval; } /*! Generic REST DELETE method * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] pi Offset, where path starts * Example: * curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0 */ static int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi) { int retval = -1; int i; clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); for (i=0; iout); FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); FCGX_FPrintF(r->out, "\r\n"); retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; } /*! Generic REST PUT method * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] dvec Stream input data * Example: curl -X PUT -d {\"enabled\":\"false\"} http://127.0.0.1/restconf/data/interfaces/interface=eth1 * Problem: we have URI that defines a path (eg "interface/name=eth1") and data * which defines a tree from that point. * But, xmldb api can only do either * - xmldb_put() with a complete xml-tree, or * - xmldb_put_xkey for path and key value * What we need is path and sub-xml tree. * Alt1: parse URI to XML and and call xmldb_put() * Alt2: Extend xmldb API with path + xml-tree. */ static int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, cvec *qvec, char *data) { int retval = -1; int i; cxobj *xdata = NULL; cbuf *cbx = NULL; cxobj *x; clicon_debug(1, "%s api_path:%s json:%s", __FUNCTION__, api_path, data); for (i=0; iout); FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); FCGX_FPrintF(r->out, "\r\n"); done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xdata) xml_free(xdata); if (cbx) cbuf_free(cbx); return retval; } /*! Generic REST method, GET, PUT, DELETE * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] dvec Stream input data */ static int api_data(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, cvec *qvec, char *data) { 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(h, r, pcvec, pi, qvec); else if (strcmp(request_method, "PUT")==0) retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data); else if (strcmp(request_method, "DELETE")==0) retval = api_data_delete(h, r, api_path, pi); else retval = notfound(r); return retval; } /*! Process a FastCGI request * @param[in] r Fastcgi request handle */ static int request_process(clicon_handle h, 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, "data") == 0) /* restconf, skip /api/data */ retval = api_data(h, r, path, pcvec, 2, qvec, data); 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__, 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; /* Parse yang database spec file */ if (yang_spec_main(h, NULL, 0) < 0) goto done; 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 (FCGX_Init() != 0){ clicon_err(OE_CFG, errno, "FCGX_Init"); goto done; } if ((sock = FCGX_OpenSocket(sockpath, 10)) < 0){ clicon_err(OE_CFG, errno, "FCGX_OpenSocket"); 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(h, 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; }