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 <stdio.h>
43 #include <string.h>
44 #include <stdlib.h>
45 #include <unistd.h>
46 #include <stdarg.h>
47 #include <errno.h>
48 #include <signal.h>
49 #include <fcntl.h>
50 #include <time.h>
51 #include <pwd.h>
52 #include <syslog.h>
53 #include <sys/stat.h>
54 #include <sys/time.h>
55 #include <sys/socket.h>
56 #include <sys/param.h>
57 #include <sys/types.h>
58 #include <netinet/in.h>
59 #include <arpa/inet.h>
60 #include <netinet/in.h>
61 
62 /* cligen */
63 #include <cligen/cligen.h>
64 
65 /* clicon */
66 #include <clixon/clixon.h>
67 
68 #include "clixon_backend_transaction.h"
69 #include "backend_plugin.h"
70 #include "backend_handle.h"
71 #include "backend_commit.h"
72 #include "backend_startup.h"
73 
74 /*! Merge db1 into db2 without commit
75  * @retval   -1       Error
76  * @retval    0       Validation failed (with cbret set)
77  * @retval    1       Validation OK
78  */
79 static int
db_merge(clicon_handle h,const char * db1,const char * db2,cbuf * cbret)80 db_merge(clicon_handle h,
81  	 const char   *db1,
82     	 const char   *db2,
83 	 cbuf         *cbret)
84 {
85     int    retval = -1;
86     cxobj *xt = NULL;
87 
88     /* Get data as xml from db1 */
89     if (xmldb_get0(h, (char*)db1, YB_MODULE, NULL, NULL, 0, &xt, NULL) < 0)
90 	goto done;
91     /* Merge xml into db2. Without commit */
92     retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, clicon_username_get(h), cbret);
93  done:
94     xmldb_get0_free(h, &xt);
95     return retval;
96 }
97 
98 /*! Clixon startup startup mode: Commit startup configuration into running state
99  * @param[in]  h       Clixon handle
100  * @param[in]  db      tmp or startup
101  * @param[out] cbret   If status is invalid contains error message
102  * @retval    -1       Error
103  * @retval     0       Validation failed
104  * @retval     1       OK
105 
106 OK:
107                               reset
108 running                         |--------+------------> RUNNING
109                 parse validate OK       / commit
110 startup -------+--+-------+------------+
111 
112 
113 INVALID (requires manual edit of candidate)
114 failsafe      ----------------------+
115                             reset    \ commit
116 running                       |-------+---------------> RUNNING FAILSAFE
117               parse validate fail
118 startup      ---+-------------------------------------> INVALID XML
119 
120 ERR: (requires repair of startup) NYI
121 failsafe      ----------------------+
122                             reset    \ commit
123 running                       |-------+---------------> RUNNING FAILSAFE
124               parse fail
125 startup       --+-------------------------------------> BROKEN XML
126 
127  * @note: if commit fails, copy factory to running
128  */
129 int
startup_mode_startup(clicon_handle h,char * db,cbuf * cbret)130 startup_mode_startup(clicon_handle        h,
131 		     char                *db,
132 		     cbuf                *cbret)
133 {
134     int         retval = -1;
135     int         ret;
136 
137     if (strcmp(db, "running")==0){
138 	clicon_err(OE_FATAL, 0, "Invalid startup db: %s", db);
139 	goto done;
140     }
141     /* If startup does not exist, create it empty */
142     if (xmldb_exists(h, db) != 1){ /* diff */
143 	if (xmldb_create(h, db) < 0) /* diff */
144 	    return -1;
145     }
146     if ((ret = startup_commit(h, db, cbret)) < 0)
147 	goto done;
148     if (ret == 0)
149 	goto fail;
150     retval = 1;
151  done:
152     return retval;
153  fail:
154     retval = 0;
155     goto done;
156 }
157 
158 /*! Merge xml in filename into database
159  * @retval   -1       Error
160  * @retval    0       Validation failed (with cbret set)
161  * @retval    1       Validation OK
162  */
163 static int
load_extraxml(clicon_handle h,char * filename,const char * db,cbuf * cbret)164 load_extraxml(clicon_handle h,
165 	      char         *filename,
166 	      const char   *db,
167 	      cbuf         *cbret)
168 {
169     int        retval =  -1;
170     cxobj     *xt = NULL;
171     int        fd = -1;
172     yang_stmt *yspec = NULL;
173 
174     if (filename == NULL)
175 	return 1;
176     if ((fd = open(filename, O_RDONLY)) < 0){
177 	clicon_err(OE_UNIX, errno, "open(%s)", filename);
178 	goto done;
179     }
180     yspec = clicon_dbspec_yang(h);
181     if (clixon_xml_parse_file(fd, YB_MODULE, yspec, NULL, &xt, NULL) < 0)
182 	goto done;
183     /* Replace parent w first child */
184     if (xml_rootchild(xt, 0, &xt) < 0)
185 	goto done;
186     /* Merge user reset state */
187     retval = xmldb_put(h, (char*)db, OP_MERGE, xt, clicon_username_get(h), cbret);
188  done:
189     if (fd != -1)
190 	close(fd);
191     if (xt)
192 	xml_free(xt);
193     return retval;
194 }
195 
196 /*! Load extra XML via file and/or reset callback, and merge with current
197  * An application can add extra XML either via the -c <file> option or
198  * via the .ca_reset callback. This XML is "merged" into running, that is,
199  * it does not trigger validation calbacks.
200  * The function uses an extra "tmp" database, loads the file to it, and calls
201  * the reset function on it.
202  * @param[in]  h    Clicon handle
203  * @param[in]  file (Optional) extra xml file
204  * @param[out] status  Startup status
205  * @param[out] cbret   If status is invalid contains error message
206  * @retval    -1       Error
207  * @retval     0       Validation failed
208  * @retval     1       OK
209 
210 running -----------------+----+------>
211            reset  loadfile   / merge
212 tmp     |-------+-----+-----+
213              reset   extrafile
214  */
215 int
startup_extraxml(clicon_handle h,char * file,cbuf * cbret)216 startup_extraxml(clicon_handle        h,
217 		 char                *file,
218 		 cbuf                *cbret)
219 {
220     int         retval = -1;
221     char       *tmp_db = "tmp";
222     int         ret;
223     cxobj	*xt0 = NULL;
224     cxobj	*xt = NULL;
225 
226     /* Clear tmp db */
227     if (xmldb_db_reset(h, tmp_db) < 0)
228 	goto done;
229     /* Application may define extra xml in its reset function */
230     if (clixon_plugin_reset_all(h, tmp_db) < 0)
231 	goto done;
232     /* Extra XML can also be added via file */
233     if (file){
234 	/* Parse and load file into tmp db */
235 	if ((ret = load_extraxml(h, file, tmp_db, cbret)) < 0)
236 	    goto done;
237 	if (ret == 0)
238 	    goto fail;
239     }
240     /*
241      * Check if tmp db is empty.
242      * It should be empty if extra-xml is null and reset plugins did nothing
243      * then skip validation.
244      */
245     if (xmldb_get(h, tmp_db, NULL, NULL, &xt0) < 0)
246 	goto done;
247     if (xmldb_empty_get(h, tmp_db))
248 	goto ok;
249     xt = NULL;
250     /* Validate the tmp db and return possibly upgraded xml in xt
251      */
252     if ((ret = startup_validate(h, tmp_db, &xt, cbret)) < 0)
253 	goto done;
254     if (ret == 0)
255 	goto fail;
256     if (xt==NULL || xml_child_nr(xt)==0)
257 	goto ok;
258     /* Merge tmp into running (no commit) */
259     if ((ret = db_merge(h, tmp_db, "running", cbret)) < 0)
260 	goto fail;
261     if (ret == 0)
262 	goto fail;
263  ok:
264     retval = 1;
265  done:
266     if (xt0)
267 	xml_free(xt0);
268     xmldb_get0_free(h, &xt);
269     if (xmldb_delete(h, tmp_db) != 0 && errno != ENOENT)
270 	return -1;
271     return retval;
272  fail:
273     retval = 0;
274     goto done;
275 }
276 
277 /*! Reset running and start in failsafe mode. If no failsafe then quit.
278   Typically done when startup status is not OK so
279 
280 failsafe      ----------------------+
281                             reset    \ commit
282 running                   ----|-------+---------------> RUNNING FAILSAFE
283                            \
284 tmp                         |---------------------->
285  */
286 int
startup_failsafe(clicon_handle h)287 startup_failsafe(clicon_handle h)
288 {
289     int   retval = -1;
290     int   ret;
291     char *db = "failsafe";
292     cbuf *cbret = NULL;
293 
294     if ((cbret = cbuf_new()) == NULL){
295 	clicon_err(OE_XML, errno, "cbuf_new");
296 	goto done;
297     }
298     if ((ret = xmldb_exists(h, db)) < 0)
299 	goto done;
300     if (ret == 0){ /* No it does not exist, fail */
301 	clicon_err(OE_DB, 0, "Startup failed and no Failsafe database found, exiting");
302 	goto done;
303     }
304     /* Copy original running to tmp as backup (restore if error) */
305     if (xmldb_copy(h, "running", "tmp") < 0)
306 	goto done;
307     if (xmldb_db_reset(h, "running") < 0)
308 	goto done;
309     ret = candidate_commit(h, db, cbret);
310     if (ret != 1)
311 	if (xmldb_copy(h, "tmp", "running") < 0)
312 	    goto done;
313     if (ret < 0)
314 	goto done;
315     if (ret == 0){
316 	clicon_err(OE_DB, 0, "Startup failed, Failsafe database validation failed %s", cbuf_get(cbret));
317 	goto done;
318     }
319     clicon_log(LOG_NOTICE, "Startup failed, Failsafe database loaded ");
320     retval = 0;
321  done:
322     if (cbret)
323 	cbuf_free(cbret);
324     return retval;
325 }
326 
327 /*! Init modules state of the backend (server). To compare with startup XML
328  * Set the modules state as setopt to the datastore module.
329  * Only if CLICON_XMLDB_MODSTATE is enabled
330  * @retval -1 Error
331  * @retval  0 OK
332  */
333 int
startup_module_state(clicon_handle h,yang_stmt * yspec)334 startup_module_state(clicon_handle h,
335 		     yang_stmt    *yspec)
336 {
337     int    retval = -1;
338     cxobj *x = NULL;
339     int    ret;
340 
341     if (!clicon_option_bool(h, "CLICON_XMLDB_MODSTATE"))
342 	goto ok;
343     /* Set up cache
344      * Now, access brief module cache with clicon_modst_cache_get(h, 1) */
345     if ((ret = yang_modules_state_get(h, yspec, NULL, NULL, 1, &x)) < 0)
346 	goto done;
347     if (ret == 0)
348 	goto fail;
349  ok:
350     retval = 1;
351  done:
352     if (x)
353 	xml_free(x);
354     return retval;
355  fail:
356     retval = 0;
357     goto done;
358 }
359