/* * 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; }