1 /*
2  *
3   ***** BEGIN LICENSE BLOCK *****
4 
5   Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
6   Copyright (C) 2017-2019 Olof Hagsand
7   Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
8 
9   This file is part of CLIXON.
10 
11   Licensed under the Apache License, Version 2.0 (the "License");
12   you may not use this file except in compliance with the License.
13   You may obtain a copy of the License at
14 
15     http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22 
23   Alternatively, the contents of this file may be used under the terms of
24   the GNU General Public License Version 3 or later (the "GPL"),
25   in which case the provisions of the GPL are applicable instead
26   of those above. If you wish to allow use of your version of this file only
27   under the terms of the GPL, and not to allow others to
28   use your version of this file under the terms of Apache License version 2,
29   indicate your decision by deleting the provisions above and replace them with
30   the  notice and other provisions required by the GPL. If you do not delete
31   the provisions above, a recipient may use your version of this file under
32   the terms of any one of the Apache License version 2 or the GPL.
33 
34   ***** END LICENSE BLOCK *****
35 
36  *
37  * Client-side functions for clicon_proto protocol
38  * Historically this code was part of the clicon_cli application. But
39  * it should (is?) be general enough to be used by other applications.
40  */
41 
42 #ifdef HAVE_CONFIG_H
43 #include "clixon_config.h" /* generated by config & autoconf */
44 #endif
45 
46 #include <stdlib.h>
47 #include <stdio.h>
48 #include <string.h>
49 #include <errno.h>
50 #include <assert.h>
51 #include <unistd.h>
52 #include <sys/param.h>
53 #include <sys/stat.h>
54 #include <sys/socket.h>
55 #include <sys/syslog.h>
56 
57 /* cligen */
58 #include <cligen/cligen.h>
59 
60 /* clicon */
61 #include "clixon_queue.h"
62 #include "clixon_hash.h"
63 #include "clixon_handle.h"
64 #include "clixon_log.h"
65 #include "clixon_yang.h"
66 #include "clixon_xml.h"
67 #include "clixon_options.h"
68 #include "clixon_data.h"
69 #include "clixon_yang_module.h"
70 #include "clixon_plugin.h"
71 #include "clixon_string.h"
72 #include "clixon_xpath_ctx.h"
73 #include "clixon_xpath.h"
74 #include "clixon_proto.h"
75 #include "clixon_err.h"
76 #include "clixon_stream.h"
77 #include "clixon_err_string.h"
78 #include "clixon_xml_nsctx.h"
79 #include "clixon_xml_bind.h"
80 #include "clixon_xml_sort.h"
81 #include "clixon_xml_io.h"
82 #include "clixon_netconf_lib.h"
83 #include "clixon_proto_client.h"
84 
85 /*! Send internal netconf rpc from client to backend
86  * @param[in]    h      CLICON handle
87  * @param[in]    msg    Encoded message. Deallocate with free
88  * @param[out]   xret0  Return value from backend as xml tree. Free w xml_free
89  * @param[inout] sock0  If pointer exists, do not close socket to backend on success
90  *                      and return it here. For keeping a notify socket open
91  * @note sock0 is if connection should be persistent, like a notification/subscribe api
92  * @note xret is populated with yangspec according to standard handle yangspec
93  */
94 int
clicon_rpc_msg(clicon_handle h,struct clicon_msg * msg,cxobj ** xret0,int * sock0)95 clicon_rpc_msg(clicon_handle      h,
96 	       struct clicon_msg *msg,
97 	       cxobj            **xret0,
98 	       int               *sock0)
99 {
100     int                retval = -1;
101     char              *sock;
102     int                port;
103     char              *retdata = NULL;
104     cxobj             *xret = NULL;
105 
106 #ifdef RPC_USERNAME_ASSERT
107     assert(strstr(msg->op_body, "username")!=NULL); /* XXX */
108 #endif
109     clicon_debug(1, "%s request:%s", __FUNCTION__, msg->op_body);
110     if ((sock = clicon_sock(h)) == NULL){
111 	clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set");
112 	goto done;
113     }
114     /* What to do if inet socket? */
115     switch (clicon_sock_family(h)){
116     case AF_UNIX:
117 	if (clicon_rpc_connect_unix(h, msg, sock, &retdata, sock0) < 0){
118 #if 0
119 	    if (errno == ESHUTDOWN)
120 		/* Maybe could reconnect on a higher layer, but lets fail
121 		   loud and proud */
122 		cligen_exiting_set(cli_cligen(h), 1);
123 #endif
124 	    goto done;
125 	}
126 	break;
127     case AF_INET:
128 	if ((port = clicon_sock_port(h)) < 0){
129 	    clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set");
130 	    goto done;
131 	}
132 	if (port < 0){
133 	    clicon_err(OE_FATAL, 0, "CLICON_SOCK_PORT not set");
134 	    goto done;
135 	}
136 	if (clicon_rpc_connect_inet(h, msg, sock, port, &retdata, sock0) < 0)
137 	    goto done;
138 	break;
139     }
140     clicon_debug(1, "%s retdata:%s", __FUNCTION__, retdata);
141 
142     if (retdata){
143 	/* Cannot populate xret here because need to know RPC name (eg "lock") in order to associate yang
144 	 * to reply.
145 	 */
146 	if (clixon_xml_parse_string(retdata, YB_NONE, NULL, &xret, NULL) < 0)
147 	    goto done;
148     }
149     if (xret0){
150 	*xret0 = xret;
151 	xret = NULL;
152     }
153     retval = 0;
154  done:
155     if (retdata)
156 	free(retdata);
157     if (xret)
158 	xml_free(xret);
159     return retval;
160 }
161 
162 /*! Check if there is a valid (cached) session-id. If not, send a hello request to backend
163  * Session-ids survive TCP sessions that are created for each message sent to the backend.
164  * Clients use two approaches, either:
165  * (1) Once at the beginning of the session. Netconf and restconf does this
166  * (2) First usage, ie "lazy" evaluation when first needed
167  * @param[in]  h           clicon handle
168  * @param[out] session_id  Session id
169  * @retval     0           OK and session_id set
170  * @retval    -1           Error
171  */
172 static int
session_id_check(clicon_handle h,uint32_t * session_id)173 session_id_check(clicon_handle h,
174 		 uint32_t     *session_id)
175 {
176     int      retval = -1;
177     uint32_t id;
178 
179     if (clicon_session_id_get(h, &id) < 0){ /* Not set yet */
180 	if (clicon_hello_req(h, &id) < 0)
181 	    goto done;
182 	clicon_session_id_set(h, id);
183     }
184     retval = 0;
185     *session_id = id;
186  done:
187     return retval;
188 }
189 
190 /*! Generic xml netconf clicon rpc
191  * Want to go over to use netconf directly between client and server,...
192  * @param[in]  h       clicon handle
193  * @param[in]  xmlstr  XML netconf tree as string
194  * @param[out] xret    Return XML netconf tree, error or OK (need to be freed)
195  * @param[out] sp      Socket pointer for notification, otherwise NULL
196  * @code
197  *   cxobj *xret = NULL;
198  *   if (clicon_rpc_netconf(h, "<rpc></rpc>", &xret, NULL) < 0)
199  *	err;
200  *   xml_free(xret);
201  * @endcode
202  * @see clicon_rpc_netconf_xml xml as tree instead of string
203  */
204 int
clicon_rpc_netconf(clicon_handle h,char * xmlstr,cxobj ** xret,int * sp)205 clicon_rpc_netconf(clicon_handle  h,
206 		   char          *xmlstr,
207 		   cxobj        **xret,
208 		   int           *sp)
209 {
210     int                retval = -1;
211     uint32_t           session_id;
212     struct clicon_msg *msg = NULL;
213 
214     if (session_id_check(h, &session_id) < 0)
215 	goto done;
216     if ((msg = clicon_msg_encode(session_id, "%s", xmlstr)) < 0)
217 	goto done;
218     if (clicon_rpc_msg(h, msg, xret, sp) < 0)
219 	goto done;
220     retval = 0;
221  done:
222     if (msg)
223 	free(msg);
224     return retval;
225 }
226 
227 /*! Generic xml netconf clicon rpc
228  * Want to go over to use netconf directly between client and server,...
229  * @param[in]  h       clicon handle
230  * @param[in]  xml     XML netconf tree
231  * @param[out] xret    Return XML netconf tree, error or OK
232  * @param[out] sp      Socket pointer for notification, otherwise NULL
233  * @code
234  *   cxobj *xret = NULL;
235  *   int    s;
236  *   if (clicon_rpc_netconf_xml(h, x, &xret, &s) < 0)
237  *	err;
238  *   xml_free(xret);
239  * @endcode
240 
241  * @see clicon_rpc_netconf xml as string instead of tree
242  */
243 int
clicon_rpc_netconf_xml(clicon_handle h,cxobj * xml,cxobj ** xret,int * sp)244 clicon_rpc_netconf_xml(clicon_handle  h,
245 		       cxobj         *xml,
246 		       cxobj        **xret,
247 		       int           *sp)
248 {
249     int        retval = -1;
250     cbuf      *cb = NULL;
251     cxobj     *xname;
252     char      *rpcname;
253     cxobj     *xreply;
254     yang_stmt *yspec;
255 
256     if ((cb = cbuf_new()) == NULL){
257 	clicon_err(OE_XML, errno, "cbuf_new");
258 	goto done;
259     }
260     if ((xname = xml_child_i_type(xml, 0, 0)) == NULL){
261 	clicon_err(OE_NETCONF, EINVAL, "Missing rpc name");
262 	goto done;
263     }
264     rpcname = xml_name(xname); /* Store rpc name and use in yang binding after reply */
265     if (clicon_xml2cbuf(cb, xml, 0, 0, -1) < 0)
266 	goto done;
267     if (clicon_rpc_netconf(h, cbuf_get(cb), xret, sp) < 0)
268 	goto done;
269     if ((xreply = xml_find_type(*xret, NULL, "rpc-reply", CX_ELMNT)) != NULL &&
270 	xml_find_type(xreply, NULL, "rpc-error", CX_ELMNT) == NULL){
271 	yspec = clicon_dbspec_yang(h);
272 	/* Here use rpc name to bind to yang */
273 	if (xml_bind_yang_rpc_reply(xreply, rpcname, yspec, NULL) < 0)
274 	    goto done;
275     }
276     retval = 0;
277  done:
278     if (cb)
279 	cbuf_free(cb);
280     return retval;
281 }
282 
283 
284 /*! Get database configuration
285  * Same as clicon_proto_change just with a cvec instead of lvec
286  * @param[in]  h        CLICON handle
287  * @param[in]  username If NULL, use default
288  * @param[in]  db       Name of database
289  * @param[in]  xpath    XPath (or "")
290  * @param[in]  nsc       Namespace context for filter
291  * @param[out] xt       XML tree. Free with xml_free.
292  *                      Either <config> or <rpc-error>.
293  * @retval    0         OK
294  * @retval   -1         Error, fatal or xml
295  * @code
296  *   cxobj *xt = NULL;
297  *   cvec *nsc = NULL;
298  *
299  *   if ((nsc = xml_nsctx_init(NULL, "urn:example:hello")) == NULL)
300  *       err;
301  *   if (clicon_rpc_get_config(h, NULL, "running", "/hello/world", nsc, &xt) < 0)
302  *       err;
303  *   if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
304  *	clixon_netconf_error(xerr, "msg", "/hello/world");
305  *      err;
306  *   }
307  *   if (xt)
308  *      xml_free(xt);
309  *  if (nsc)
310  *     xml_nsctx_free(nsc);
311  * @endcode
312  * @see clicon_rpc_get
313  * @see clixon_netconf_error
314  * @note the netconf return message is yang populated, as well as the return data
315  */
316 int
clicon_rpc_get_config(clicon_handle h,char * username,char * db,char * xpath,cvec * nsc,cxobj ** xt)317 clicon_rpc_get_config(clicon_handle h,
318 		      char         *username,
319 		      char         *db,
320 		      char         *xpath,
321 		      cvec         *nsc,
322 		      cxobj       **xt)
323 {
324     int                retval = -1;
325     struct clicon_msg *msg = NULL;
326     cbuf              *cb = NULL;
327     cxobj             *xret = NULL;
328     cxobj             *xerr = NULL;
329     cxobj             *xd;
330     cg_var            *cv = NULL;
331     char              *prefix;
332     uint32_t           session_id;
333     int                ret;
334     yang_stmt         *yspec;
335 
336     if (session_id_check(h, &session_id) < 0)
337 	goto done;
338     if ((cb = cbuf_new()) == NULL)
339 	goto done;
340     cprintf(cb, "<rpc xmlns=\"%s\"", NETCONF_BASE_NAMESPACE);
341     if (username == NULL)
342 	username = clicon_username_get(h);
343     if (username != NULL)
344 	cprintf(cb, " username=\"%s\"", username);
345     cprintf(cb, " xmlns:%s=\"%s\"",
346 	    NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE);
347     cprintf(cb, "><get-config><source><%s/></source>", db);
348     if (xpath && strlen(xpath)){
349 	cprintf(cb, "<%s:filter %s:type=\"xpath\" %s:select=\"%s\"",
350 		NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX,
351 		xpath);
352 	while ((cv = cvec_each(nsc, cv)) != NULL){
353 	    cprintf(cb, " xmlns");
354 	    if ((prefix = cv_name_get(cv)))
355 		cprintf(cb, ":%s", prefix);
356 	    cprintf(cb, "=\"%s\"", cv_string_get(cv));
357 	}
358 	cprintf(cb, "/>");
359     }
360     cprintf(cb, "</get-config></rpc>");
361     if ((msg = clicon_msg_encode(session_id, "%s", cbuf_get(cb))) == NULL)
362 	goto done;
363     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
364 	goto done;
365     /* Send xml error back: first check error, then ok */
366     if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL)
367 	xd = xml_parent(xd); /* point to rpc-reply */
368     else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL){
369 	if ((xd = xml_new("data", NULL, CX_ELMNT)) == NULL)
370 	    goto done;
371     }
372     else{
373 	yspec = clicon_dbspec_yang(h);
374 	if ((ret = xml_bind_yang(xd, YB_MODULE, yspec, &xerr)) < 0)
375 	    goto done;
376 	if (ret == 0){
377 	    if (clixon_netconf_internal_error(xerr,
378 					      ". Internal error, backend returned invalid XML.",
379 					      NULL) < 0)
380 		goto done;
381 	    if ((xd = xpath_first(xerr, NULL, "rpc-error")) == NULL){
382 		clicon_err(OE_XML, ENOENT, "Expected rpc-error tag but none found(internal)");
383 		goto done;
384 	    }
385 	}
386     }
387     if (xt){
388 	if (xml_rm(xd) < 0)
389 	    goto done;
390 	*xt = xd;
391     }
392     retval = 0;
393   done:
394     if (cb)
395 	cbuf_free(cb);
396     if (xerr)
397 	xml_free(xerr);
398     if (xret)
399 	xml_free(xret);
400     if (msg)
401 	free(msg);
402     return retval;
403 }
404 
405 /*! Send database entries as XML to backend daemon
406  * @param[in] h          CLICON handle
407  * @param[in] db         Name of database
408  * @param[in] op         Operation on database item: OP_MERGE, OP_REPLACE
409  * @param[in] xml        XML string. Ex: <config><a>..</a><b>...</b></config>
410  * @retval    0          OK
411  * @retval   -1          Error and logged to syslog
412  * @note xml arg need to have <config> as top element
413  * @code
414  * if (clicon_rpc_edit_config(h, "running", OP_MERGE,
415  *                            "<config><a>4</a></config>") < 0)
416  *    err;
417  * @endcode
418  */
419 int
clicon_rpc_edit_config(clicon_handle h,char * db,enum operation_type op,char * xmlstr)420 clicon_rpc_edit_config(clicon_handle       h,
421 		       char               *db,
422 		       enum operation_type op,
423 		       char               *xmlstr)
424 {
425     int                retval = -1;
426     struct clicon_msg *msg = NULL;
427     cbuf              *cb = NULL;
428     cxobj             *xret = NULL;
429     cxobj             *xerr;
430     char              *username;
431     uint32_t           session_id;
432 
433     if (session_id_check(h, &session_id) < 0)
434 	goto done;
435     if ((cb = cbuf_new()) == NULL)
436 	goto done;
437     cprintf(cb, "<rpc xmlns=\"%s\"", NETCONF_BASE_NAMESPACE);
438     cprintf(cb, " xmlns:%s=\"%s\"", NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE);
439     if ((username = clicon_username_get(h)) != NULL)
440 	cprintf(cb, " username=\"%s\"", username);
441     cprintf(cb, "><edit-config><target><%s/></target>", db);
442     cprintf(cb, "<default-operation>%s</default-operation>",
443 	    xml_operation2str(op));
444     if (xmlstr)
445 	cprintf(cb, "%s", xmlstr);
446     cprintf(cb, "</edit-config></rpc>");
447     if ((msg = clicon_msg_encode(session_id, "%s", cbuf_get(cb))) == NULL)
448 	goto done;
449     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
450 	goto done;
451     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
452 	clixon_netconf_error(xerr, "Editing configuration", NULL);
453 	goto done;
454     }
455     retval = 0;
456   done:
457     if (xret)
458 	xml_free(xret);
459     if (cb)
460 	cbuf_free(cb);
461     if (msg)
462 	free(msg);
463     return retval;
464 }
465 
466 /*! Send a request to backend to copy a file from one location to another
467  * Note this assumes the backend can access these files and (usually) assumes
468  * clients and servers have the access to the same filesystem.
469  * @param[in] h        CLICON handle
470  * @param[in] db1      src database, eg "running"
471  * @param[in] db2      dst database, eg "startup"
472  * @retval    0        OK
473  * @retval   -1        Error and logged to syslog
474  * @code
475  * if (clicon_rpc_copy_config(h, "running", "startup") < 0)
476  *    err;
477  * @endcode
478  */
479 int
clicon_rpc_copy_config(clicon_handle h,char * db1,char * db2)480 clicon_rpc_copy_config(clicon_handle h,
481 		       char         *db1,
482 		       char         *db2)
483 {
484     int                retval = -1;
485     struct clicon_msg *msg = NULL;
486     cxobj             *xret = NULL;
487     cxobj             *xerr;
488     char              *username;
489     uint32_t           session_id;
490 
491     if (session_id_check(h, &session_id) < 0)
492 	goto done;
493     username = clicon_username_get(h);
494     if ((msg = clicon_msg_encode(session_id,
495 				 "<rpc xmlns=\"%s\" username=\"%s\">"
496 				 "<copy-config><source><%s/></source><target><%s/></target></copy-config></rpc>",
497 				 NETCONF_BASE_NAMESPACE,
498 				 username?username:"",
499 				 db1, db2)) == NULL)
500 	goto done;
501     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
502 	goto done;
503     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
504 	clixon_netconf_error(xerr, "Copying configuration", NULL);
505 	goto done;
506     }
507     retval = 0;
508  done:
509     if (xret)
510 	xml_free(xret);
511     if (msg)
512 	free(msg);
513     return retval;
514 }
515 
516 /*! Send a request to backend to delete a config database
517  * @param[in] h        CLICON handle
518  * @param[in] db       database, eg "running"
519  * @retval    0        OK
520  * @retval   -1        Error and logged to syslog
521  * @code
522  * if (clicon_rpc_delete_config(h, "startup") < 0)
523  *    err;
524  * @endcode
525  */
526 int
clicon_rpc_delete_config(clicon_handle h,char * db)527 clicon_rpc_delete_config(clicon_handle h,
528 			 char         *db)
529 {
530     int                retval = -1;
531     struct clicon_msg *msg = NULL;
532     cxobj             *xret = NULL;
533     cxobj             *xerr;
534     char              *username;
535     uint32_t           session_id;
536 
537     if (session_id_check(h, &session_id) < 0)
538 	goto done;
539     username = clicon_username_get(h);
540     if ((msg = clicon_msg_encode(session_id,
541 				 "<rpc xmlns=\"%s\" username=\"%s\">"
542 				 "<edit-config><target><%s/></target><default-operation>none</default-operation><config operation=\"delete\"/></edit-config></rpc>",
543 				 NETCONF_BASE_NAMESPACE,
544 				 username?username:"", db)) == NULL)
545 	goto done;
546     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
547 	goto done;
548     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
549 	clixon_netconf_error(xerr, "Deleting configuration", NULL);
550 	goto done;
551     }
552     retval = 0;
553  done:
554     if (xret)
555 	xml_free(xret);
556     if (msg)
557 	free(msg);
558     return retval;
559 }
560 
561 /*! Lock a database
562  * @param[in] h        CLICON handle
563  * @param[in] db       database, eg "running"
564  * @retval    0        OK
565  * @retval   -1        Error and logged to syslog
566  */
567 int
clicon_rpc_lock(clicon_handle h,char * db)568 clicon_rpc_lock(clicon_handle h,
569 		char         *db)
570 {
571     int                retval = -1;
572     struct clicon_msg *msg = NULL;
573     cxobj             *xret = NULL;
574     cxobj             *xerr;
575     char              *username;
576     uint32_t           session_id;
577 
578     if (session_id_check(h, &session_id) < 0)
579 	goto done;
580     username = clicon_username_get(h);
581     if ((msg = clicon_msg_encode(session_id,
582 				 "<rpc xmlns=\"%s\" username=\"%s\">"
583 				 "<lock><target><%s/></target></lock></rpc>",
584 				 NETCONF_BASE_NAMESPACE,
585 				 username?username:"", db)) == NULL)
586 	goto done;
587     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
588 	goto done;
589     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
590 	clixon_netconf_error(xerr, "Locking configuration", NULL);
591 	goto done;
592     }
593     retval = 0;
594  done:
595     if (xret)
596 	xml_free(xret);
597     if (msg)
598 	free(msg);
599     return retval;
600 }
601 
602 /*! Unlock a database
603  * @param[in] h        CLICON handle
604  * @param[in] db       database, eg "running"
605  * @retval    0        OK
606  * @retval   -1        Error and logged to syslog
607  */
608 int
clicon_rpc_unlock(clicon_handle h,char * db)609 clicon_rpc_unlock(clicon_handle h,
610 		  char         *db)
611 {
612     int                retval = -1;
613     struct clicon_msg *msg = NULL;
614     cxobj             *xret = NULL;
615     cxobj             *xerr;
616     char              *username;
617     uint32_t           session_id;
618 
619     if (session_id_check(h, &session_id) < 0)
620 	goto done;
621     username = clicon_username_get(h);
622     if ((msg = clicon_msg_encode(session_id,
623 				 "<rpc xmlns=\"%s\" username=\"%s\">"
624 				 "<unlock><target><%s/></target></unlock></rpc>",
625 				 NETCONF_BASE_NAMESPACE,
626 				 username?username:"", db)) == NULL)
627 	goto done;
628     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
629 	goto done;
630     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
631 	clixon_netconf_error(xerr, "Configuration unlock", NULL);
632 	goto done;
633     }
634     retval = 0;
635  done:
636     if (xret)
637 	xml_free(xret);
638     if (msg)
639 	free(msg);
640     return retval;
641 }
642 
643 /*! Get database configuration and state data
644  * @param[in]  h         Clicon handle
645  * @param[in]  xpath     XPath in a filter stmt (or NULL/"" for no filter)
646  * @param[in]  namespace Namespace associated w xpath
647  * @param[in]  nsc       Namespace context for filter
648  * @param[in]  content   Clixon extension: all, config, noconfig. -1 means all
649  * @param[in]  depth     Nr of XML levels to get, -1 is all, 0 is none
650  * @param[out] xt        XML tree. Free with xml_free.
651  *                       Either <config> or <rpc-error>.
652  * @retval    0          OK
653  * @retval   -1          Error, fatal or xml
654  * @note if xpath is set but namespace is NULL, the default, netconf base
655  *       namespace will be used which is most probably wrong.
656  * @code
657  *  cxobj *xt = NULL;
658  *  cvec *nsc = NULL;
659  *
660  *  if ((nsc = xml_nsctx_init(NULL, "urn:example:hello")) == NULL)
661  *     err;
662  *  if (clicon_rpc_get(h, "/hello/world", nsc, CONTENT_ALL, -1, &xt) < 0)
663  *     err;
664  *  if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
665  *     clixon_netconf_error(xerr, "clicon_rpc_get", NULL);
666  *     err;
667  *  }
668  *  if (xt)
669  *     xml_free(xt);
670  *  if (nsc)
671  *     xml_nsctx_free(nsc);
672  * @endcode
673  * @see clicon_rpc_get_config which is almost the same as with content=config, but you can also select dbname
674  * @see clixon_netconf_error
675  * @note the netconf return message is yang populated, as well as the return data
676  */
677 int
clicon_rpc_get(clicon_handle h,char * xpath,cvec * nsc,netconf_content content,int32_t depth,cxobj ** xt)678 clicon_rpc_get(clicon_handle   h,
679 	       char           *xpath,
680 	       cvec           *nsc, /* namespace context for filter */
681 	       netconf_content content,
682 	       int32_t         depth,
683 	       cxobj         **xt)
684 {
685     int                retval = -1;
686     struct clicon_msg *msg = NULL;
687     cbuf              *cb = NULL;
688     cxobj             *xret = NULL;
689     cxobj             *xerr = NULL;
690     cxobj             *xd;
691     char              *username;
692     cg_var            *cv = NULL;
693     char              *prefix;
694     uint32_t           session_id;
695     int                ret;
696     yang_stmt         *yspec;
697 
698     if (session_id_check(h, &session_id) < 0)
699 	goto done;
700     if ((cb = cbuf_new()) == NULL)
701 	goto done;
702     cprintf(cb, "<rpc xmlns=\"%s\" ", NETCONF_BASE_NAMESPACE);
703     if ((username = clicon_username_get(h)) != NULL)
704 	cprintf(cb, " username=\"%s\"", username);
705     cprintf(cb, " xmlns:%s=\"%s\"",
706 	    NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE);
707     cprintf(cb, "><get");
708     /* Clixon extension, content=all,config, or nonconfig */
709     if ((int)content != -1)
710 	cprintf(cb, " content=\"%s\"", netconf_content_int2str(content));
711     /* Clixon extension, depth=<level> */
712     if (depth != -1)
713 	cprintf(cb, " depth=\"%d\"", depth);
714     cprintf(cb, ">");
715     if (xpath && strlen(xpath)) {
716 	cprintf(cb, "<%s:filter %s:type=\"xpath\" %s:select=\"%s\"",
717 		NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX, NETCONF_BASE_PREFIX,
718 		xpath);
719 
720 	while ((cv = cvec_each(nsc, cv)) != NULL){
721 	    cprintf(cb, " xmlns");
722 	    if ((prefix = cv_name_get(cv)))
723 		cprintf(cb, ":%s", prefix);
724 	    cprintf(cb, "=\"%s\"", cv_string_get(cv));
725 	}
726 	cprintf(cb, "/>");
727     }
728     cprintf(cb, "</get></rpc>");
729     if ((msg = clicon_msg_encode(session_id,
730 				 "%s", cbuf_get(cb))) == NULL)
731 	goto done;
732     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
733 	goto done;
734     /* Send xml error back: first check error, then ok */
735     if ((xd = xpath_first(xret, NULL, "/rpc-reply/rpc-error")) != NULL)
736 	xd = xml_parent(xd); /* point to rpc-reply */
737     else if ((xd = xpath_first(xret, NULL, "/rpc-reply/data")) == NULL){
738 	if ((xd = xml_new("data", NULL, CX_ELMNT)) == NULL)
739 	    goto done;
740     }
741     else{
742 	yspec = clicon_dbspec_yang(h);
743 	if ((ret = xml_bind_yang(xd, YB_MODULE, yspec, &xerr)) < 0)
744 	    goto done;
745 	if (ret == 0){
746 	    if (clixon_netconf_internal_error(xerr,
747 					      ". Internal error, backend returned invalid XML.",
748 					      NULL) < 0)
749 		goto done;
750 	    if ((xd = xpath_first(xerr, NULL, "rpc-error")) == NULL){
751 		clicon_err(OE_XML, ENOENT, "Expected rpc-error tag but none found(internal)");
752 		goto done;
753 	    }
754 	}
755     }
756     if (xt){
757 	if (xml_rm(xd) < 0)
758 	    goto done;
759 	*xt = xd;
760     }
761     retval = 0;
762   done:
763     if (cb)
764 	cbuf_free(cb);
765     if (xerr)
766 	xml_free(xerr);
767     if (xret)
768 	xml_free(xret);
769     if (msg)
770 	free(msg);
771     return retval;
772 }
773 
774 /*! Close a (user) session
775  * @param[in] h        CLICON handle
776  * @retval    0        OK
777  * @retval   -1        Error and logged to syslog
778  */
779 int
clicon_rpc_close_session(clicon_handle h)780 clicon_rpc_close_session(clicon_handle h)
781 {
782     int                retval = -1;
783     struct clicon_msg *msg = NULL;
784     cxobj             *xret = NULL;
785     cxobj             *xerr;
786     char              *username;
787     uint32_t           session_id;
788 
789     if (session_id_check(h, &session_id) < 0)
790 	goto done;
791     username = clicon_username_get(h);
792     if ((msg = clicon_msg_encode(session_id,
793 				 "<rpc xmlns=\"%s\" username=\"%s\"><close-session/></rpc>",
794 				 NETCONF_BASE_NAMESPACE, username?username:"")) == NULL)
795 	goto done;
796     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
797 	goto done;
798     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
799 	clixon_netconf_error(xerr, "Close session", NULL);
800 	goto done;
801     }
802     retval = 0;
803  done:
804     if (xret)
805 	xml_free(xret);
806     if (msg)
807 	free(msg);
808     return retval;
809 }
810 
811 /*! Kill other user sessions
812  * @param[in] h           CLICON handle
813  * @param[in] session_id  Session id of other user session
814  * @retval    0        OK
815  * @retval   -1        Error and logged to syslog
816  */
817 int
clicon_rpc_kill_session(clicon_handle h,uint32_t session_id)818 clicon_rpc_kill_session(clicon_handle h,
819 			uint32_t      session_id)
820 {
821     int                retval = -1;
822     struct clicon_msg *msg = NULL;
823     cxobj             *xret = NULL;
824     cxobj             *xerr;
825     char              *username;
826     uint32_t           my_session_id; /* Not the one to kill */
827 
828     if (session_id_check(h, &my_session_id) < 0)
829 	goto done;
830     username = clicon_username_get(h);
831     if ((msg = clicon_msg_encode(my_session_id,
832 				 "<rpc xmlns=\"%s\" username=\"%s\"><kill-session><session-id>%u</session-id></kill-session></rpc>",
833 				 NETCONF_BASE_NAMESPACE,
834 				 username?username:"", session_id)) == NULL)
835 	goto done;
836     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
837 	goto done;
838     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
839 	clixon_netconf_error(xerr, "Kill session", NULL);
840 	goto done;
841     }
842     retval = 0;
843  done:
844     if (xret)
845 	xml_free(xret);
846     if (msg)
847 	free(msg);
848     return retval;
849 }
850 
851 /*! Send validate request to backend daemon
852  * @param[in] h        CLICON handle
853  * @param[in] db       Name of database
854  * @retval    0        OK
855  * @retval   -1        Error and logged to syslog
856  */
857 int
clicon_rpc_validate(clicon_handle h,char * db)858 clicon_rpc_validate(clicon_handle h,
859 		    char         *db)
860 {
861     int                retval = -1;
862     struct clicon_msg *msg = NULL;
863     cxobj             *xret = NULL;
864     cxobj             *xerr;
865     char              *username;
866     uint32_t           session_id;
867 
868     if (session_id_check(h, &session_id) < 0)
869 	goto done;
870     username = clicon_username_get(h);
871     if ((msg = clicon_msg_encode(session_id,
872 				 "<rpc xmlns=\"%s\" username=\"%s\"><validate><source><%s/></source></validate></rpc>",
873 				 NETCONF_BASE_NAMESPACE,
874 				 username?username:"", db)) == NULL)
875 	goto done;
876     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
877 	goto done;
878     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
879 	clixon_netconf_error(xerr, CLIXON_ERRSTR_VALIDATE_FAILED, NULL);
880 	goto done;
881     }
882     retval = 0;
883  done:
884     if (msg)
885 	free(msg);
886     if (xret)
887 	xml_free(xret);
888     return retval;
889 }
890 
891 /*! Commit changes send a commit request to backend daemon
892  * @param[in] h          CLICON handle
893  * @retval    0        OK
894  * @retval   -1        Error and logged to syslog
895  */
896 int
clicon_rpc_commit(clicon_handle h)897 clicon_rpc_commit(clicon_handle h)
898 {
899     int                retval = -1;
900     struct clicon_msg *msg = NULL;
901     cxobj             *xret = NULL;
902     cxobj             *xerr;
903     char              *username;
904     uint32_t           session_id;
905 
906     if (session_id_check(h, &session_id) < 0)
907 	goto done;
908     username = clicon_username_get(h);
909     if ((msg = clicon_msg_encode(session_id,
910 				 "<rpc xmlns=\"%s\" username=\"%s\"><commit/></rpc>",
911 				 NETCONF_BASE_NAMESPACE,
912 				 username?username:"")) == NULL)
913 	goto done;
914     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
915 	goto done;
916     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
917 	clixon_netconf_error(xerr, CLIXON_ERRSTR_COMMIT_FAILED, NULL);
918 	goto done;
919     }
920     retval = 0;
921  done:
922     if (xret)
923 	xml_free(xret);
924     if (msg)
925 	free(msg);
926     return retval;
927 }
928 
929 /*! Discard all changes in candidate / revert to running
930  * @param[in] h        CLICON handle
931  * @retval    0        OK
932  * @retval   -1        Error and logged to syslog
933  */
934 int
clicon_rpc_discard_changes(clicon_handle h)935 clicon_rpc_discard_changes(clicon_handle h)
936 {
937     int                retval = -1;
938     struct clicon_msg *msg = NULL;
939     cxobj             *xret = NULL;
940     cxobj             *xerr;
941     char              *username;
942     uint32_t           session_id;
943 
944     if (session_id_check(h, &session_id) < 0)
945 	goto done;
946     username = clicon_username_get(h);
947     if ((msg = clicon_msg_encode(session_id,
948 				 "<rpc xmlns=\"%s\"  username=\"%s\"><discard-changes/></rpc>",
949 				 NETCONF_BASE_NAMESPACE,
950 				 username?username:"")) == NULL)
951 	goto done;
952     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
953 	goto done;
954     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
955 	clixon_netconf_error(xerr, "Discard changes", NULL);
956 	goto done;
957     }
958     retval = 0;
959  done:
960     if (xret)
961 	xml_free(xret);
962     if (msg)
963 	free(msg);
964     return retval;
965 }
966 
967 /*! Create a new notification subscription
968  * @param[in]   h        Clicon handle
969  * @param{in]   stream   name of notificatio/log stream (CLICON is predefined)
970  * @param{in]   filter   message filter, eg xpath for xml notifications
971  * @param[out]  s0       socket returned where notification mesages will appear
972  * @retval      0        OK
973  * @retval      -1       Error and logged to syslog
974 
975  * @note When using netconf create-subsrciption,status and format is not supported
976  */
977 int
clicon_rpc_create_subscription(clicon_handle h,char * stream,char * filter,int * s0)978 clicon_rpc_create_subscription(clicon_handle    h,
979 			       char            *stream,
980 			       char            *filter,
981 			       int             *s0)
982 {
983     int                retval = -1;
984     struct clicon_msg *msg = NULL;
985     cxobj             *xret = NULL;
986     cxobj             *xerr;
987     char              *username;
988     uint32_t           session_id;
989 
990     if (session_id_check(h, &session_id) < 0)
991 	goto done;
992     username = clicon_username_get(h);
993     if ((msg = clicon_msg_encode(session_id,
994 				 "<rpc xmlns=\"%s\" username=\"%s\"><create-subscription xmlns=\"%s\">"
995 				 "<stream>%s</stream>"
996 				 "<filter type=\"xpath\" select=\"%s\" />"
997 				 "</create-subscription></rpc>",
998 				 NETCONF_BASE_NAMESPACE,
999 				 username?username:"",
1000 				 EVENT_RFC5277_NAMESPACE,
1001 				 stream?stream:"", filter?filter:"")) == NULL)
1002 	goto done;
1003     if (clicon_rpc_msg(h, msg, &xret, s0) < 0)
1004 	goto done;
1005     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
1006 	clixon_netconf_error(xerr, "Create subscription", NULL);
1007 	goto done;
1008     }
1009     retval = 0;
1010   done:
1011     if (xret)
1012 	xml_free(xret);
1013     if (msg)
1014 	free(msg);
1015     return retval;
1016 }
1017 
1018 /*! Send a debug request to backend server
1019  * @param[in] h        CLICON handle
1020  * @param[in] level    Debug level
1021  * @retval    0        OK
1022  * @retval   -1        Error and logged to syslog
1023  */
1024 int
clicon_rpc_debug(clicon_handle h,int level)1025 clicon_rpc_debug(clicon_handle h,
1026 		int           level)
1027 {
1028     int                retval = -1;
1029     struct clicon_msg *msg = NULL;
1030     cxobj             *xret = NULL;
1031     cxobj             *xerr;
1032     char              *username;
1033     uint32_t           session_id;
1034 
1035     if (session_id_check(h, &session_id) < 0)
1036 	goto done;
1037     username = clicon_username_get(h);
1038     if ((msg = clicon_msg_encode(session_id,
1039 				 "<rpc xmlns=\"%s\" username=\"%s\"><debug xmlns=\"%s\"><level>%d</level></debug></rpc>",
1040 				 NETCONF_BASE_NAMESPACE,
1041 				 username?username:"",
1042 				 CLIXON_LIB_NS,
1043 				 level)) == NULL)
1044 	goto done;
1045     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
1046 	goto done;
1047     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
1048 	clixon_netconf_error(xerr, "Debug", NULL);
1049 	goto done;
1050     }
1051     if (xpath_first(xret, NULL, "//rpc-reply/ok") == NULL){
1052 	clicon_err(OE_XML, 0, "rpc error"); /* XXX extract info from rpc-error */
1053 	goto done;
1054     }
1055     retval = 0;
1056  done:
1057     if (msg)
1058 	free(msg);
1059     if (xret)
1060 	xml_free(xret);
1061     return retval;
1062 }
1063 
1064 /*! Send a hello request to the backend server
1065  * @param[in] h        CLICON handle
1066  * @param[in] level    Debug level
1067  * @retval    0        OK
1068  * @retval   -1        Error and logged to syslog
1069  * @note this is internal netconf to backend, not northbound to user client
1070  * @note this deviates from RFC6241 slightly in that it waits for a reply, the RFC does not
1071  *       stipulate that.
1072  */
1073 int
clicon_hello_req(clicon_handle h,uint32_t * id)1074 clicon_hello_req(clicon_handle h,
1075 		 uint32_t     *id)
1076 {
1077     int                retval = -1;
1078     struct clicon_msg *msg = NULL;
1079     cxobj             *xret = NULL;
1080     cxobj             *xerr;
1081     cxobj             *x;
1082     char              *username;
1083     char              *b;
1084     int                ret;
1085 
1086     username = clicon_username_get(h);
1087     if ((msg = clicon_msg_encode(0, "<hello username=\"%s\" xmlns=\"%s\"><capabilities><capability>urn:ietf:params:netconf:base:1.0</capability></capabilities></hello>",
1088 				 username?username:"",
1089 				 NETCONF_BASE_NAMESPACE)) == NULL)
1090 	goto done;
1091     if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
1092 	goto done;
1093     if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
1094 	clixon_netconf_error(xerr, "Hello", NULL);
1095 	goto done;
1096     }
1097     if ((x = xpath_first(xret, NULL, "hello/session-id")) == NULL){
1098 	clicon_err(OE_XML, 0, "hello session-id");
1099 	goto done;
1100     }
1101     b = xml_body(x);
1102     if ((ret = parse_uint32(b, id, NULL)) <= 0){
1103 	clicon_err(OE_XML, errno, "parse_uint32");
1104 	goto done;
1105     }
1106     retval = 0;
1107  done:
1108     if (msg)
1109 	free(msg);
1110     if (xret)
1111 	xml_free(xret);
1112     return retval;
1113 }
1114 
1115 
1116