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