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 #ifdef HAVE_CONFIG_H
38 #include "clixon_config.h" /* generated by config & autoconf */
39 #endif
40 
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <stdint.h>
44 #include <string.h>
45 #include <errno.h>
46 #include <dlfcn.h>
47 #include <dirent.h>
48 #include <syslog.h>
49 
50 #include <sys/stat.h>
51 #include <sys/param.h>
52 
53 /* cligen */
54 #include <cligen/cligen.h>
55 
56 #include "clixon_err.h"
57 #include "clixon_queue.h"
58 #include "clixon_hash.h"
59 #include "clixon_log.h"
60 #include "clixon_file.h"
61 #include "clixon_handle.h"
62 #include "clixon_yang.h"
63 #include "clixon_xml.h"
64 #include "clixon_xml_nsctx.h"
65 #include "clixon_yang_module.h"
66 #include "clixon_plugin.h"
67 
68 /* List of plugins XXX
69  * 1. Place in clixon handle not global variables
70  * 2. Use qelem circular lists
71  */
72 static clixon_plugin *_clixon_plugins = NULL;  /* List of plugins (of client) */
73 static int            _clixon_nplugins = 0;  /* Number of plugins */
74 
75 /*! Iterator over clixon plugins
76  *
77  * @note Never manipulate the plugin during operation or using the
78  * same object recursively
79  *
80  * @param[in]  h       Clicon handle
81  * @param[in] plugin   previous plugin, or NULL on init
82  * @code
83  *   clicon_plugin *cp = NULL;
84  *   while ((cp = clixon_plugin_each(h, cp)) != NULL) {
85  *     ...
86  *   }
87  * @endcode
88  * @note Not optimized, alwasy iterates from the start of the list
89  */
90 clixon_plugin *
clixon_plugin_each(clicon_handle h,clixon_plugin * cpprev)91 clixon_plugin_each(clicon_handle  h,
92 		   clixon_plugin *cpprev)
93 {
94     int            i;
95     clixon_plugin *cp;
96     clixon_plugin *cpnext = NULL;
97 
98     if (cpprev == NULL)
99 	cpnext = _clixon_plugins;
100     else{
101 	for (i = 0; i < _clixon_nplugins; i++) {
102 	    cp = &_clixon_plugins[i];
103 	    if (cp == cpprev)
104 		break;
105 	    cp = NULL;
106 	}
107 	if (cp && i < _clixon_nplugins-1)
108 	    cpnext = &_clixon_plugins[i+1];
109     }
110     return cpnext;
111 }
112 
113 /*! Reverse iterator over clixon plugins, iterater from nr to 0
114  *
115  * @note Never manipulate the plugin during operation or using the
116  * same object recursively
117  *
118  * @param[in]  h       Clicon handle
119  * @param[in] plugin   previous plugin, or NULL on init
120  * @code
121  *   clicon_plugin *cp = NULL;
122  *   while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) {
123  *     ...
124  *   }
125  * @endcode
126  * @note Not optimized, alwasy iterates from the start of the list
127  */
128 clixon_plugin *
clixon_plugin_each_revert(clicon_handle h,clixon_plugin * cpprev,int nr)129 clixon_plugin_each_revert(clicon_handle  h,
130 			  clixon_plugin *cpprev,
131 			  int            nr)
132 {
133     int            i;
134     clixon_plugin *cp = NULL;
135     clixon_plugin *cpnext = NULL;
136 
137     if (cpprev == NULL){
138 	if (nr>0)
139 	    cpnext = &_clixon_plugins[nr-1];
140     }
141     else{
142 	for (i = nr-1; i >= 0; i--) {
143 	    cp = &_clixon_plugins[i];
144 	    if (cp == cpprev)
145 		break;
146 	    cp = NULL;
147 	}
148 	if (cp && i > 0)
149 	    cpnext = &_clixon_plugins[i-1];
150     }
151     return cpnext;
152 }
153 
154 /*! Find plugin by name
155  * @param[in]  h    Clicon handle
156  * @param[in]  name Plugin name
157  * @retval     p    Plugin if found
158  * @retval     NULL Not found
159  * @code
160  *    clixon_plugin *cp;
161  *    cp = clixon_plugin_find(h, "plugin-name");
162  * @endcode
163  */
164 clixon_plugin *
clixon_plugin_find(clicon_handle h,const char * name)165 clixon_plugin_find(clicon_handle h,
166 		   const char   *name)
167 {
168     int            i;
169     clixon_plugin *cp = NULL;
170 
171     for (i = 0; i < _clixon_nplugins; i++) {
172 	cp = &_clixon_plugins[i];
173 	if (strcmp(cp->cp_name, name) == 0)
174 	    return cp;
175     }
176     return NULL;
177 }
178 
179 /*! Load a dynamic plugin object and call its init-function
180  * @param[in]  h        Clicon handle
181  * @param[in]  file     Which plugin to load
182  * @param[in]  function Which function symbol to load and call
183  * @param[in]  dlflags  See man(3) dlopen
184  * @param[out] cpp      Clixon plugin structure (if retval is 1)
185  * @retval     1        OK
186  * @retval     0        Failed load, log, skip and continue with other plugins
187  * @retval    -1        Error
188  * @see clixon_plugins_load  Load all plugins
189  */
190 static int
plugin_load_one(clicon_handle h,char * file,const char * function,int dlflags,clixon_plugin ** cpp)191 plugin_load_one(clicon_handle   h,
192 		char           *file, /* note modified */
193 		const char     *function,
194 		int             dlflags,
195 		clixon_plugin **cpp)
196 {
197     int                retval = -1;
198     char              *error;
199     void              *handle = NULL;
200     plginit2_t        *initfn;
201     clixon_plugin_api *api = NULL;
202     clixon_plugin     *cp = NULL;
203     char              *name;
204     char              *p;
205 
206     clicon_debug(1, "%s file:%s function:%s", __FUNCTION__, file, function);
207     dlerror();    /* Clear any existing error */
208     if ((handle = dlopen(file, dlflags)) == NULL) {
209         error = (char*)dlerror();
210 	clicon_err(OE_PLUGIN, errno, "dlopen: %s", error ? error : "Unknown error");
211 	goto done;
212     }
213     /* call plugin_init() if defined, eg CLIXON_PLUGIN_INIT or CLIXON_BACKEND_INIT */
214     if ((initfn = dlsym(handle, function)) == NULL){
215 	clicon_err(OE_PLUGIN, errno, "Failed to find %s when loading clixon plugin %s", CLIXON_PLUGIN_INIT, file);
216 	goto done;
217     }
218     if ((error = (char*)dlerror()) != NULL) {
219 	clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error);
220 	goto done;
221     }
222     clicon_err_reset();
223     if ((api = initfn(h)) == NULL) {
224 	if (!clicon_errno){ 	/* if clicon_err() is not called then log and continue */
225 	    clicon_log(LOG_DEBUG, "Warning: failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
226 	    retval = 0;
227 	    goto done;
228 	}
229 	else{
230 	    clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
231 	    goto done;
232 	}
233     }
234     /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */
235     if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){
236 	clicon_err(OE_UNIX, errno, "malloc");
237 	goto done;
238     }
239     memset(cp, 0, sizeof(struct clixon_plugin));
240     cp->cp_handle = handle;
241     /* Extract string after last '/' in filename, if any */
242     name = strrchr(file, '/') ? strrchr(file, '/')+1 : file;
243     /* strip extension, eg .so from name */
244     if ((p=strrchr(name, '.')) != NULL)
245 	*p = '\0';
246     /* Copy name to struct */
247     snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s",
248 	     (int)strlen(name), name);
249     cp->cp_api = *api;
250     clicon_debug(1, "%s", __FUNCTION__);
251     if (cp){
252 	*cpp = cp;
253 	cp = NULL;
254     }
255     retval = 1;
256  done:
257     if (retval != 1 && handle)
258 	dlclose(handle);
259     if (cp)
260 	free(cp);
261     return retval;
262 }
263 
264 /*! Load a set of plugin objects from a directory and and call their init-function
265  * @param[in]  h     Clicon handle
266  * @param[in]  function Which function symbol to load and call (eg CLIXON_PLUGIN_INIT)
267  * @param[in]  dir   Directory. .so files in this dir will be loaded.
268  * @param[in]  regexp Regexp for matching files in plugin directory. Default *.so.
269  * @retval     0     OK
270  * @retval     -1    Error
271  */
272 int
clixon_plugins_load(clicon_handle h,const char * function,const char * dir,const char * regexp)273 clixon_plugins_load(clicon_handle h,
274     		    const char   *function,
275 		    const char   *dir,
276     		    const char   *regexp)
277 {
278     int            retval = -1;
279     int            ndp;
280     struct dirent *dp = NULL;
281     int            i;
282     char           filename[MAXPATHLEN];
283     clixon_plugin *cp = NULL;
284     int            ret;
285 
286     clicon_debug(1, "%s", __FUNCTION__);
287     /* Get plugin objects names from plugin directory */
288     if((ndp = clicon_file_dirent(dir, &dp, regexp?regexp:"(.so)$", S_IFREG)) < 0)
289 	goto done;
290     /* Load all plugins */
291     for (i = 0; i < ndp; i++) {
292 	snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
293 	clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...",
294 		     (int)strlen(filename), filename);
295 	if ((ret = plugin_load_one(h, filename, function, RTLD_NOW, &cp)) < 0)
296 	    goto done;
297 	if (ret == 0)
298 	    continue;
299 	_clixon_nplugins++;
300 	if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) {
301 	    clicon_err(OE_UNIX, errno, "realloc");
302 	    goto done;
303 	}
304 	_clixon_plugins[_clixon_nplugins-1] = *cp;
305 	free(cp);
306     }
307     retval = 0;
308 done:
309     if (dp)
310 	free(dp);
311     return retval;
312 }
313 
314 /*! Create a pseudo plugin so that a main function can register callbacks
315  * @param[in]  h     Clicon handle
316  * @param[in]  name  Plugin name
317  * @param[out] cpp   Clixon plugin structure (direct pointer)
318  * @retval     0     OK, with cpp set
319  * @retval    -1     Error
320  */
321 int
clixon_pseudo_plugin(clicon_handle h,const char * name,clixon_plugin ** cpp)322 clixon_pseudo_plugin(clicon_handle   h,
323 		     const char     *name,
324 		     clixon_plugin **cpp)
325 {
326     int            retval = -1;
327     clixon_plugin *cp = NULL;
328 
329     clicon_debug(1, "%s", __FUNCTION__);
330 
331     /* Create a pseudo plugins */
332     /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */
333     if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){
334 	clicon_err(OE_UNIX, errno, "malloc");
335 	goto done;
336     }
337     memset(cp, 0, sizeof(struct clixon_plugin));
338     snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", (int)strlen(name), name);
339 
340     _clixon_nplugins++;
341     if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) {
342 	clicon_err(OE_UNIX, errno, "realloc");
343 	goto done;
344     }
345     _clixon_plugins[_clixon_nplugins-1] = *cp;
346     *cpp = &_clixon_plugins[_clixon_nplugins-1];
347 
348     retval = 0;
349 done:
350     if (cp)
351 	free(cp);
352     return retval;
353 }
354 
355 /*! Call single plugin start callback
356  * @param[in]  cp      Plugin handle
357  * @param[in]  h       Clixon handle
358  * @retval     0       OK
359  * @retval    -1       Error
360  */
361 int
clixon_plugin_start_one(clixon_plugin * cp,clicon_handle h)362 clixon_plugin_start_one(clixon_plugin *cp,
363 			clicon_handle  h)
364 {
365     int          retval = -1;
366     plgstart_t  *fn;          /* Plugin start */
367 
368     if ((fn = cp->cp_api.ca_start) != NULL){
369 	if (fn(h) < 0) {
370 	    if (clicon_errno < 0)
371 		clicon_log(LOG_WARNING, "%s: Internal error: Start callback in plugin: %s returned -1 but did not make a clicon_err call",
372 			   __FUNCTION__, cp->cp_name);
373 	    goto done;
374 	}
375     }
376     retval = 0;
377  done:
378     return retval;
379 }
380 
381 /*! Call plugin_start in all plugins
382  * @param[in]  h       Clixon handle
383  * Call plugin start functions (if defined)
384  * @note  Start functions can use clicon_argv_get() to get -- command line options
385  */
386 int
clixon_plugin_start_all(clicon_handle h)387 clixon_plugin_start_all(clicon_handle h)
388 {
389     int            retval = -1;
390     clixon_plugin *cp = NULL;
391 
392     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
393 	if (clixon_plugin_start_one(cp, h) < 0)
394 	    goto done;
395     }
396     retval = 0;
397  done:
398     return retval;
399 }
400 
401 /*! Unload all plugins: call exit function and close shared handle
402  * @param[in]  h       Clicon handle
403  * @param[in]  cp      Plugin handle
404  * @param[in]  h       Clixon handle
405  * @retval     0       OK
406  * @retval    -1       Error
407  */
408 int
clixon_plugin_exit_one(clixon_plugin * cp,clicon_handle h)409 clixon_plugin_exit_one(clixon_plugin *cp,
410 		       clicon_handle  h)
411 {
412     int          retval = -1;
413     char        *error;
414     plgexit_t   *fn;
415 
416     if ((fn = cp->cp_api.ca_exit) != NULL){
417 	if (fn(h) < 0) {
418 	    if (clicon_errno < 0)
419 		clicon_log(LOG_WARNING, "%s: Internal error: Exit callback in plugin: %s returned -1 but did not make a clicon_err call",
420 			   __FUNCTION__, cp->cp_name);
421 	    goto done;
422 	}
423 	if (dlclose(cp->cp_handle) != 0) {
424 	    error = (char*)dlerror();
425 	    clicon_err(OE_PLUGIN, errno, "dlclose: %s", error ? error : "Unknown error");
426 	}
427     }
428     retval = 0;
429  done:
430     return retval;
431 }
432 
433 /*! Unload all plugins: call exit function and close shared handle
434  * @param[in]  h       Clixon handle
435  * @retval     0       OK
436  * @retval    -1       Error
437  */
438 int
clixon_plugin_exit_all(clicon_handle h)439 clixon_plugin_exit_all(clicon_handle h)
440 {
441     int            retval = -1;
442     clixon_plugin *cp = NULL;
443 
444     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
445 	if (clixon_plugin_exit_one(cp, h) < 0)
446 	    goto done;
447     }
448     if (_clixon_plugins){
449 	free(_clixon_plugins);
450 	_clixon_plugins = NULL;
451     }
452     _clixon_nplugins = 0;
453     retval = 0;
454  done:
455     return retval;
456 }
457 
458 /*! Run the restconf user-defined credentials callback
459  * @param[in]  cp   Plugin handle
460  * @param[in]  h    Clicon handle
461  * @param[in]  arg  Argument, such as fastcgi handler for restconf
462  * @retval    -1    Error
463  * @retval     0    Not authenticated
464  * @retval     1    Authenticated
465  * @note If authenticated either a callback was called and clicon_username_set()
466  *       Or no callback was found.
467  */
468 int
clixon_plugin_auth_one(clixon_plugin * cp,clicon_handle h,void * arg)469 clixon_plugin_auth_one(clixon_plugin *cp,
470 		       clicon_handle h,
471 		       void         *arg)
472 {
473     int        retval = 1;  /* Authenticated */
474     plgauth_t *fn;          /* Plugin auth */
475 
476     if ((fn = cp->cp_api.ca_auth) != NULL){
477 	if ((retval = fn(h, arg)) < 0) {
478 	    if (clicon_errno < 0)
479 		clicon_log(LOG_WARNING, "%s: Internal error: Auth callback in plugin: %s returned -1 but did not make a clicon_err call",
480 			   __FUNCTION__, cp->cp_name);
481 	    goto done;
482 	}
483     }
484  done:
485     return retval;
486 }
487 
488 /*! Run the restconf user-defined credentials callback for all plugins
489  * Find first authentication callback and call that, then return.
490  * The callback is to set the authenticated user
491  * @param[in]  cp      Plugin handle
492  * @param[in]  h    Clicon handle
493  * @param[in]  arg  Argument, such as fastcgi handler for restconf
494  * @retval    -1    Error
495  * @retval     0    Not authenticated
496  * @retval     1    Authenticated
497  * @note If authenticated either a callback was called and clicon_username_set()
498  *       Or no callback was found.
499  */
500 int
clixon_plugin_auth_all(clicon_handle h,void * arg)501 clixon_plugin_auth_all(clicon_handle h,
502 		       void         *arg)
503 {
504     int            retval = -1;
505     clixon_plugin *cp = NULL;
506     int            i = 0;
507     int            ret;
508 
509     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
510 	i++;
511 	if ((ret = clixon_plugin_auth_one(cp, h, arg)) < 0)
512 	    goto done;
513 	if (ret == 1)
514 	    goto authenticated;
515 	break;
516     }
517     if (i==0)
518 	retval = 1;
519     else
520 	retval = 0;
521  done:
522     return retval;
523  authenticated:
524     retval = 1;
525     goto done;
526 }
527 
528 /*! Callback for a yang extension (unknown) statement single plugin
529  * extension can be made.
530  * @param[in] cp   Plugin handle
531  * @param[in] h    Clixon handle
532  * @param[in] yext Yang node of extension
533  * @param[in] ys   Yang node of (unknown) statement belonging to extension
534  * @retval    0    OK,
535  * @retval   -1    Error
536  */
537 int
clixon_plugin_extension_one(clixon_plugin * cp,clicon_handle h,yang_stmt * yext,yang_stmt * ys)538 clixon_plugin_extension_one(clixon_plugin *cp,
539 			    clicon_handle  h,
540 			    yang_stmt     *yext,
541 			    yang_stmt     *ys)
542 {
543     int             retval = 1;
544     plgextension_t *fn;          /* Plugin extension fn */
545 
546     if ((fn = cp->cp_api.ca_extension) != NULL){
547 	if (fn(h, yext, ys) < 0) {
548 	    if (clicon_errno < 0)
549 		clicon_log(LOG_WARNING, "%s: Internal error: Extension callback in plugin: %s returned -1 but did not make a clicon_err call",
550 			   __FUNCTION__, cp->cp_name);
551 	    goto done;
552 	}
553     }
554     retval = 0;
555  done:
556     return retval;
557 }
558 
559 /*! Callback for a yang extension (unknown) statement in all plugins
560  * Called at parsing of yang module containing a statement of an extension.
561  * A plugin may identify the extension and perform actions
562  * on the yang statement, such as transforming the yang.
563  * A callback is made for every statement, which means that several calls per
564  * extension can be made.
565  * @param[in] h    Clixon handle
566  * @param[in] yext Yang node of extension
567  * @param[in] ys   Yang node of (unknown) statement belonging to extension
568  * @retval     0   OK, all callbacks executed OK
569  * @retval    -1   Error in one callback
570  */
571 int
clixon_plugin_extension_all(clicon_handle h,yang_stmt * yext,yang_stmt * ys)572 clixon_plugin_extension_all(clicon_handle h,
573 			    yang_stmt    *yext,
574 			    yang_stmt    *ys)
575 {
576     int            retval = -1;
577     clixon_plugin *cp = NULL;
578 
579     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
580 	if (clixon_plugin_extension_one(cp, h, yext, ys) < 0)
581 	    goto done;
582     }
583     retval = 0;
584  done:
585     return retval;
586 }
587 
588 /*! Call plugin general-purpose datastore upgrade in one plugin
589  *
590  * @param[in] cp   Plugin handle
591  * @param[in] h    Clicon handle
592  * @param[in] db   Name of datastore, eg "running", "startup" or "tmp"
593  * @param[in] xt   XML tree. Upgrade this "in place"
594  * @param[in] msd  Module-state diff, info on datastore module-state
595  * @retval   -1    Error
596  * @retval    0    OK
597  * Upgrade datastore on load before or as an alternative to module-specific upgrading mechanism
598  */
599 int
clixon_plugin_datastore_upgrade_one(clixon_plugin * cp,clicon_handle h,const char * db,cxobj * xt,modstate_diff_t * msd)600 clixon_plugin_datastore_upgrade_one(clixon_plugin   *cp,
601 				    clicon_handle    h,
602 				    const char      *db,
603 				    cxobj           *xt,
604 				    modstate_diff_t *msd)
605 
606 {
607     int                  retval = -1;
608     datastore_upgrade_t *fn;
609 
610     if ((fn = cp->cp_api.ca_datastore_upgrade) != NULL){
611 	if (fn(h, db, xt, msd) < 0) {
612 	    if (clicon_errno < 0)
613 		clicon_log(LOG_WARNING, "%s: Internal error: Datastore upgrade callback in plugin: %s returned -1 but did not make a clicon_err call",
614 			   __FUNCTION__, cp->cp_name);
615 	    goto done;
616 	}
617     }
618     retval = 0;
619  done:
620     return retval;
621 }
622 
623 /*! Call plugin general-purpose datastore upgrade in all plugins
624  *
625  * @param[in] h    Clicon handle
626  * @param[in] db   Name of datastore, eg "running", "startup" or "tmp"
627  * @param[in] xt   XML tree. Upgrade this "in place"
628  * @param[in] msd  Module-state diff, info on datastore module-state
629  * @retval   -1    Error
630  * @retval    0    OK
631  * Upgrade datastore on load before or as an alternative to module-specific upgrading mechanism
632  */
633 int
clixon_plugin_datastore_upgrade_all(clicon_handle h,const char * db,cxobj * xt,modstate_diff_t * msd)634 clixon_plugin_datastore_upgrade_all(clicon_handle    h,
635 				    const char      *db,
636 				    cxobj           *xt,
637 				    modstate_diff_t *msd)
638 {
639     int            retval = -1;
640     clixon_plugin *cp = NULL;
641 
642     while ((cp = clixon_plugin_each(h, cp)) != NULL) {
643 	if (clixon_plugin_datastore_upgrade_one(cp, h, db, xt, msd) < 0)
644 	    goto done;
645     }
646     retval = 0;
647  done:
648     return retval;
649 }
650 
651 /*--------------------------------------------------------------------
652  * RPC callbacks for both client/frontend and backend plugins.
653  * RPC callbacks are explicitly registered in the plugin_init() function
654  * with a tag and a function
655  * When the the tag is encountered, the callback is called.
656  * Primarily backend, but also netconf and restconf frontend plugins.
657  * CLI frontend so far have direct callbacks, ie functions in the cligen
658  * specification are directly dlsym:ed to the CLI plugin.
659  * It would be possible to use this rpc registering API for CLI plugins as well.
660  *
661  * When namespace and name match, the callback is made
662  */
663 typedef struct {
664     qelem_t       rc_qelem;	/* List header */
665     clicon_rpc_cb rc_callback;  /* RPC Callback */
666     void         *rc_arg;	/* Application specific argument to cb */
667     char         *rc_namespace;/* Namespace to combine with name tag */
668     char         *rc_name;	/* Xml/json tag/name */
669 } rpc_callback_t;
670 
671 /* List of rpc callback entries XXX hang on handle */
672 static rpc_callback_t *rpc_cb_list = NULL;
673 
674 /*! Register a RPC callback by appending a new RPC to the list
675  *
676  * @param[in]  h         clicon handle
677  * @param[in]  cb        Callback called
678  * @param[in]  arg       Domain-specific argument to send to callback
679  * @param[in]  ns        namespace of rpc
680  * @param[in]  name      RPC name
681  * @retval     0         OK
682  * @retval    -1         Error
683  * @see rpc_callback_call  which makes the actual callback
684  */
685 int
rpc_callback_register(clicon_handle h,clicon_rpc_cb cb,void * arg,const char * ns,const char * name)686 rpc_callback_register(clicon_handle  h,
687 		      clicon_rpc_cb  cb,
688 		      void          *arg,
689     		      const char    *ns,
690 		      const char    *name)
691 {
692     rpc_callback_t *rc = NULL;
693 
694     if (name == NULL || ns == NULL){
695 	clicon_err(OE_DB, EINVAL, "name or namespace NULL");
696 	goto done;
697     }
698     if ((rc = malloc(sizeof(rpc_callback_t))) == NULL) {
699 	clicon_err(OE_DB, errno, "malloc: %s", strerror(errno));
700 	goto done;
701     }
702     memset(rc, 0, sizeof(*rc));
703     rc->rc_callback = cb;
704     rc->rc_arg  = arg;
705     rc->rc_namespace  = strdup(ns);
706     rc->rc_name  = strdup(name);
707     ADDQ(rc, rpc_cb_list);
708     return 0;
709  done:
710     if (rc){
711 	if (rc->rc_namespace)
712 	    free(rc->rc_namespace);
713 	if (rc->rc_name)
714 	    free(rc->rc_name);
715 	free(rc);
716     }
717     return -1;
718 }
719 
720 /*! Delete all RPC callbacks
721  */
722 int
rpc_callback_delete_all(clicon_handle h)723 rpc_callback_delete_all(clicon_handle h)
724 {
725     rpc_callback_t *rc;
726 
727     while((rc = rpc_cb_list) != NULL) {
728 	DELQ(rc, rpc_cb_list, rpc_callback_t *);
729 	if (rc->rc_namespace)
730 	    free(rc->rc_namespace);
731 	if (rc->rc_name)
732 	    free(rc->rc_name);
733 	free(rc);
734     }
735     return 0;
736 }
737 
738 /*! Search RPC callbacks and invoke if XML match with tag
739  *
740  * @param[in]  h       clicon handle
741  * @param[in]  xn      Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
742  * @param[out] cbret   Return XML (as string in CLIgen buffer), error or OK
743  * @param[in]  arg     Domain-speific arg (eg client_entry)
744  * @retval -1   Error
745  * @retval  0   OK, not found handler.
746  * @retval  n   OK, <n> handler called
747  * @see rpc_callback_register  which register a callback function
748  * @note that several callbacks can be registered. They need to cooperate on
749  * return values, ie if one writes cbret, the other needs to handle that by
750  * leaving it, replacing it or amending it.
751  */
752 int
rpc_callback_call(clicon_handle h,cxobj * xe,cbuf * cbret,void * arg)753 rpc_callback_call(clicon_handle h,
754 		  cxobj        *xe,
755 		  cbuf         *cbret,
756 		  void         *arg)
757 {
758     int            retval = -1;
759     rpc_callback_t *rc;
760     char           *name;
761     char           *prefix;
762     char           *ns;
763     int             nr = 0; /* How many callbacks */
764 
765     if (rpc_cb_list == NULL)
766 	return 0;
767     name = xml_name(xe);
768     prefix = xml_prefix(xe);
769     xml2ns(xe, prefix, &ns);
770     rc = rpc_cb_list;
771     do {
772 	if (strcmp(rc->rc_name, name) == 0 &&
773 	    ns && rc->rc_namespace &&
774 	    strcmp(rc->rc_namespace, ns) == 0){
775 	    if (rc->rc_callback(h, xe, cbret, arg, rc->rc_arg) < 0){
776 		clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_name);
777 		goto done;
778 	    }
779 	    nr++;
780 	}
781 	rc = NEXTQ(rpc_callback_t *, rc);
782     } while (rc != rpc_cb_list);
783     retval = nr; /* 0: none found, >0 nr of handlers called */
784  done:
785     clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
786     return retval;
787 }
788 
789 /*--------------------------------------------------------------------
790  * Upgrade callbacks for backend upgrade of datastore
791  * Register upgrade callbacks in plugin_init() with a module and a "from" and "to"
792  * revision.
793  */
794 typedef struct {
795     qelem_t           uc_qelem;	    /* List header */
796     clicon_upgrade_cb uc_callback;  /* RPC Callback */
797     const char       *uc_fnstr;     /* Stringified fn name for debug */
798     void             *uc_arg;	    /* Application specific argument to cb */
799     char             *uc_namespace; /* Module namespace */
800 } upgrade_callback_t;
801 
802 /* List of rpc callback entries XXX hang on handle */
803 static upgrade_callback_t *upgrade_cb_list = NULL;
804 
805 /*! Register an upgrade callback by appending the new callback to the list
806  *
807  * @param[in]  h         clicon handle
808  * @param[in]  cb        Callback called
809  * @param[in]  fnstr     Stringified function for debug
810  * @param[in]  arg       Domain-specific argument to send to callback
811  * @param[in]  ns        Module namespace (if NULL all modules)
812  * @retval     0         OK
813  * @retval    -1         Error
814  * @see upgrade_callback_call  which makes the actual callback
815  */
816 int
upgrade_callback_reg_fn(clicon_handle h,clicon_upgrade_cb cb,const char * fnstr,const char * ns,void * arg)817 upgrade_callback_reg_fn(clicon_handle     h,
818 			clicon_upgrade_cb cb,
819 			const char       *fnstr,
820 			const char       *ns,
821 			void             *arg)
822 {
823     upgrade_callback_t *uc;
824 
825     if ((uc = malloc(sizeof(upgrade_callback_t))) == NULL) {
826 	clicon_err(OE_DB, errno, "malloc: %s", strerror(errno));
827 	goto done;
828     }
829     memset(uc, 0, sizeof(*uc));
830     uc->uc_callback = cb;
831     uc->uc_fnstr = fnstr;
832     uc->uc_arg  = arg;
833     if (ns)
834 	uc->uc_namespace  = strdup(ns);
835     ADDQ(uc, upgrade_cb_list);
836     return 0;
837  done:
838     if (uc){
839 	if (uc->uc_namespace)
840 	    free(uc->uc_namespace);
841 	free(uc);
842     }
843     return -1;
844 }
845 
846 /*! Delete all Upgrade callbacks
847  */
848 int
upgrade_callback_delete_all(clicon_handle h)849 upgrade_callback_delete_all(clicon_handle h)
850 {
851     upgrade_callback_t *uc;
852 
853     while((uc = upgrade_cb_list) != NULL) {
854 	DELQ(uc, upgrade_cb_list, upgrade_callback_t *);
855 	if (uc->uc_namespace)
856 	    free(uc->uc_namespace);
857 	free(uc);
858     }
859     return 0;
860 }
861 
862 /*! Upgrade specific module identified by namespace, search matching callbacks
863  *
864  * @param[in]  h       clicon handle
865  * @param[in]  xt      Top-level XML tree to be updated (includes other ns as well)
866  * @param[in]  ns      Namespace of module
867  * @param[in]  op      One of XML_FLAG_ADD, _DEL or _CHANGE
868  * @param[in]  from    From revision on the form YYYYMMDD (if DEL or CHANGE)
869  * @param[in]  to      To revision on the form YYYYMMDD (if ADD or CHANGE)
870  * @param[out] cbret   Return XML (as string in CLIgen buffer), on invalid
871  * @retval -1  Error
872  * @retval  0  Invalid - cbret contains reason as netconf
873  * @retval  1  OK
874  * @see upgrade_callback_reg_fn  which registers the callbacks
875  */
876 int
upgrade_callback_call(clicon_handle h,cxobj * xt,char * ns,uint16_t op,uint32_t from,uint32_t to,cbuf * cbret)877 upgrade_callback_call(clicon_handle h,
878 		      cxobj        *xt,
879 		      char         *ns,
880 		      uint16_t      op,
881 		      uint32_t      from,
882 		      uint32_t      to,
883 		      cbuf         *cbret)
884 {
885     int                 retval = -1;
886     upgrade_callback_t *uc;
887     int                 nr = 0; /* How many callbacks */
888     int                 ret;
889 
890     if (upgrade_cb_list == NULL)
891 	return 1;
892     uc = upgrade_cb_list;
893     do {
894 	/* For matching an upgrade callback:
895 	 * - No module name registered (matches all modules) OR
896 	 * - Names match
897 	 * AND
898 	 * - No registered from revision (matches all revisions) OR
899 	 *   - Registered from revision >= from AND
900          *   - Registered to revision <= to (which includes case both 0)
901 	 */
902 	if (uc->uc_namespace == NULL || strcmp(uc->uc_namespace, ns)==0){
903 	    if ((ret = uc->uc_callback(h, xt, ns, op, from, to, uc->uc_arg, cbret)) < 0){
904 		clicon_debug(1, "%s Error in: %s", __FUNCTION__, uc->uc_namespace);
905 		goto done;
906 	    }
907 	    if (ret == 0){
908 		if (cbuf_len(cbret)==0){
909 		    clicon_err(OE_CFG, 0, "Validation fail %s(%s): cbret not set",
910 			       uc->uc_fnstr, ns);
911 		    goto done;
912 		}
913 		goto fail;
914 	    }
915 	    nr++;
916 	}
917 	uc = NEXTQ(upgrade_callback_t *, uc);
918     } while (uc != upgrade_cb_list);
919     retval = 1;
920  done:
921     clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
922     return retval;
923  fail:
924     retval =0;
925     goto done;
926 }
927 
928