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 
38 #ifdef HAVE_CONFIG_H
39 #include "clixon_config.h" /* generated by config & autoconf */
40 #endif
41 
42 #include <stdlib.h>
43 #include <stdio.h>
44 #include <string.h>
45 #include <signal.h>
46 #include <errno.h>
47 #include <stdarg.h>
48 #include <time.h>
49 #include <ctype.h>
50 
51 #include <unistd.h>
52 #include <dirent.h>
53 #include <syslog.h>
54 #include <arpa/inet.h>
55 #include <netinet/in.h>
56 #include <sys/types.h>
57 #include <sys/stat.h>
58 #include <fcntl.h>
59 #include <sys/wait.h>
60 #include <sys/param.h>
61 #include <sys/mount.h>
62 #include <pwd.h>
63 
64 /* cligen */
65 #include <cligen/cligen.h>
66 
67 /* clicon */
68 #include <clixon/clixon.h>
69 
70 #include "clixon_cli_api.h"
71 
72 #include "cli_common.h"
73 
74 /*! Register log notification stream
75  * @param[in] h       Clicon handle
76  * @param[in] stream  Event stream. CLICON is predefined, others are application-defined
77  * @param[in] filter  Filter. For xml notification ie xpath: .[name="kalle"]
78  * @param[in] status  0 for stop, 1 to start
79  * @param[in] fn      Callback function called when notification occurs
80  * @param[in] arg     Argument to function note
81  * Note this calls cligen_regfd which may callback on cli command interpretator
82  */
83 int
cli_notification_register(clicon_handle h,char * stream,enum format_enum format,char * filter,int status,int (* fn)(int,void *),void * arg)84 cli_notification_register(clicon_handle    h,
85 			  char            *stream,
86 			  enum format_enum format,
87 			  char            *filter,
88 			  int              status,
89 			  int            (*fn)(int, void*),
90 			  void            *arg)
91 {
92     int              retval = -1;
93     char            *logname = NULL;
94     void            *p;
95     int              s;
96     clicon_hash_t   *cdat = clicon_data(h);
97     size_t           len;
98     int              s_exist = -1;
99 
100     len = strlen("log_socket_") + strlen(stream) + 1;
101     if ((logname = malloc(len)) == NULL){
102 	clicon_err(OE_UNIX, errno, "malloc");
103 	goto done;
104     }
105     snprintf(logname, len, "log_socket_%s", stream);
106     if ((p = clicon_hash_value(cdat, logname, &len)) != NULL)
107 	s_exist = *(int*)p;
108 
109     if (status){ /* start */
110 	if (s_exist!=-1){
111 	    clicon_err(OE_PLUGIN, 0, "Result log socket already exists");
112 	    goto done;
113 	}
114 	if (clicon_rpc_create_subscription(h, stream, filter, &s) < 0)
115 	    goto done;
116 	if (cligen_regfd(s, fn, arg) < 0)
117 	    goto done;
118 	if (clicon_hash_add(cdat, logname, &s, sizeof(s)) == NULL)
119 	    goto done;
120     }
121     else{ /* stop */
122 	if (s_exist != -1){
123 	    cligen_unregfd(s_exist);
124 	}
125 	clicon_hash_del(cdat, logname);
126 #if 0 /* cant turn off */
127 	if (clicon_rpc_create_subscription(h, status, stream, format, filter, NULL) < 0)
128 	    goto done;
129 #endif
130     }
131     retval = 0;
132   done:
133     if (logname)
134 	free(logname);
135     return retval;
136 }
137 
138 /* Signal functions, not exported to API */
139 void
cli_signal_block(clicon_handle h)140 cli_signal_block(clicon_handle h)
141 {
142 	clicon_signal_block (SIGTSTP);
143 	clicon_signal_block (SIGQUIT);
144 	clicon_signal_block (SIGCHLD);
145 	if (!clicon_quiet_mode(h))
146 	    clicon_signal_block (SIGINT);
147 }
148 
149 void
cli_signal_unblock(clicon_handle h)150 cli_signal_unblock(clicon_handle h)
151 {
152 	clicon_signal_unblock (SIGTSTP);
153 	clicon_signal_unblock (SIGQUIT);
154 	clicon_signal_unblock (SIGCHLD);
155 	clicon_signal_unblock (SIGINT);
156 }
157 
158 /*
159  * Flush pending signals for a given signal type
160  */
161 void
cli_signal_flush(clicon_handle h)162 cli_signal_flush(clicon_handle h)
163 {
164     /* XXX A bit rough. Use sigpending() and more clever logic ?? */
165 
166     sigfn_t   h1, h2, h3, h4;
167 
168     set_signal (SIGTSTP, SIG_IGN, &h1);
169     set_signal (SIGQUIT, SIG_IGN, &h2);
170     set_signal (SIGCHLD, SIG_IGN, &h3);
171     set_signal (SIGINT, SIG_IGN, &h4);
172 
173     cli_signal_unblock (h);
174 
175     set_signal (SIGTSTP, h1, NULL);
176     set_signal (SIGQUIT, h2, NULL);
177     set_signal (SIGCHLD, h3, NULL);
178     set_signal (SIGINT, h4, NULL);
179 
180     cli_signal_block (h);
181 }
182 
183 /*! Create body and add last CLI variable vector as value
184  * Create and add an XML body as child of XML node xbot. Set its value to the last
185  * CLI variable vector element.
186  */
187 static int
dbxml_body(cxobj * xbot,cvec * cvv)188 dbxml_body(cxobj     *xbot,
189 	   cvec      *cvv)
190 {
191     int     retval = -1;
192     char   *str = NULL;
193     cxobj  *xb;
194     cg_var *cval;
195     int     len;
196 
197     len = cvec_len(cvv);
198     cval = cvec_i(cvv, len-1);
199     if ((str = cv2str_dup(cval)) == NULL){
200 	clicon_err(OE_UNIX, errno, "cv2str_dup");
201 	goto done;
202     }
203     if ((xb = xml_new("body", xbot, CX_BODY)) == NULL)
204 	goto done;
205     if (xml_value_set(xb,  str) < 0)
206 	goto done;
207     retval = 0;
208  done:
209     if (str)
210 	free(str);
211     return retval;
212 }
213 
214 /*! Modify xml datastore from a callback using xml key format strings
215  * @param[in]  h     Clicon handle
216  * @param[in]  cvv   Vector of cli string and instantiated variables
217  * @param[in]  argv  Vector. First element xml key format string, eg "/aaa/%s"
218  * @param[in]  op    Operation to perform on database
219  * @param[in]  nsctx Namespace context for last value added
220  * Cvv will contain first the complete cli string, and then a set of optional
221  * instantiated variables.
222  * If the last node is a leaf, the last cvv element is added as a value. This value
223  * Example:
224  * cvv[0]  = "set interfaces interface eth0 type bgp"
225  * cvv[1]  = "eth0"
226  * cvv[2]  = "bgp"
227  * argv[0] = "/interfaces/interface/%s/type"
228  * op: OP_MERGE
229  * @see cli_callback_generate where arg is generated
230  * @note The last value may require namespace binding present in nsctx. Note that the nsctx
231  *   cannot normally be supplied by the clispec functions, such as cli_set, but need to be
232  *   generated by afunction such as clixon_instance_id_bind() or other programmatically.
233  */
234 int
cli_dbxml(clicon_handle h,cvec * cvv,cvec * argv,enum operation_type op,cvec * nsctx)235 cli_dbxml(clicon_handle       h,
236 	  cvec               *cvv,
237 	  cvec               *argv,
238 	  enum operation_type op,
239 	  cvec               *nsctx)
240 {
241     int        retval = -1;
242     char      *api_path_fmt;    /* xml key format */
243     char      *api_path = NULL; /* xml key */
244     cg_var    *arg;
245     cbuf      *cb = NULL;
246     yang_stmt *yspec;
247     cxobj     *xbot = NULL;     /* xpath, NULL if datastore */
248     yang_stmt *y = NULL;        /* yang spec of xpath */
249     cxobj     *xtop = NULL;     /* xpath root */
250     cxobj     *xa;              /* attribute */
251     cxobj     *xerr = NULL;
252     int        ret;
253     cg_var    *cv;
254 
255     if (cvec_len(argv) != 1){
256 	clicon_err(OE_PLUGIN, 0, "Requires one element to be xml key format string");
257 	goto done;
258     }
259     if ((yspec = clicon_dbspec_yang(h)) == NULL){
260 	clicon_err(OE_FATAL, 0, "No DB_SPEC");
261 	goto done;
262     }
263     arg = cvec_i(argv, 0);
264     api_path_fmt = cv_string_get(arg);
265     if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0)
266 	goto done;
267     /* Create config top-of-tree */
268     if ((xtop = xml_new("config", NULL, CX_ELMNT)) == NULL)
269 	goto done;
270     xbot = xtop;
271     if (api_path){
272 	if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0)
273 	    goto done;
274 	if (ret == 0){
275 	    if ((cb = cbuf_new()) == NULL){
276 		clicon_err(OE_UNIX, errno, "cbuf_new");
277 		goto done;
278 	    }
279 	    cprintf(cb, "api-path syntax error \"%s\": ", api_path_fmt);
280 	    if (netconf_err2cb(xerr, cb) < 0)
281 		goto done;
282 	    clicon_err(OE_CFG, EINVAL, "%s", cbuf_get(cb));
283 	    goto done;
284 	}
285     }
286     if ((xa = xml_new("operation", xbot, CX_ATTR)) == NULL)
287 	goto done;
288     if (xml_prefix_set(xa, NETCONF_BASE_PREFIX) < 0)
289 	goto done;
290     if (xml_value_set(xa, xml_operation2str(op)) < 0)
291 	goto done;
292     /* Add body last in case of leaf */
293     if (cvec_len(cvv) > 1 &&
294 	(yang_keyword_get(y) == Y_LEAF)){
295 	/* Add the body last */
296 	if (dbxml_body(xbot, cvv) < 0)
297 	    goto done;
298 	/* Loop over namespace context and add them to this leaf node */
299 	cv = NULL;
300 	while ((cv = cvec_each(nsctx, cv)) != NULL){
301 	    char *ns = cv_string_get(cv);
302 	    char *pf = cv_name_get(cv);
303 	    if (ns && pf && xmlns_set(xbot, pf, ns) < 0)
304 		goto done;
305 	}
306     }
307     if ((cb = cbuf_new()) == NULL){
308 	clicon_err(OE_XML, errno, "cbuf_new");
309 	goto done;
310     }
311     if (clicon_xml2cbuf(cb, xtop, 0, 0, -1) < 0)
312 	goto done;
313     if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0)
314 	goto done;
315     retval = 0;
316  done:
317     if (xerr)
318 	xml_free(xerr);
319     if (cb)
320 	cbuf_free(cb);
321     if (api_path)
322 	free(api_path);
323     if (xtop)
324 	xml_free(xtop);
325     return retval;
326 }
327 
328 /*! Set datastore xml entry
329  * @param[in]  h    Clicon handle
330  * @param[in]  cvv  Vector of cli string and instantiated variables
331  * @param[in]  argv Vector. First element xml key format string, eg "/aaa/%s"
332  */
333 int
cli_set(clicon_handle h,cvec * cvv,cvec * argv)334 cli_set(clicon_handle h,
335 	cvec         *cvv,
336 	cvec         *argv)
337 {
338     int retval = -1;
339 
340     if (cli_dbxml(h, cvv, argv, OP_REPLACE, NULL) < 0)
341 	goto done;
342     retval = 0;
343  done:
344     return retval;
345 }
346 
347 /*! Merge datastore xml entry
348  * @param[in]  h    Clicon handle
349  * @param[in]  cvv  Vector of cli string and instantiated variables
350  * @param[in]  argv Vector. First element xml key format string, eg "/aaa/%s"
351  */
352 int
cli_merge(clicon_handle h,cvec * cvv,cvec * argv)353 cli_merge(clicon_handle h,
354 	  cvec         *cvv,
355 	  cvec         *argv)
356 {
357     int retval = -1;
358 
359     if (cli_dbxml(h, cvv, argv, OP_MERGE, NULL) < 0)
360 	goto done;
361     retval = 0;
362  done:
363     return retval;
364 }
365 
366 /*! Create datastore xml entry
367  * @param[in]  h    Clicon handle
368  * @param[in]  cvv  Vector of cli string and instantiated variables
369  * @param[in]  argv Vector. First element xml key format string, eg "/aaa/%s"
370  */
371 int
cli_create(clicon_handle h,cvec * cvv,cvec * argv)372 cli_create(clicon_handle h,
373 	   cvec         *cvv,
374 	   cvec         *argv)
375 {
376     int retval = -1;
377 
378     if (cli_dbxml(h, cvv, argv, OP_CREATE, NULL) < 0)
379 	goto done;
380     retval = 0;
381  done:
382     return retval;
383 }
384 /*! Remove datastore xml entry
385  * @param[in]  h    Clicon handle
386  * @param[in]  cvv  Vector of cli string and instantiated variables
387  * @param[in]  argv Vector. First element xml key format string, eg "/aaa/%s"
388  * @see cli_del
389  */
390 int
cli_remove(clicon_handle h,cvec * cvv,cvec * argv)391 cli_remove(clicon_handle h,
392 	   cvec         *cvv,
393 	   cvec         *argv)
394 {
395     int retval = -1;
396 
397     if (cli_dbxml(h, cvv, argv, OP_REMOVE, NULL) < 0)
398 	goto done;
399     retval = 0;
400  done:
401     return retval;
402 }
403 
404 /*! Delete datastore xml
405  * @param[in]  h    Clicon handle
406  * @param[in]  cvv  Vector of cli string and instantiated variables
407  * @param[in]  argv Vector. First element xml key format string, eg "/aaa/%s"
408  */
409 int
cli_del(clicon_handle h,cvec * cvv,cvec * argv)410 cli_del(clicon_handle h,
411 	cvec         *cvv,
412 	cvec         *argv)
413 {
414     int   retval = -1;
415 
416     if (cli_dbxml(h, cvv, argv, OP_REMOVE, NULL) < 0)
417 	goto done;
418     retval = 0;
419  done:
420     return retval;
421 }
422 
423 /*! Set debug level on CLI client (not backend daemon)
424  * @param[in] h     Clicon handle
425  * @param[in] vars  If variable "level" exists, its integer value is used
426  * @param[in] arg   Else use the integer value of argument
427  * @note The level is either what is specified in arg as int argument.
428  *       _or_ if a 'level' variable is present in vars use that value instead.
429  */
430 int
cli_debug_cli(clicon_handle h,cvec * vars,cvec * argv)431 cli_debug_cli(clicon_handle h,
432 	       cvec         *vars,
433 	       cvec         *argv)
434 {
435     int     retval = -1;
436     cg_var *cv;
437     int     level;
438 
439     if ((cv = cvec_find(vars, "level")) == NULL){
440 	if (cvec_len(argv) != 1){
441 	    clicon_err(OE_PLUGIN, 0, "Requires either label var or single arg: 0|1");
442 	    goto done;
443 	}
444 	cv = cvec_i(argv, 0);
445     }
446     level = cv_int32_get(cv);
447     /* cli */
448     clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */
449     retval = 0;
450  done:
451     return retval;
452 }
453 
454 /*! Set debug level on backend daemon (not CLI)
455  * @param[in] h     Clicon handle
456  * @param[in] vars  If variable "level" exists, its integer value is used
457  * @param[in] arg   Else use the integer value of argument
458  * @note The level is either what is specified in arg as int argument.
459  *       _or_ if a 'level' variable is present in vars use that value instead.
460  */
461 int
cli_debug_backend(clicon_handle h,cvec * vars,cvec * argv)462 cli_debug_backend(clicon_handle h,
463 		  cvec         *vars,
464 		  cvec         *argv)
465 {
466     int     retval = -1;
467     cg_var *cv;
468     int     level;
469 
470     if ((cv = cvec_find(vars, "level")) == NULL){
471 	if (cvec_len(argv) != 1){
472 	    clicon_err(OE_PLUGIN, 0, "Requires either label var or single arg: 0|1");
473 	    goto done;
474 	}
475 	cv = cvec_i(argv, 0);
476     }
477     level = cv_int32_get(cv);
478     /* config daemon */
479     retval = clicon_rpc_debug(h, level);
480  done:
481     return retval;
482 }
483 
484 /*! Set debug level on restconf daemon
485  * @param[in] h     Clicon handle
486  * @param[in] vars  If variable "level" exists, its integer value is used
487  * @param[in] arg   Else use the integer value of argument
488  * @note The level is either what is specified in arg as int argument.
489  *       _or_ if a 'level' variable is present in vars use that value instead.
490  */
491 int
cli_debug_restconf(clicon_handle h,cvec * vars,cvec * argv)492 cli_debug_restconf(clicon_handle h,
493 		   cvec         *vars,
494 		   cvec         *argv)
495 {
496     int     retval = -1;
497     cg_var *cv;
498     int     level;
499 
500     if ((cv = cvec_find(vars, "level")) == NULL){
501 	if (cvec_len(argv) != 1){
502 	    clicon_err(OE_PLUGIN, 0, "Requires either label var or single arg: 0|1");
503 	    goto done;
504 	}
505 	cv = cvec_i(argv, 0);
506     }
507     level = cv_int32_get(cv);
508     /* restconf daemon */
509     if (0) /* XXX notyet */
510 	retval = clicon_rpc_debug(h, level);
511  done:
512     return retval;
513 }
514 
515 
516 /*! Set syntax mode
517  */
518 int
cli_set_mode(clicon_handle h,cvec * vars,cvec * argv)519 cli_set_mode(clicon_handle h,
520 	      cvec         *vars,
521 	      cvec         *argv)
522 {
523     int     retval = -1;
524     char   *str = NULL;
525 
526     if (cvec_len(argv) != 1){
527 	clicon_err(OE_PLUGIN, 0, "Requires one element to be cli mode");
528 	goto done;
529     }
530     str = cv_string_get(cvec_i(argv, 0));
531     cli_set_syntax_mode(h, str);
532     retval = 0;
533   done:
534     return retval;
535 }
536 
537 /*! Start bash from cli callback
538  * XXX Application specific??
539  * XXX replace fprintf with clicon_err?
540  */
541 int
cli_start_shell(clicon_handle h,cvec * vars,cvec * argv)542 cli_start_shell(clicon_handle h,
543 		cvec         *vars,
544 		cvec         *argv)
545 {
546     char          *cmd;
547     struct passwd *pw;
548     int            retval = -1;
549     char           bcmd[128];
550     cg_var        *cv1 = cvec_i(vars, 1);
551 
552     cmd = (cvec_len(vars)>1 ? cv_string_get(cv1) : NULL);
553 
554     if ((pw = getpwuid(getuid())) == NULL){
555 	fprintf(stderr, "%s: getpwuid: %s\n",
556                __FUNCTION__, strerror(errno));
557 	goto done;
558     }
559     if (chdir(pw->pw_dir) < 0){
560 	fprintf(stderr, "%s: chdir(%s): %s\n",
561 		__FUNCTION__, pw->pw_dir, strerror(errno));
562 	endpwent();
563 	goto done;
564     }
565     endpwent();
566     cli_signal_flush(h);
567     cli_signal_unblock(h);
568     if (cmd){
569 	snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd);
570 	if (system(bcmd) < 0){
571 	    cli_signal_block(h);
572 	    fprintf(stderr, "%s: system(bash -c): %s\n",
573 		    __FUNCTION__, strerror(errno));
574 	    goto done;
575 	}
576     }
577     else
578 	if (system("bash -l") < 0){
579 	    cli_signal_block(h);
580 	    fprintf(stderr, "%s: system(bash): %s\n",
581 		    __FUNCTION__, strerror(errno));
582 	    goto done;
583 	}
584     cli_signal_block(h);
585 #if 0 /* Allow errcodes from bash */
586     if (retval != 0){
587 	fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval);
588 	goto done;
589     }
590 #endif
591     retval = 0;
592  done:
593     return retval;
594 }
595 
596 /*! Generic quit callback
597  */
598 int
cli_quit(clicon_handle h,cvec * vars,cvec * argv)599 cli_quit(clicon_handle h,
600 	  cvec         *vars,
601 	  cvec         *argv)
602 {
603     cligen_exiting_set(cli_cligen(h), 1);
604     return 0;
605 }
606 
607 /*! Generic commit callback
608  * @param[in]  argv No arguments expected
609  */
610 int
cli_commit(clicon_handle h,cvec * vars,cvec * argv)611 cli_commit(clicon_handle h,
612 	    cvec         *vars,
613 	    cvec         *argv)
614 {
615     int            retval = -1;
616 
617     if ((retval = clicon_rpc_commit(h)) < 0)
618 	goto done;
619     retval = 0;
620   done:
621     return retval;
622 }
623 
624 /*! Generic validate callback
625  */
626 int
cli_validate(clicon_handle h,cvec * vars,cvec * argv)627 cli_validate(clicon_handle h,
628 	      cvec         *vars,
629 	      cvec         *argv)
630 {
631     int     retval = -1;
632 
633     if ((retval = clicon_rpc_validate(h, "candidate")) < 0)
634 	goto done;
635     retval = 0;
636  done:
637     return retval;
638 }
639 
640 /*! Compare two dbs using XML. Write to file and run diff
641  */
642 static int
compare_xmls(cxobj * xc1,cxobj * xc2,int astext)643 compare_xmls(cxobj *xc1,
644 	     cxobj *xc2,
645 	     int    astext)
646 {
647     int    fd;
648     FILE  *f;
649     char   filename1[MAXPATHLEN];
650     char   filename2[MAXPATHLEN];
651     char   cmd[MAXPATHLEN];
652     int    retval = -1;
653     cxobj *xc;
654 
655     snprintf(filename1, sizeof(filename1), "/tmp/cliconXXXXXX");
656     snprintf(filename2, sizeof(filename2), "/tmp/cliconXXXXXX");
657     if ((fd = mkstemp(filename1)) < 0){
658 	clicon_err(OE_UNDEF, errno, "tmpfile: %s", strerror (errno));
659 	goto done;
660     }
661     if ((f = fdopen(fd, "w")) == NULL)
662 	goto done;
663     xc = NULL;
664     if (astext)
665 	while ((xc = xml_child_each(xc1, xc, -1)) != NULL)
666 	    xml2txt_cb(f, xc, cligen_output);
667     else
668 	while ((xc = xml_child_each(xc1, xc, -1)) != NULL)
669 	    clicon_xml2file_cb(f, xc, 0, 1, cligen_output);
670 
671     fclose(f);
672     close(fd);
673 
674     if ((fd = mkstemp(filename2)) < 0){
675 	clicon_err(OE_UNDEF, errno, "mkstemp: %s", strerror (errno));
676 	goto done;
677     }
678     if ((f = fdopen(fd, "w")) == NULL)
679 	goto done;
680     xc = NULL;
681     if (astext)
682 	while ((xc = xml_child_each(xc2, xc, -1)) != NULL)
683 	    xml2txt_cb(f, xc, cligen_output);
684     else
685 	while ((xc = xml_child_each(xc2, xc, -1)) != NULL)
686 	    clicon_xml2file_cb(f, xc, 0, 1, cligen_output);
687     fclose(f);
688     close(fd);
689 
690     snprintf(cmd, sizeof(cmd), "/usr/bin/diff -dU 1 %s %s |  grep -v @@ | sed 1,2d", 		 filename1, filename2);
691     if (system(cmd) < 0)
692 	goto done;
693 
694     retval = 0;
695   done:
696     unlink(filename1);
697     unlink(filename2);
698     return retval;
699 }
700 
701 /*! Compare two dbs using XML. Write to file and run diff
702  * @param[in]   h     Clicon handle
703  * @param[in]   cvv
704  * @param[in]   arg   arg: 0 as xml, 1: as text
705  */
706 int
compare_dbs(clicon_handle h,cvec * cvv,cvec * argv)707 compare_dbs(clicon_handle h,
708 	    cvec         *cvv,
709 	    cvec         *argv)
710 {
711     cxobj *xc1 = NULL; /* running xml */
712     cxobj *xc2 = NULL; /* candidate xml */
713     cxobj *xerr = NULL;
714     int    retval = -1;
715     int    astext;
716 
717     if (cvec_len(argv) > 1){
718 	clicon_err(OE_PLUGIN, 0, "Requires 0 or 1 element. If given: astext flag 0|1");
719 	goto done;
720     }
721     if (cvec_len(argv))
722 	astext = cv_int32_get(cvec_i(argv, 0));
723     else
724 	astext = 0;
725     if (clicon_rpc_get_config(h, NULL, "running", "/", NULL, &xc1) < 0)
726 	goto done;
727     if ((xerr = xpath_first(xc1, NULL, "/rpc-error")) != NULL){
728 	clixon_netconf_error(xerr, "Get configuration", NULL);
729 	goto done;
730     }
731     if (clicon_rpc_get_config(h, NULL, "candidate", "/", NULL, &xc2) < 0)
732 	goto done;
733     if ((xerr = xpath_first(xc2, NULL, "/rpc-error")) != NULL){
734 	clixon_netconf_error(xerr, "Get configuration", NULL);
735 	goto done;
736     }
737     if (compare_xmls(xc1, xc2, astext) < 0) /* astext? */
738 	goto done;
739     retval = 0;
740   done:
741     if (xc1)
742 	xml_free(xc1);
743     if (xc2)
744 	xml_free(xc2);
745     return retval;
746 }
747 
748 /*! Load a configuration file to candidate database
749  * Utility function used by cligen spec file
750  * @param[in] h     CLICON handle
751  * @param[in] cvv   Vector of variables (where <varname> is found)
752  * @param[in] argv  A string: "<varname> (merge|replace)"
753  *   <varname> is name of a variable occuring in "cvv" containing filename
754  * @note that "filename" is local on client filesystem not backend.
755  * @note file is assumed to have a dummy top-tag, eg <clicon></clicon>
756  * @code
757  *   # cligen spec
758  *   load file <name2:string>, load_config_file("name2","merge");
759  * @endcode
760  * @see save_config_file
761  */
762 int
load_config_file(clicon_handle h,cvec * cvv,cvec * argv)763 load_config_file(clicon_handle h,
764 		 cvec         *cvv,
765 		 cvec         *argv)
766 {
767     int         ret = -1;
768     struct stat st;
769     char       *filename = NULL;
770     int         replace;
771     cg_var     *cv;
772     char       *opstr;
773     char       *varstr;
774     int         fd = -1;
775     cxobj      *xt = NULL;
776     cxobj      *x;
777     cbuf       *cbxml;
778 
779     if (cvec_len(argv) != 2){
780 	if (cvec_len(argv)==1)
781 	    clicon_err(OE_PLUGIN, 0, "Got single argument:\"%s\". Expected \"<varname>,<op>\"", cv_string_get(cvec_i(argv,0)));
782 	else
783 	    clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <varname>,<op>", cvec_len(argv));
784 	goto done;
785     }
786     varstr = cv_string_get(cvec_i(argv, 0));
787     opstr  = cv_string_get(cvec_i(argv, 1));
788     if (strcmp(opstr, "merge") == 0)
789 	replace = 0;
790     else if (strcmp(opstr, "replace") == 0)
791 	replace = 1;
792     else{
793 	clicon_err(OE_PLUGIN, 0, "No such op: %s, expected merge or replace", opstr);
794 	goto done;
795     }
796     if ((cv = cvec_find(cvv, varstr)) == NULL){
797 	clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
798 	goto done;
799     }
800     filename = cv_string_get(cv);
801     if (stat(filename, &st) < 0){
802  	clicon_err(OE_UNIX, 0, "load_config: stat(%s): %s",
803 		   filename, strerror(errno));
804 	goto done;
805     }
806     /* Open and parse local file into xml */
807     if ((fd = open(filename, O_RDONLY)) < 0){
808 	clicon_err(OE_UNIX, errno, "open(%s)", filename);
809 	goto done;
810     }
811     if (clixon_xml_parse_file(fd, YB_NONE, NULL, NULL, &xt, NULL) < 0)
812 	goto done;
813     if (xt == NULL)
814 	goto done;
815     if ((cbxml = cbuf_new()) == NULL)
816 	goto done;
817     x = NULL;
818     while ((x = xml_child_each(xt, x, -1)) != NULL) {
819 	/* Ensure top-level is "config", maybe this is too rough? */
820 	xml_name_set(x, "config");
821 	if (clicon_xml2cbuf(cbxml, x, 0, 0, -1) < 0)
822 	    goto done;
823     }
824     if (clicon_rpc_edit_config(h, "candidate",
825 			       replace?OP_REPLACE:OP_MERGE,
826 			       cbuf_get(cbxml)) < 0)
827 	goto done;
828     cbuf_free(cbxml);
829     //    }
830     ret = 0;
831  done:
832     if (xt)
833 	xml_free(xt);
834     if (fd != -1)
835 	close(fd);
836     return ret;
837 }
838 
839 /*! Copy database to local file
840  * Utility function used by cligen spec file
841  * @param[in] h     CLICON handle
842  * @param[in] cvv  variable vector (containing <varname>)
843  * @param[in] argv  a string: "<dbname> <varname>"
844  *   <dbname>  is running, candidate, or startup
845  *   <varname> is name of cligen variable in the "cvv" vector containing file name
846  * Note that "filename" is local on client filesystem not backend.
847  * The function can run without a local database
848  * @note The file is saved with dummy top-tag: clicon: <clicon></clicon>
849  * @code
850  *   save file <name:string>, save_config_file("running name");
851  * @endcode
852  * @see load_config_file
853  */
854 int
save_config_file(clicon_handle h,cvec * cvv,cvec * argv)855 save_config_file(clicon_handle h,
856 		 cvec         *cvv,
857 		 cvec         *argv)
858 {
859     int        retval = -1;
860     char      *filename = NULL;
861     cg_var    *cv;
862     char      *dbstr;
863     char      *varstr;
864     cxobj     *xt = NULL;
865     cxobj     *xerr;
866     FILE      *f = NULL;
867 
868     if (cvec_len(argv) != 2){
869 	if (cvec_len(argv)==1)
870 	    clicon_err(OE_PLUGIN, 0, "Got single argument:\"%s\". Expected \"<dbname>,<varname>\"",
871 		       cv_string_get(cvec_i(argv,0)));
872 	else
873 	    clicon_err(OE_PLUGIN, 0, " Got %d arguments. Expected: <dbname>,<varname>",
874 		       cvec_len(argv));
875 
876 	goto done;
877     }
878     dbstr = cv_string_get(cvec_i(argv, 0));
879     varstr  = cv_string_get(cvec_i(argv, 1));
880     if (strcmp(dbstr, "running") != 0 &&
881 	strcmp(dbstr, "candidate") != 0 &&
882 	strcmp(dbstr, "startup") != 0) {
883 	clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
884 	goto done;
885     }
886     if ((cv = cvec_find(cvv, varstr)) == NULL){
887 	clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
888 	goto done;
889     }
890     filename = cv_string_get(cv);
891     if (clicon_rpc_get_config(h, NULL, dbstr,"/", NULL, &xt) < 0)
892 	goto done;
893     if (xt == NULL){
894 	clicon_err(OE_CFG, 0, "get config: empty tree"); /* Shouldnt happen */
895 	goto done;
896     }
897     if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
898 	clixon_netconf_error(xerr, "Get configuration", NULL);
899 	goto done;
900     }
901     /* get-config returns a <data> tree. Save as <config> tree so it can be used
902      * as data-store.
903      */
904     if (xml_name_set(xt, "config") < 0)
905 	goto done;
906     if ((f = fopen(filename, "w")) == NULL){
907 	clicon_err(OE_CFG, errno, "Creating file %s", filename);
908 	goto done;
909     }
910     if (clicon_xml2file(f, xt, 0, 1) < 0)
911 	goto done;
912     retval = 0;
913     /* Fall through */
914   done:
915     if (xt)
916 	xml_free(xt);
917     if (f != NULL)
918 	fclose(f);
919     return retval;
920 }
921 
922 /*! Delete all elements in a database
923  * Utility function used by cligen spec file
924  */
925 int
delete_all(clicon_handle h,cvec * cvv,cvec * argv)926 delete_all(clicon_handle h,
927 	   cvec         *cvv,
928 	   cvec         *argv)
929 {
930     char            *dbstr;
931     int              retval = -1;
932 
933     if (cvec_len(argv) != 1){
934 	clicon_err(OE_PLUGIN, 0, "Requires one element: dbname");
935 	goto done;
936     }
937     dbstr = cv_string_get(cvec_i(argv, 0));
938     if (strcmp(dbstr, "running") != 0 &&
939 	strcmp(dbstr, "candidate") != 0 &&
940 	strcmp(dbstr, "startup") != 0){
941 	clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
942 	goto done;
943     }
944     if (clicon_rpc_delete_config(h, dbstr) < 0)
945 	goto done;
946     retval = 0;
947   done:
948     return retval;
949 }
950 
951 /*! Discard all changes in candidate and replace with running
952  */
953 int
discard_changes(clicon_handle h,cvec * cvv,cvec * argv)954 discard_changes(clicon_handle h,
955 		cvec         *cvv,
956 		cvec         *argv)
957 {
958     return clicon_rpc_discard_changes(h);
959 
960 }
961 /*! Copy from one database to another, eg running->startup
962  * @param[in] argv  a string: "<db1> <db2>" Copy from db1 to db2
963  */
964 int
db_copy(clicon_handle h,cvec * cvv,cvec * argv)965 db_copy(clicon_handle h,
966 	cvec         *cvv,
967 	cvec         *argv)
968 {
969     char *db1;
970     char *db2;
971 
972     db1 = cv_string_get(cvec_i(argv, 0));
973     db2 = cv_string_get(cvec_i(argv, 1));
974     return clicon_rpc_copy_config(h, db1, db2);
975 }
976 
977 /*! This is the callback used by cli_setlog to print log message in CLI
978  * param[in]  s    UNIX socket from backend  where message should be read
979  * param[in]  arg  format: txt, xml, xml2txt, xml2json
980  */
981 static int
cli_notification_cb(int s,void * arg)982 cli_notification_cb(int   s,
983 		    void *arg)
984 {
985     struct clicon_msg *reply = NULL;
986     int                eof;
987     int                retval = -1;
988     cxobj             *xt = NULL;
989     cxobj             *xe;
990     cxobj             *x;
991     enum format_enum   format = (enum format_enum)arg;
992     int                ret;
993 
994     /* get msg (this is the reason this function is called) */
995     if (clicon_msg_rcv(s, &reply, &eof) < 0)
996 	goto done;
997     if (eof){
998 	clicon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
999 	close(s);
1000 	errno = ESHUTDOWN;
1001 	clixon_event_unreg_fd(s, cli_notification_cb);
1002 	goto done;
1003     }
1004     /* XXX pass yang_spec and use xerr*/
1005     if ((ret = clicon_msg_decode(reply, NULL, NULL, &xt, NULL)) < 0)
1006 	goto done;
1007     if (ret == 0){ /* will not happen since no yspec ^*/
1008 	clicon_err(OE_NETCONF, EFAULT, "Notification malformed");
1009 	goto done;
1010     }
1011     if ((xe = xpath_first(xt, NULL, "//event")) != NULL){
1012 	x = NULL;
1013 	while ((x = xml_child_each(xe, x, -1)) != NULL) {
1014 	    switch (format){
1015 	    case FORMAT_XML:
1016 		if (clicon_xml2file_cb(stdout, x, 0, 1, cligen_output) < 0)
1017 		    goto done;
1018 		break;
1019 	    case FORMAT_TEXT:
1020 		if (xml2txt_cb(stdout, x, cligen_output) < 0)
1021 		    goto done;
1022 		break;
1023 	    case FORMAT_JSON:
1024 		if (xml2json_cb(stdout, x, 1, cligen_output) < 0)
1025 		    goto done;
1026 		break;
1027 	    default:
1028 		break;
1029 	    }
1030 	}
1031     }
1032     retval = 0;
1033   done:
1034     if (xt)
1035 	xml_free(xt);
1036     if (reply)
1037 	free(reply);
1038     return retval;
1039 }
1040 
1041 /*! Make a notify subscription to backend and un/register callback for return messages.
1042  *
1043  * @param[in] h      Clicon handle
1044  * @param[in] cvv    Not used
1045  * @param[in] arg    A string with <log stream name> <stream status> [<format>]
1046  * where <status> is "0" or "1"
1047  * and   <format> is XXX
1048  * Example code: Start logging of mystream and show logs as xml
1049  * @code
1050  * cmd("comment"), cli_notify("mystream","1","xml");
1051  * @endcode
1052  * XXX: format is a memory leak
1053  */
1054 int
cli_notify(clicon_handle h,cvec * cvv,cvec * argv)1055 cli_notify(clicon_handle h,
1056 	   cvec         *cvv,
1057 	   cvec         *argv)
1058 {
1059     char            *stream = NULL;
1060     int              retval = -1;
1061     int              status;
1062     char            *formatstr = NULL;
1063     enum format_enum format = FORMAT_TEXT;
1064 
1065     if (cvec_len(argv) != 2 && cvec_len(argv) != 3){
1066 	clicon_err(OE_PLUGIN, 0, "Requires arguments: <logstream> <status> [<format>]");
1067 	goto done;
1068     }
1069     stream = cv_string_get(cvec_i(argv, 0));
1070     status  = atoi(cv_string_get(cvec_i(argv, 1)));
1071     if (cvec_len(argv) > 2){
1072 	formatstr = cv_string_get(cvec_i(argv, 2));
1073 	format = format_str2int(formatstr);
1074     }
1075     if (cli_notification_register(h,
1076 				  stream,
1077 				  format,
1078 				  "",
1079 				  status,
1080 				  cli_notification_cb,
1081 				  (void*)format) < 0)
1082 	goto done;
1083 
1084     retval = 0;
1085   done:
1086     return retval;
1087 }
1088 
1089 /*! Lock database
1090  *
1091  * @param[in] h      Clicon handle
1092  * @param[in] cvv    Not used
1093  * @param[in] arg    A string with <database>
1094  * @code
1095  * lock("comment"), cli_lock("running");
1096  * @endcode
1097  * XXX: format is a memory leak
1098  */
1099 int
cli_lock(clicon_handle h,cvec * cvv,cvec * argv)1100 cli_lock(clicon_handle h,
1101 	 cvec         *cvv,
1102 	 cvec         *argv)
1103 {
1104     char            *db;
1105     int              retval = -1;
1106 
1107     if (cvec_len(argv) != 1){
1108 	clicon_err(OE_PLUGIN, 0, "Requires arguments: <db>");
1109 	goto done;
1110     }
1111     db = cv_string_get(cvec_i(argv, 0));
1112     if (clicon_rpc_lock(h, db) < 0)
1113 	goto done;
1114     retval = 0;
1115   done:
1116     return retval;
1117 }
1118 
1119 /*! Unlock database
1120  *
1121  * @param[in] h      Clicon handle
1122  * @param[in] cvv    Not used
1123  * @param[in] arg    A string with <database>
1124  * @code
1125  * lock("comment"), cli_lock("running");
1126  * @endcode
1127  * XXX: format is a memory leak
1128  */
1129 int
cli_unlock(clicon_handle h,cvec * cvv,cvec * argv)1130 cli_unlock(clicon_handle h,
1131 	   cvec         *cvv,
1132 	   cvec         *argv)
1133 {
1134     char            *db;
1135     int              retval = -1;
1136 
1137     if (cvec_len(argv) != 1){
1138 	clicon_err(OE_PLUGIN, 0, "Requires arguments: <db>");
1139 	goto done;
1140     }
1141     db = cv_string_get(cvec_i(argv, 0));
1142     if (clicon_rpc_unlock(h, db) < 0)
1143 	goto done;
1144     retval = 0;
1145   done:
1146     return retval;
1147 }
1148 
1149 /*! Copy one configuration object to antother
1150  *
1151  * Works for objects that are items ina yang list with a keyname, eg as:
1152  *   list sender{
1153  *      key name;
1154  *	leaf name{...
1155  *
1156  * @param[in]  h    CLICON handle
1157  * @param[in]  cvv  Vector of variables from CLIgen command-line
1158  * @param[in]  argv Vector: <db>, <xpath>, <field>, <fromvar>, <tovar>
1159  * Explanation of argv fields:
1160  *  db:        Database name, eg candidate|tmp|startup
1161  *  xpath:     XPATH expression with exactly two %s pointing to field and from name
1162  *  namespace: XPATH default namespace
1163  *  field:     Name of list key, eg name
1164  *  fromvar:   Name of variable containing name of object to copy from (given by xpath)
1165  *  tovar:     Name of variable containing name of object to copy to.
1166  * @code
1167  * cli spec:
1168  *  copy snd <n1:string> to <n2:string>, cli_copy_config("candidate", "/sender[%s='%s']", "urn:example:clixon", "from", "n1", "n2");
1169  * cli command:
1170  *  copy snd from to to
1171  * @endcode
1172  */
1173 int
cli_copy_config(clicon_handle h,cvec * cvv,cvec * argv)1174 cli_copy_config(clicon_handle h,
1175 		cvec         *cvv,
1176 		cvec         *argv)
1177 {
1178     int          retval = -1;
1179     char        *db;
1180     cxobj       *x1 = NULL;
1181     cxobj       *x2 = NULL;
1182     cxobj       *x;
1183     char        *xpath;
1184     char        *namespace;
1185     int          i;
1186     int          j;
1187     cbuf        *cb = NULL;
1188     char        *keyname;
1189     char        *fromvar;
1190     cg_var      *fromcv;
1191     char        *fromname = NULL;
1192     char        *tovar;
1193     cg_var      *tocv;
1194     char        *toname;
1195     cxobj       *xerr;
1196     cvec        *nsc = NULL;
1197 
1198     if (cvec_len(argv) != 6){
1199 	clicon_err(OE_PLUGIN, 0, "Requires 6 elements: <db> <xpath> <namespace> <keyname> <from> <to>");
1200 	goto done;
1201     }
1202     /* First argv argument: Database */
1203     db = cv_string_get(cvec_i(argv, 0));
1204     /* Second argv argument: xpath */
1205     xpath = cv_string_get(cvec_i(argv, 1));
1206     /* Third argv argument: namespace */
1207     namespace = cv_string_get(cvec_i(argv, 2));
1208     /* Third argv argument: name of keyname */
1209     keyname = cv_string_get(cvec_i(argv, 3));
1210     /* Fourth argv argument: from variable */
1211     fromvar = cv_string_get(cvec_i(argv, 4));
1212     /* Fifth argv argument: to variable */
1213     tovar = cv_string_get(cvec_i(argv, 5));
1214 
1215     /* Get from variable -> cv -> from name */
1216     if ((fromcv = cvec_find(cvv, fromvar)) == NULL){
1217 	clicon_err(OE_PLUGIN, 0, "fromvar '%s' not found in cligen var list", fromvar);
1218 	goto done;
1219     }
1220     /* Get from name from cv */
1221     fromname = cv_string_get(fromcv);
1222     /* Create xpath */
1223     if ((cb = cbuf_new()) == NULL){
1224 	clicon_err(OE_PLUGIN, errno, "cbuf_new");
1225 	goto done;
1226     }
1227     /* Sanity check that xpath contains exactly two %s, ie [%s='%s'] */
1228     j = 0;
1229     for (i=0; i<strlen(xpath); i++){
1230 	if (xpath[i] == '%')
1231 	    j++;
1232     }
1233     if (j != 2){
1234 	clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have two '%%'", xpath);
1235 	goto done;
1236     }
1237     cprintf(cb, xpath, keyname, fromname);
1238     if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
1239 	goto done;
1240     /* Get from object configuration and store in x1 */
1241     if (clicon_rpc_get_config(h, NULL, db, cbuf_get(cb), nsc, &x1) < 0)
1242 	goto done;
1243     if ((xerr = xpath_first(x1, NULL, "/rpc-error")) != NULL){
1244 	clixon_netconf_error(xerr, "Get configuration", NULL);
1245 	goto done;
1246     }
1247 
1248     /* Get to variable -> cv -> to name */
1249     if ((tocv = cvec_find(cvv, tovar)) == NULL){
1250 	clicon_err(OE_PLUGIN, 0, "tovar '%s' not found in cligen var list", tovar);
1251 	goto done;
1252     }
1253     toname = cv_string_get(tocv);
1254     /* Create copy xml tree x2 */
1255     if ((x2 = xml_new("config", NULL, CX_ELMNT)) == NULL)
1256 	goto done;
1257     if (xml_copy(x1, x2) < 0)
1258 	goto done;
1259     xml_name_set(x2, "config");
1260     cprintf(cb, "/%s", keyname);
1261 
1262     if ((x = xpath_first(x2, nsc, "%s", cbuf_get(cb))) == NULL){
1263 	clicon_err(OE_PLUGIN, 0, "Field %s not found in copy tree", keyname);
1264 	goto done;
1265     }
1266     x = xml_find(x, "body");
1267     xml_value_set(x, toname);
1268     /* resuse cb */
1269     cbuf_reset(cb);
1270     /* create xml copy tree and merge it with database configuration */
1271     clicon_xml2cbuf(cb, x2, 0, 0, -1);
1272     if (clicon_rpc_edit_config(h, db, OP_MERGE, cbuf_get(cb)) < 0)
1273 	goto done;
1274     retval = 0;
1275  done:
1276     if (nsc)
1277 	xml_nsctx_free(nsc);
1278     if (cb)
1279 	cbuf_free(cb);
1280     if (x1 != NULL)
1281 	xml_free(x1);
1282     if (x2 != NULL)
1283 	xml_free(x2);
1284     return retval;
1285 }
1286 
1287 
1288 /*! set debug level on stderr (not syslog).
1289  * The level is either what is specified in arg as int argument.
1290  * _or_ if a 'level' variable is present in vars use that value instead.
1291  * XXX obsolete. Use cli_debug_cliv or cli_debug_backendv instead
1292  */
1293 int
cli_debug(clicon_handle h,cvec * vars,cg_var * arg)1294 cli_debug(clicon_handle h,
1295 	  cvec         *vars,
1296 	  cg_var       *arg)
1297 {
1298     cg_var *cv;
1299     int     level;
1300 
1301     if ((cv = cvec_find(vars, "level")) == NULL)
1302 	cv = arg;
1303     level = cv_int32_get(cv);
1304     /* cli */
1305     clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */
1306     /* config daemon */
1307     if (clicon_rpc_debug(h, level) < 0)
1308 	goto done;
1309   done:
1310     return 0;
1311 }
1312 
1313 int
cli_help(clicon_handle h,cvec * vars,cvec * argv)1314 cli_help(clicon_handle h, cvec *vars, cvec *argv)
1315 {
1316     cligen_handle ch = cli_cligen(h);
1317     parse_tree   *pt;
1318 
1319     pt = cligen_ph_active_get(ch);
1320     return cligen_help(ch, stdout, pt);
1321 }
1322