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