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