diff --git a/apps/xmldb/xmldb_main.c b/apps/xmldb/xmldb_main.c new file mode 100644 index 00000000..93368d8f --- /dev/null +++ b/apps/xmldb/xmldb_main.c @@ -0,0 +1,930 @@ +/* + * + 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 + . + + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +/* Command line options to be passed to getopt(3) */ +#define XMLDB_OPTS "hDSf:a:p:y:m:r:" + +#define DEFAULT_PORT 7878 +#define DEFAULT_ADDR "127.0.0.1" + +static int +xmldb_send_error(int s, + char *reason) +{ + int retval = -1; + cbuf *cb = NULL; /* Outgoing return message */ + + clicon_log(LOG_NOTICE, "%s", reason); + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "%s", reason); + if (write(s, cbuf_get(cb), cbuf_len(cb)+1) < 0){ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + +/*! Process incoming xmldb get message + * @param[in] s Stream socket + * @param[in] cb Packet buffer + * @param[in] xr XML request node with root in "get" + * example + * + * + * + * / + * # If set send back list of xpath hits not single tree + * + * + * @note restrictions on using only databases called candidate and running + * + */ +static int +xmldb_from_get(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + cxobj *x; + cbuf *cb = NULL; /* Outgoing return message */ + char *db; + int vector = 0; + char *xpath = "/"; + cxobj *xt = NULL; /* Top of return tree */ + cxobj *xc; /* Child */ + cxobj **xvec = NULL; + size_t xlen = 0; + int i; + + if (xpath_first(xr, "source/candidate") != NULL) + db = "candidate"; + else if (xpath_first(xr, "source/running") != NULL) + db = "running"; + else { + xmldb_send_error(s, "Get request: Expected candidate or running as source"); + goto drop; + } + if ((x = xpath_first(xr, "xpath")) != NULL) + xpath = xml_body(x); + if (xpath_first(xr, "vector") != NULL) + vector++; + /* Actual get call */ + if (xmldb_get(h, db, xpath, vector, &xt, &xvec, &xlen) < 0) + goto done; + xml_name_set(xt, "config"); + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (vector){ + for (i=0; i + * + * + * merge|none|replace + * + * ... + * + * + * + * @note restrictions on using only databases called candidate and running + * @note key,val see xmldb_put_xkey + */ +static int +xmldb_from_put(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + cxobj *x; + char *db; + cxobj *xc; /* Child */ + char *opstr; + enum operation_type op = OP_REPLACE; + + if (xpath_first(xr, "target/candidate") != NULL) + db = "candidate"; + else if (xpath_first(xr, "target/running") != NULL) + db = "running"; + else { + xmldb_send_error(s, "Put request: Expected candidate or running as source"); + goto drop; + } + if ((x = xpath_first(xr, "default-operation")) != NULL) + if ((opstr = xml_body(x)) != NULL){ + if (strcmp(opstr, "replace") == 0) + op = OP_REPLACE; + else + if (strcmp(opstr, "merge") == 0) + op = OP_MERGE; + else + if (strcmp(opstr, "none") == 0) + op = OP_NONE; + else{ + xmldb_send_error(s, "Put request: unrecognized default-operation"); + goto drop; + } + } + if ((xc = xpath_first(xr, "config")) != NULL){ + /* Actual put call */ + if (xmldb_put(h, db, xc, op) < 0) + goto done; + } + if (write(s, "", strlen("")+1) < 0){ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + drop: + retval = 0; + done: + return retval; +} + +static int +xmldb_from_put_xkey(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + cxobj *x; + char *db; + char *xkey; + char *val; + char *opstr; + enum operation_type op = OP_REPLACE; + + if (xpath_first(xr, "target/candidate") != NULL) + db = "candidate"; + else if (xpath_first(xr, "target/running") != NULL) + db = "running"; + else { + xmldb_send_error(s, "Put request: Expected candidate or running as source"); + goto drop; + } + if ((x = xpath_first(xr, "default-operation")) != NULL) + if ((opstr = xml_body(x)) != NULL){ + if (strcmp(opstr, "replace") == 0) + op = OP_REPLACE; + else + if (strcmp(opstr, "merge") == 0) + op = OP_MERGE; + else + if (strcmp(opstr, "none") == 0) + op = OP_NONE; + else{ + xmldb_send_error(s, "Put xkey request: unrecognized default-operation"); + goto drop; + } + } + if ((x = xpath_first(xr, "xkey")) == NULL){ + xmldb_send_error(s, "Put xkey request: no xkey"); + goto drop; + } + xkey = xml_body(x); + if ((x = xpath_first(xr, "value")) == NULL){ + xmldb_send_error(s, "Put xkey request: no value"); + goto drop; + } + val = xml_body(x); + if (xmldb_put_xkey(h, db, xkey, val, op) < 0) + goto done; + if (write(s, "", strlen("")+1) < 0){ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + drop: + retval = 0; + done: + return retval; +} + +/*! Process incoming copy message + * @param[in] s Stream socket + * @param[in] cb Packet buffer + * @param[in] xr XML request node with root in "exists" + * example + * + * + * + * + * + * + * @note restrictions on using only databases called candidate and running + */ +static int +xmldb_from_copy(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + char *source; + char *target; + + if (xpath_first(xr, "source/candidate") != NULL) + source = "candidate"; + else if (xpath_first(xr, "source/running") != NULL) + source = "running"; + else { + xmldb_send_error(s, "Copy request: Expected candidate or running as source"); + goto drop; + } + if (xpath_first(xr, "target/candidate") != NULL) + target = "candidate"; + else if (xpath_first(xr, "target/running") != NULL) + target = "running"; + else { + xmldb_send_error(s, "Copy request: Expected candidate or running as target"); + goto drop; + } + if (xmldb_copy(h, source, target) < 0) + goto done; + if (write(s, "", strlen("")+1) < 0){ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + drop: + retval = 0; + done: + return retval; +} + +/*! Process incoming lock message + * @param[in] s Stream socket + * @param[in] cb Packet buffer + * @param[in] xr XML request node with root in "exists" + * example + * + * + * + * 43 + * + * + * @note restrictions on using only databases called candidate and running + */ +static int +xmldb_from_lock(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + char *target; + char *idstr = NULL; + cxobj *x; + + if (xpath_first(xr, "target/candidate") != NULL) + target = "candidate"; + else if (xpath_first(xr, "target/running") != NULL) + target = "running"; + else { + xmldb_send_error(s, "Lock request: Expected candidate or running as target"); + goto drop; + } + if ((x = xpath_first(xr, "id")) != NULL){ + xmldb_send_error(s, "Lock request: mandatory id not found"); + goto drop; + } + idstr = xml_body(x); + if (xmldb_lock(h, target, atoi(idstr)) < 0) + goto done; + drop: + retval = 0; + done: + return retval; +} + + +/*! Process incoming unlock message + * @param[in] s Stream socket + * @param[in] cb Packet buffer + * @param[in] xr XML request node with root in "exists" + * example + * + * + * + * 43 + * + * + * @note restrictions on using only databases called candidate and running + */ +static int +xmldb_from_unlock(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + char *target; + char *idstr = NULL; + cxobj *x; + + if (xpath_first(xr, "target/candidate") != NULL) + target = "candidate"; + else if (xpath_first(xr, "target/running") != NULL) + target = "running"; + else { + xmldb_send_error(s, "Unlock request: Expected candidate or running as target"); + goto drop; + } + if ((x = xpath_first(xr, "id")) != NULL){ + xmldb_send_error(s, "Unlock request: mandatory id not found"); + goto drop; + } + idstr = xml_body(x); + if (xmldb_unlock(h, target, atoi(idstr)) < 0) + goto done; + drop: + retval = 0; + done: + return retval; + +} + +/*! Process incoming islocked message + * @param[in] s Stream socket + * @param[in] cb Packet buffer + * @param[in] xr XML request node with root in "exists" + * example + * + * + * + * + * + * @note restrictions on using only databases called candidate and running + */ +static int +xmldb_from_islocked(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + char *db; + int ret; + cbuf *cb = NULL; + + if (xpath_first(xr, "target/candidate") != NULL) + db = "candidate"; + else if (xpath_first(xr, "target/running") != NULL) + db = "running"; + else { + xmldb_send_error(s, "Islocked request: Expected candidate or running as source"); + goto drop; + } + if ((ret = xmldb_islocked(h, db)) < 0) + goto done; + if (ret > 0){ + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "%u", ret); + if (write(s, cbuf_get(cb), cbuf_len(cb)+1) < 0){ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + } + else{ + if (write(s, "", strlen("")+1) < 0){ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + } + drop: + retval = 0; + done: + if (cb) + cbuf_free(cb); + return retval; +} + + +/*! Process incoming exists? message + * @param[in] s Stream socket + * @param[in] cb Packet buffer + * @param[in] xr XML request node with root in "exists" + * example + * + * + * + * + * + * @note restrictions on using only databases called candidate and running + */ +static int +xmldb_from_exists(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + char *db; + + if (xpath_first(xr, "target/candidate") != NULL) + db = "candidate"; + else if (xpath_first(xr, "target/running") != NULL) + db = "running"; + else { + xmldb_send_error(s, "Exists request: Expected candidate or running as source"); + goto drop; + } + /* XXX error and non-exist treated same */ + if (xmldb_exists(h, db) == 1){ + if (write(s, "", strlen("")+1) < 0){ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + } + else{ + xmldb_send_error(s, "DB does not exist"); + goto drop; + } + drop: + retval = 0; + done: + return retval; +} + +/*! Process incoming xmldb delete message + * @param[in] s Stream socket + * @param[in] cb Packet buffer + * @param[in] xr XML request node with root in "delete" + * example + * + * + * + * + * + * @note restrictions on using only databases called candidate and running + */ +static int +xmldb_from_delete(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + char *db; + + if (xpath_first(xr, "target/candidate") != NULL) + db = "candidate"; + else if (xpath_first(xr, "target/running") != NULL) + db = "running"; + else { + xmldb_send_error(s, "Delete request: Expected candidate or running as source"); + goto drop; + } + if (xmldb_delete(h, db) < 0) + ; /* ignore */ + if (write(s, "", strlen("")+1) < 0){ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + drop: + retval = 0; + done: + return retval; +} + + +/*! Process incoming xmldb init message + * @param[in] s Stream socket + * @param[in] cb Packet buffer + * @param[in] xr XML request node with root in "init" + * example + * + * + * + * + * + * @note restrictions on using only databases called candidate and running + */ +static int +xmldb_from_init(clicon_handle h, + int s, + cxobj *xr) +{ + int retval = -1; + char *db; + + if (xpath_first(xr, "target/candidate") != NULL) + db = "candidate"; + else if (xpath_first(xr, "target/running") != NULL) + db = "running"; + else { + xmldb_send_error(s, "Init request: Expected candidate or running as source"); + goto drop; + } + if (xmldb_init(h, db) < 0) + goto done; + if (write(s, "", strlen("")+1) < 0){ + clicon_err(OE_UNIX, errno, "write"); + goto done; + } + drop: + retval = 0; + done: + return retval; +} + +/*! Process incoming xmldb packet + * @param[in] h Clicon handle + * @param[in] s Stream socket + * @param[in] cbin Incoming packet buffer + * example: ]]>]]> + */ +static int +xmldb_from_client(clicon_handle h, + int s, + cbuf *cbin) +{ + int retval = -1; + char *str; + cxobj *xrq = NULL; /* Request (in) */ + cxobj *xr; + cxobj *x; + cxobj *xt = NULL; + + clicon_debug(1, "xmldb message: \"%s\"", cbuf_get(cbin)); + str = cbuf_get(cbin); + str[strlen(str)-strlen("]]>]]>")] = '\0'; + /* Parse incoming XML message */ + if (clicon_xml_parse_string(&str, &xrq) < 0) + goto done; + if (debug) + clicon_xml2file(stderr, xrq, 0, 1); + if ((xr = xpath_first(xrq, "rpc")) != NULL){ + if ((x = xpath_first(xr, "get")) != NULL){ + if (xmldb_from_get(h, s, x) < 0) + goto done; + } + else if ((x = xpath_first(xr, "put")) != NULL){ + if (xmldb_from_put(h, s, x) < 0) + goto done; + } + else if ((x = xpath_first(xr, "put-xkey")) != NULL){ + if (xmldb_from_put_xkey(h, s, x) < 0) + goto done; + } + else if ((x = xpath_first(xr, "copy")) != NULL){ + if (xmldb_from_copy(h, s, x) < 0) + goto done; + } + else if ((x = xpath_first(xr, "lock")) != NULL){ + if (xmldb_from_lock(h, s, x) < 0) + goto done; + } + else if ((x = xpath_first(xr, "unlock")) != NULL){ + if (xmldb_from_unlock(h, s, x) < 0) + goto done; + } + else if ((x = xpath_first(xr, "islocked")) != NULL){ + if (xmldb_from_islocked(h, s, x) < 0) + goto done; + } + else if ((x = xpath_first(xr, "exists")) != NULL){ + if (xmldb_from_exists(h, s, x) < 0) + goto done; + } + else if ((x = xpath_first(xr, "init")) != NULL){ + if (xmldb_from_init(h, s, x) < 0) + goto done; + } + else if ((x = xpath_first(xr, "delete")) != NULL){ + if (xmldb_from_delete(h, s, x) < 0) + goto done; + } + } + else{ + xmldb_send_error(s, "Expected rpc as top xml msg"); + goto drop; + } + drop: + retval = 0; + done: + if (xrq) + xml_free(xrq); + if (xt) + xml_free(xt); + return retval; +} + +/*! stolen from netconf_lib.c */ +static int +detect_endtag(char *tag, char ch, int *state) +{ + int retval = 0; + + if (tag[*state] == ch){ + (*state)++; + if (*state == strlen(tag)){ + *state = 0; + retval = 1; + } + } + else + *state = 0; + return retval; +} + +/*! config_accept_client + */ +int +config_accept_client(int fd, + void *arg) +{ + int retval = -1; + clicon_handle h = (clicon_handle)arg; + int s = -1; + struct sockaddr_un from; + socklen_t slen; + ssize_t len; + unsigned char buf[BUFSIZ]; + int i; + cbuf *cb = NULL; + int xml_state = 0; + + clicon_debug(1, "Accepting client request"); + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + return retval; + } + len = sizeof(from); + if ((s = accept(fd, (struct sockaddr*)&from, &slen)) < 0){ + clicon_err(OE_UNIX, errno, "%s: accept", __FUNCTION__); + goto done; + } + if ((len = read(s, buf, sizeof(buf))) < 0){ + clicon_err(OE_UNIX, errno, "read"); + goto done; + } + for (i=0; i]]>", + buf[i], + &xml_state)) { + if (xmldb_from_client(h, s, cb) < 0){ + goto done; + } + cbuf_reset(cb); + } + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (s != -1) + close(s); + return retval; +} + +/*! Create tcp server socket and register callback + */ +static int +server_socket(clicon_handle h, + char *ipv4addr, + uint16_t port) +{ + int retval = -1; + int s; + struct sockaddr_in addr; + + /* Open control socket */ + if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + clicon_err(OE_UNIX, errno, "socket"); + goto done; + } + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (inet_pton(addr.sin_family, ipv4addr, &addr.sin_addr) != 1){ + clicon_err(OE_UNIX, errno, "inet_pton: %s (Expected IPv4 address. Check settings of CLICON_SOCK_FAMILY and CLICON_SOCK)", ipv4addr); + goto done; + } + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0){ + clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__); + goto done; + } + clicon_debug(1, "Listen on server socket at %s:%hu", ipv4addr, port); + if (listen(s, 5) < 0){ + clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__); + goto done; + } + if (event_reg_fd(s, config_accept_client, h, "server socket") < 0) { + close(s); + goto done; + } + retval = 0; + done: + return retval; +} + +/* + * usage + */ +static void +usage(char *argv0) +{ + fprintf(stderr, "usage:%s\n" + "where options are\n" + "\t-h\t\tHelp\n" + "\t-D\t\tDebug\n" + "\t-S\t\tLog on syslog\n" + "\t-f \tCLICON config file\n" + "\t-a \tIP address\n" + "\t-p \tTCP port\n" + "\t-y \tYang dir\n" + "\t-m \tYang main module name\n" + "\t-r \tYang module revision\n", + argv0 + ); + exit(0); +} + +int +main(int argc, char **argv) +{ + char c; + int use_syslog; + clicon_handle h; + uint16_t port; + char *addr = DEFAULT_ADDR; + + /* In the startup, logs to stderr & debug flag set later */ + clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR); + + /* Defaults */ + use_syslog = 0; + port = DEFAULT_PORT; + + if ((h = clicon_handle_init()) == NULL) + goto done; + /* getopt in two steps, first find config-file before over-riding options. */ + while ((c = getopt(argc, argv, XMLDB_OPTS)) != -1) + switch (c) { + case '?' : + case 'h' : /* help */ + usage(argv[0]); + break; + case 'D' : /* debug */ + debug = 1; + break; + case 'f': /* config file */ + if (!strlen(optarg)) + usage(argv[0]); + clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); + break; + case 'S': /* Log on syslog */ + use_syslog = 1; + break; + } + /* + * Logs, error and debug to stderr or syslog, set debug level + */ + clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, + use_syslog?CLICON_LOG_SYSLOG:CLICON_LOG_STDERR); + clicon_debug_init(debug, NULL); + + /* Find and read configfile */ + if (clicon_options_main(h) < 0){ + usage(argv[0]); + goto done; + } + + /* Now rest of options */ + optind = 1; + while ((c = getopt(argc, argv, XMLDB_OPTS)) != -1) + switch (c) { + case 'D': /* Processed earlier, ignore now. */ + case 'S': + case 'f': + break; + case 'a': /* address */ + clicon_option_str_set(h, "CLICON_XMLDB_ADDR", optarg); + break; + case 'p': /* port */ + clicon_option_str_set(h, "CLICON_XMLDB_PORT", optarg); + break; + case 'y': /* yang dir */ + clicon_option_str_set(h, "CLICON_YANG_DIR", optarg); + break; + case 'm': /* yang module */ + clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg); + break; + case 'r': /* yang revision */ + clicon_option_str_set(h, "CLICON_YANG_MODULE_REVISION", optarg); + break; + default: + usage(argv[0]); + break; + } + argc -= optind; + argv += optind; + clicon_option_str_set(h, "CLICON_XMLDB_RPC", "0"); + if (clicon_yang_dir(h) == NULL){ + clicon_err(OE_UNIX, errno, "yang dir not set"); + goto done; + } + if (clicon_yang_module_main(h) == NULL){ + clicon_err(OE_UNIX, errno, "yang main module not set"); + goto done; + } + if (yang_spec_main(h, NULL, 0) < 0) + goto done; + addr = clicon_xmldb_addr(h); + port = clicon_xmldb_port(h); + if (server_socket(h, addr, port) < 0) + goto done; + + if (event_loop() < 0) + goto done; + + done: + return 0; +}