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  * YANG module revision change management.
37  * See draft-wang-netmod-module-revision-management-01
38  */
39 
40 #ifdef HAVE_CONFIG_H
41 #include "clixon_config.h" /* generated by config & autoconf */
42 #endif
43 
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 #include <errno.h>
48 #include <string.h>
49 #include <limits.h>
50 #include <stdint.h>
51 #include <sys/types.h>
52 #include <sys/stat.h>
53 #include <sys/syslog.h>
54 #include <fcntl.h>
55 
56 /* cligen */
57 #include <cligen/cligen.h>
58 
59 /* clixon */
60 #include "clixon_queue.h"
61 #include "clixon_hash.h"
62 #include "clixon_string.h"
63 #include "clixon_err.h"
64 #include "clixon_handle.h"
65 #include "clixon_yang.h"
66 #include "clixon_log.h"
67 #include "clixon_xml.h"
68 #include "clixon_options.h"
69 #include "clixon_data.h"
70 #include "clixon_yang_module.h"
71 #include "clixon_yang_parse_lib.h"
72 #include "clixon_netconf_lib.h"
73 #include "clixon_xml_nsctx.h"
74 #include "clixon_xml_map.h"
75 #include "clixon_xml_io.h"
76 #include "clixon_validate.h"
77 #include "clixon_xml_changelog.h"
78 #include "clixon_xpath_ctx.h"
79 #include "clixon_xpath.h"
80 
81 static int
changelog_rename(clicon_handle h,cxobj * xt,cxobj * xw,cvec * nsc,char * tag)82 changelog_rename(clicon_handle h,
83 		 cxobj        *xt,
84 		 cxobj        *xw,
85 		 cvec         *nsc,
86 		 char         *tag)
87 {
88     int     retval = -1;
89     xp_ctx *xctx = NULL;
90     char   *str = NULL;
91 
92     if (tag == NULL){
93 	clicon_err(OE_XML, 0, "tag required");
94 	goto done;
95     }
96     if (xpath_vec_ctx(xw, nsc, tag, 0, &xctx) < 0)
97 	goto done;
98     if (ctx2string(xctx, &str) < 0)
99 	goto done;
100     if (!strlen(str)){
101 	clicon_err(OE_XML, 0, "invalid rename tag: \"%s\"", str);
102 	goto done;
103     }
104     if (xml_name_set(xw, str) < 0)
105 	goto done;
106     // ok:
107     retval = 1;
108  done:
109     if (xctx)
110 	ctx_free(xctx);
111     if (str)
112 	free(str);
113     return retval;
114     // fail:
115     retval = 0;
116     goto done;
117 }
118 
119 /* replace target XML */
120 static int
changelog_replace(clicon_handle h,cxobj * xt,cxobj * xw,cxobj * xnew)121 changelog_replace(clicon_handle h,
122 		  cxobj        *xt,
123 		  cxobj        *xw,
124 		  cxobj        *xnew)
125 {
126     int    retval = -1;
127     cxobj *x;
128 
129     /* create a new node by parsing fttransform string and insert it at
130        target */
131     if (xnew == NULL){
132 	clicon_err(OE_XML, 0, "new required");
133 	goto done;
134     }
135     /* replace: remove all children of target */
136     while ((x = xml_child_i(xw, 0)) != NULL)
137 	if (xml_purge(x) < 0)
138 	    goto done;
139     /* replace: first single node under <new> */
140     if (xml_child_nr(xnew) != 1){
141 	clicon_err(OE_XML, 0, "Single child to <new> required");
142 	goto done;
143     }
144     x = xml_child_i(xnew, 0);
145     /* Copy from xnew to (now) empty target */
146     if (xml_copy(x, xw) < 0)
147 	goto done;
148     retval = 1;
149  done:
150     return retval;
151 }
152 
153 /* create a new node by parsing "new" and insert it at
154    target */
155 static int
changelog_insert(clicon_handle h,cxobj * xt,cxobj * xw,cxobj * xnew)156 changelog_insert(clicon_handle h,
157 		 cxobj        *xt,
158 		 cxobj        *xw,
159 		 cxobj        *xnew)
160 {
161     int    retval = -1;
162     cxobj *x;
163 
164     if (xnew == NULL){
165 	clicon_err(OE_XML, 0, "new required");
166 	goto done;
167     }
168     /* replace: add all new children to target */
169     while ((x = xml_child_i(xnew, 0)) != NULL)
170 	if (xml_addsub(xw, x) < 0)
171 	    goto done;
172     // ok:
173     retval = 1;
174  done:
175     return retval;
176     // fail:
177     retval = 0;
178     goto done;
179 }
180 
181 /* delete target */
182 static int
changelog_delete(clicon_handle h,cxobj * xt,cxobj * xw)183 changelog_delete(clicon_handle h,
184 		 cxobj        *xt,
185 		 cxobj        *xw)
186 {
187     int retval = -1;
188 
189     if (xml_purge(xw) < 0)
190 	goto done;
191     retval = 1;
192  done:
193     return retval;
194 }
195 
196 /* Move target node to location */
197 static int
changelog_move(clicon_handle h,cxobj * xt,cxobj * xw,cvec * nsc,char * dst)198 changelog_move(clicon_handle h,
199 	       cxobj        *xt,
200 	       cxobj        *xw,
201 	       cvec         *nsc,
202 	       char         *dst)
203 {
204     int    retval = -1;
205     cxobj *xp;       /* destination parent node */
206 
207     if ((xp = xpath_first(xt, nsc, "%s", dst)) == NULL){
208 	clicon_err(OE_XML, 0, "path required");
209 	goto done;
210     }
211     if (xml_addsub(xp, xw) < 0)
212 	goto done;
213     retval = 1;
214  done:
215     return retval;
216 }
217 
218 /*! Perform a changelog operation
219  * @param[in]  h   Clicon handle
220  * @param[in]  xt  XML to upgrade
221  * @param[in]  xi  Changelog item
222 
223  * @note XXX error handling!
224  * @note XXX xn --> xt  xpath may not match
225 */
226 static int
changelog_op(clicon_handle h,cxobj * xt,cxobj * xi)227 changelog_op(clicon_handle h,
228 	     cxobj        *xt,
229 	     cxobj        *xi)
230 
231 {
232     int     retval = -1;
233     char   *op;
234     char   *whenxpath;   /* xpath to when */
235     char   *tag;         /* xpath to extra path (move) */
236     char   *dst;         /* xpath to extra path (move) */
237     cxobj  *xnew;        /* new xml (insert, replace) */
238     char   *wxpath;      /* xpath to where (target-node) */
239     cxobj **wvec = NULL; /* Vector of where(target) nodes */
240     size_t  wlen;
241     cxobj  *xw;
242     int     ret;
243     xp_ctx *xctx = NULL;
244     int     i;
245     cvec   *nsc = NULL;
246 
247     /* Get namespace context from changelog item */
248     if (xml_nsctx_node(xi, &nsc) < 0)
249 	goto done;
250     if ((op = xml_find_body(xi, "op")) == NULL)
251 	goto ok;
252     /* get common variables that may be used in the operations below */
253     tag = xml_find_body(xi, "tag");
254     dst = xml_find_body(xi, "dst");
255     xnew = xml_find(xi, "new");
256     whenxpath = xml_find_body(xi, "when");
257     if ((wxpath = xml_find_body(xi, "where")) == NULL)
258 	goto ok;
259     /* Get vector of target nodes meeting the where requirement */
260     if (xpath_vec(xt, nsc, "%s", &wvec, &wlen, wxpath) < 0)
261        goto done;
262    for (i=0; i<wlen; i++){
263        xw = wvec[i];
264        /* If 'when' exists and is false, skip this target */
265        if (whenxpath){
266 	   if (xpath_vec_ctx(xw, nsc, whenxpath, 0, &xctx) < 0)
267 	       goto done;
268 	   if ((ret = ctx2boolean(xctx)) < 0)
269 	       goto done;
270 	   if (xctx){
271 	       ctx_free(xctx);
272 	       xctx = NULL;
273 	   }
274 	   if (ret == 0)
275 	       continue;
276        }
277        /* Now switch on operation */
278        if (strcmp(op, "rename") == 0){
279 	   ret = changelog_rename(h, xt, xw, nsc, tag);
280        }
281        else if (strcmp(op, "replace") == 0){
282 	   ret = changelog_replace(h, xt, xw, xnew);
283        }
284        else if (strcmp(op, "insert") == 0){
285 	   ret = changelog_insert(h, xt, xw, xnew);
286        }
287        else if (strcmp(op, "delete") == 0){
288 	   ret = changelog_delete(h, xt, xw);
289        }
290        else if (strcmp(op, "move") == 0){
291 	   ret = changelog_move(h, xt, xw, nsc, dst);
292        }
293        else{
294 	   clicon_err(OE_XML, 0, "Unknown operation: %s", op);
295 	   goto done;
296        }
297        if (ret < 0)
298 	   goto done;
299        if (ret == 0)
300 	   goto fail;
301    }
302  ok:
303     retval = 1;
304  done:
305     if (nsc)
306 	xml_nsctx_free(nsc);
307     if (wvec)
308 	free(wvec);
309     if (xctx)
310 	ctx_free(xctx);
311     return retval;
312  fail:
313     retval = 0;
314     clicon_debug(1, "%s fail op:%s ", __FUNCTION__, op);
315     goto done;
316 }
317 
318 /*! Iterate through one changelog item
319  * @param[in]  h   Clicon handle
320  * @param[in]  xt  Changelog list
321  * @param[in]  xn  XML to upgrade
322  */
323 static int
changelog_iterate(clicon_handle h,cxobj * xt,cxobj * xch)324 changelog_iterate(clicon_handle h,
325     		  cxobj        *xt,
326 		  cxobj        *xch)
327 
328 {
329     int        retval = -1;
330     cxobj    **vec = NULL;
331     size_t     veclen;
332     int        ret;
333     int        i;
334 
335     if (xpath_vec(xch, NULL, "step", &vec, &veclen) < 0)
336 	goto done;
337     /* Iterate through changelog items */
338     for (i=0; i<veclen; i++){
339 	if ((ret = changelog_op(h, xt, vec[i])) < 0)
340 	    goto done;
341 	if (ret == 0)
342 	    goto fail;
343     }
344     retval = 1;
345  done:
346     clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
347     if (vec)
348 	free(vec);
349     return retval;
350  fail:
351     retval = 0;
352     goto done;
353 }
354 
355 /*! Automatic upgrade using changelog
356  * @param[in]  h       Clicon handle
357  * @param[in]  xt      Top-level XML tree to be updated (includes other ns as well)
358  * @param[in]  ns      Namespace of module (for info)
359  * @param[in]  op      One of XML_FLAG_ADD, _DEL, _CHANGE
360  * @param[in]  from    From revision on the form YYYYMMDD
361  * @param[in]  to      To revision on the form YYYYMMDD (0 not in system)
362  * @param[in]  arg     User argument given at rpc_callback_register()
363  * @param[out] cbret   Return xml tree, eg <rpc-reply>..., <rpc-error..
364  * @retval     1       OK
365  * @retval     0       Invalid
366  * @retval    -1       Error
367  * @see upgrade_callback_register  where this function should be registered
368  */
369 int
xml_changelog_upgrade(clicon_handle h,cxobj * xt,char * ns,uint16_t op,uint32_t from,uint32_t to,void * arg,cbuf * cbret)370 xml_changelog_upgrade(clicon_handle h,
371 		      cxobj        *xt,
372 		      char         *ns,
373 		      uint16_t      op,
374 		      uint32_t      from,
375 		      uint32_t      to,
376 		      void         *arg,
377 		      cbuf         *cbret)
378 {
379     int        retval = -1;
380     cxobj     *xchlog; /* changelog */
381     cxobj    **vec = NULL;
382     cxobj     *xch;
383     size_t     veclen;
384     char      *b;
385     int        ret;
386     int        i;
387     uint32_t   f;
388     uint32_t   t;
389 
390     /* Check if changelog enabled */
391     if (!clicon_option_bool(h, "CLICON_XML_CHANGELOG"))
392 	goto ok;
393     /* Get changelog */
394     if ((xchlog = clicon_xml_changelog_get(h)) == NULL)
395 	goto ok;
396 
397     /* Iterate and find relevant changelog entries in the interval:
398      * - find all changelogs in the interval: [from, to]
399      * - note it t=0 then no changelog is applied
400      */
401     if (xpath_vec(xchlog, NULL, "changelog[namespace=\"%s\"]",
402 		  &vec, &veclen, ns) < 0)
403 	goto done;
404     /* Get all changelogs in the interval [from,to]*/
405     for (i=0; i<veclen; i++){
406 	xch = vec[i];
407 	f = t = 0;
408 	if ((b = xml_find_body(xch, "revfrom")) != NULL)
409 	    if (ys_parse_date_arg(b, &f) < 0)
410 		goto done;
411 	if ((b = xml_find_body(xch, "revision")) != NULL)
412 	    if (ys_parse_date_arg(b, &t) < 0)
413 		goto done;
414 	if ((f && from>f) || to<t)
415 	    continue;
416 	if ((ret = changelog_iterate(h, xt, xch)) < 0)
417 	    goto done;
418 	if (ret == 0)
419 	    goto fail;
420     }
421  ok:
422     retval = 1;
423  done:
424     if (vec)
425 	free(vec);
426     return retval;
427  fail:
428     retval = 0;
429     goto done;
430 }
431 
432 /*! Initialize module revision. read changelog, etc
433  */
434 int
clixon_xml_changelog_init(clicon_handle h)435 clixon_xml_changelog_init(clicon_handle h)
436 {
437     int        retval = -1;
438     char      *filename;
439     int        fd = -1;
440     cxobj     *xt = NULL;
441     yang_stmt *yspec;
442     int        ret;
443     cxobj     *xret = NULL;
444     cbuf      *cbret = NULL;
445 
446     yspec = clicon_dbspec_yang(h);
447     if ((filename = clicon_option_str(h, "CLICON_XML_CHANGELOG_FILE")) != NULL){
448 	if ((fd = open(filename, O_RDONLY)) < 0){
449 	    clicon_err(OE_UNIX, errno, "open(%s)", filename);
450 	    goto done;
451 	}
452 	if (clixon_xml_parse_file(fd, YB_MODULE, yspec, NULL, &xt, NULL) < 0)
453 	    goto done;
454 	if (xml_rootchild(xt, 0, &xt) < 0)
455 	    goto done;
456 	if ((ret = xml_yang_validate_all(h, xt, &xret)) < 0)
457 	    goto done;
458 	if (ret==1 && (ret = xml_yang_validate_add(h, xt, &xret)) < 0)
459 	    goto done;
460 	if (ret == 0){ /* validation failed */
461 	    if ((cbret = cbuf_new()) ==NULL){
462 		clicon_err(OE_XML, errno, "cbuf_new");
463 		goto done;
464 	    }
465 	    if (netconf_err2cb(xret, cbret) < 0)
466 		goto done;
467 	    clicon_err(OE_YANG, 0, "validation failed: %s", cbuf_get(cbret));
468 	    goto done;
469 	}
470 	if (clicon_xml_changelog_set(h, xt) < 0)
471 	    goto done;
472 	xt = NULL;
473     }
474     retval = 0;
475  done:
476     if (cbret)
477 	cbuf_free(cbret);
478     if (xret)
479 	xml_free(xret);
480     if (fd != -1)
481 	close(fd);
482     if (xt)
483 	xml_free(xt);
484     return retval;
485 }
486 
487 /*! Given a top-level XML tree and a namespace, return a vector of matching XML nodes
488  * @param[in]  h         Clicon handle
489  * @param[in]  xt        Top-level XML tree, with children marked with namespaces
490  * @param[in]  ns        The namespace to select
491  * @param[out] vecp      Vector containining XML nodes w namespace. Null-terminated.
492  * @param[out] veclenp   Length of vector
493  * @note  Need to free vec after use with free()
494  * Example
495  *   xt ::= <config><a xmlns="urn:example:a"/><aaa xmlns="urn:example:a"/><a xmlns="urn:example:b"/></config
496  *   namespace ::= urn:example:a
497  * out:
498  *   vec ::= [<a xmlns="urn:example:a"/>, <aaa xmlns="urn:example:a"/>, NULL]
499  */
500 int
xml_namespace_vec(clicon_handle h,cxobj * xt,char * ns,cxobj *** vecp,size_t * veclenp)501 xml_namespace_vec(clicon_handle h,
502 		  cxobj        *xt,
503 		  char         *ns,
504 		  cxobj      ***vecp,
505 		  size_t       *veclenp)
506 {
507     int     retval = -1;
508     cxobj **xvec = NULL;
509     size_t  xlen;
510     cxobj  *xc;
511     char   *ns0;
512     int     i;
513 
514     /* Allocate upper bound on length (ie could be too large) + a NULL element
515      * (event though we use veclen)
516      */
517     xlen = xml_child_nr_type(xt, CX_ELMNT)+1;
518     if ((xvec = calloc(xlen, sizeof(cxobj*))) == NULL){
519 	clicon_err(OE_UNIX, errno, "calloc");
520 	goto done;
521     }
522     /* Iterate and find xml nodes with assoctaed namespace */
523     xc = NULL;
524     i = 0;
525     while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
526 	if (xml2ns(xc, NULL, &ns0) < 0) /* Get namespace of XML */
527 	    goto done;
528 	if (strcmp(ns, ns0))
529 	    continue; /* no match */
530 	xvec[i++] = xc;
531     }
532     *vecp = xvec;
533     *veclenp = i;
534     retval = 0;
535  done:
536     return retval;
537 }
538