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 <syslog.h>
52 #include <ifaddrs.h>
53 #include <sys/time.h>
54 #include <sys/socket.h>
55 #include <sys/stat.h>
56 #include <sys/param.h>
57 #include <sys/types.h>
58 #include <grp.h>
59 #include <netinet/in.h>
60 #include <arpa/inet.h>
61 #include <netinet/in.h>
62 #include <libgen.h>
63
64 /* cligen */
65 #include <cligen/cligen.h>
66
67 /* clicon */
68 #include <clixon/clixon.h>
69
70 #include "clixon_backend_handle.h"
71 #include "backend_socket.h"
72 #include "backend_client.h"
73 #include "backend_plugin.h"
74 #include "backend_commit.h"
75 #include "backend_handle.h"
76 #include "backend_startup.h"
77
78 /* Command line options to be passed to getopt(3) */
79 #define BACKEND_OPTS "hD:f:E:l:d:p:b:Fza:u:P:1qs:c:U:g:y:o:"
80
81 #define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log"
82
83 /*! Clean and close all state of backend (but dont exit).
84 * Cannot use h after this
85 * @param[in] h Clixon handle
86 */
87 static int
backend_terminate(clicon_handle h)88 backend_terminate(clicon_handle h)
89 {
90 yang_stmt *yspec;
91 char *pidfile = clicon_backend_pidfile(h);
92 int sockfamily = clicon_sock_family(h);
93 char *sockpath = clicon_sock(h);
94 cxobj *x;
95 struct stat st;
96 int ss;
97 cvec *nsctx;
98
99 clicon_debug(1, "%s", __FUNCTION__);
100 if ((ss = clicon_socket_get(h)) != -1)
101 close(ss);
102 /* Disconnect datastore */
103 xmldb_disconnect(h);
104 /* Clear module state caches */
105 if ((x = clicon_modst_cache_get(h, 0)) != NULL)
106 xml_free(x);
107 if ((x = clicon_modst_cache_get(h, 1)) != NULL)
108 xml_free(x);
109 /* Free changelog */
110 if ((x = clicon_xml_changelog_get(h)) != NULL)
111 xml_free(x);
112 if ((yspec = clicon_dbspec_yang(h)) != NULL)
113 yspec_free(yspec);
114 if ((yspec = clicon_config_yang(h)) != NULL)
115 yspec_free(yspec);
116 if ((yspec = clicon_nacm_ext_yang(h)) != NULL)
117 yspec_free(yspec);
118 if ((nsctx = clicon_nsctx_global_get(h)) != NULL)
119 cvec_free(nsctx);
120 if ((x = clicon_nacm_ext(h)) != NULL)
121 xml_free(x);
122 if ((x = clicon_conf_xml(h)) != NULL)
123 xml_free(x);
124 stream_publish_exit();
125 clixon_plugin_exit_all(h);
126 /* Delete all backend plugin RPC callbacks */
127 rpc_callback_delete_all(h);
128 /* Delete all backend plugin upgrade callbacks */
129 upgrade_callback_delete_all(h);
130 xpath_optimize_exit();
131
132 if (pidfile)
133 unlink(pidfile);
134 if (sockfamily==AF_UNIX && lstat(sockpath, &st) == 0)
135 unlink(sockpath);
136 backend_handle_exit(h); /* Also deletes streams. Cannot use h after this. */
137 clixon_event_exit();
138 clicon_debug(1, "%s done", __FUNCTION__);
139 clicon_log_exit();
140 return 0;
141 }
142
143 /*! Unlink pidfile and quit
144 */
145 static void
backend_sig_term(int arg)146 backend_sig_term(int arg)
147 {
148 static int i=0;
149
150 if (i++ == 0)
151 clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d",
152 __PROGRAM__, __FUNCTION__, getpid(), arg);
153 clicon_exit_set(); /* checked in clixon_event_loop() */
154 }
155
156 /*! Create backend server socket and register callback
157 * @param[in] h Clicon handle
158 * @retval s Server socket file descriptor (see socket(2))
159 * @retval -1 Error
160 */
161 static int
backend_server_socket(clicon_handle h)162 backend_server_socket(clicon_handle h)
163 {
164 int ss;
165
166 /* Open control socket */
167 if ((ss = backend_socket_init(h)) < 0)
168 return -1;
169 /* ss is a server socket that the clients connect to. The callback
170 therefore accepts clients on ss */
171 if (clixon_event_reg_fd(ss, backend_accept_client, h, "server socket") < 0) {
172 close(ss);
173 return -1;
174 }
175 return ss;
176 }
177
178 /*! Load external NACM file
179 */
180 static int
nacm_load_external(clicon_handle h)181 nacm_load_external(clicon_handle h)
182 {
183 int retval = -1;
184 char *filename; /* NACM config file */
185 yang_stmt *yspec = NULL;
186 cxobj *xt = NULL;
187 struct stat st;
188 FILE *f = NULL;
189 int fd;
190
191 filename = clicon_option_str(h, "CLICON_NACM_FILE");
192 if (filename == NULL || strlen(filename)==0){
193 clicon_err(OE_UNIX, errno, "CLICON_NACM_FILE not set in NACM external mode");
194 goto done;
195 }
196 if (stat(filename, &st) < 0){
197 clicon_err(OE_UNIX, errno, "%s", filename);
198 goto done;
199 }
200 if (!S_ISREG(st.st_mode)){
201 clicon_err(OE_UNIX, 0, "%s is not a regular file", filename);
202 goto done;
203 }
204 if ((f = fopen(filename, "r")) == NULL) {
205 clicon_err(OE_UNIX, errno, "configure file: %s", filename);
206 return -1;
207 }
208 if ((yspec = yspec_new()) == NULL)
209 goto done;
210 if (yang_spec_parse_module(h, "ietf-netconf-acm", NULL, yspec) < 0)
211 goto done;
212 fd = fileno(f);
213 /* Read configfile */
214 if (clixon_xml_parse_file(fd, YB_MODULE, yspec, NULL, &xt, NULL) < 0)
215 goto done;
216 if (xt == NULL){
217 clicon_err(OE_XML, 0, "No xml tree in %s", filename);
218 goto done;
219 }
220 if (clicon_nacm_ext_yang_set(h, yspec) < 0)
221 goto done;
222 if (clicon_nacm_ext_set(h, xt) < 0)
223 goto done;
224
225 retval = 0;
226 done:
227 if (f)
228 fclose(f);
229 return retval;
230 }
231
232 static int
xmldb_drop_priv(clicon_handle h,const char * db,uid_t uid,gid_t gid)233 xmldb_drop_priv(clicon_handle h,
234 const char *db,
235 uid_t uid,
236 gid_t gid)
237 {
238 int retval = -1;
239 char *filename = NULL;
240
241 if (xmldb_db2file(h, db, &filename) < 0)
242 goto done;
243 if (chown(filename, uid, gid) < 0){
244 clicon_err(OE_UNIX, errno, "chown");
245 goto done;
246 }
247 retval = 0;
248 done:
249 if (filename)
250 free(filename);
251 return retval;
252 }
253
254 /*! Drop root privileges uid and gid to Clixon user/group and
255 *
256 * If config options are right, drop process uid/guid privileges and change some
257 * file ownerships.
258 * "Right" means:
259 * - uid is currently 0 (started as root)
260 * - CLICON_BACKEND_USER is set
261 * - CLICON_BACKEND_PRIVILEGES is not "none"
262 * @param[in] h Clicon handle
263 * @param[in] gid Group id (assume already known)
264 * @retval 0 OK
265 * @retval -1 Error
266 */
267 static int
check_drop_priv(clicon_handle h,gid_t gid)268 check_drop_priv(clicon_handle h,
269 gid_t gid)
270 {
271 int retval = -1;
272 uid_t uid;
273 uid_t newuid = -1;
274 enum priv_mode_t priv_mode = PM_NONE;
275 char *backend_user = NULL;
276
277 /* Get privileges mode (for dropping privileges) */
278 priv_mode = clicon_backend_privileges_mode(h);
279 if (priv_mode == PM_NONE)
280 goto ok;
281
282 /* From here, drop privileges */
283 /* Check backend user exists */
284 if ((backend_user = clicon_backend_user(h)) == NULL){
285 clicon_err(OE_DAEMON, EPERM, "Privileges cannot be dropped without specifying CLICON_BACKEND_USER\n");
286 goto done;
287 }
288 /* Get (wanted) new backend user id */
289 if (name2uid(backend_user, &newuid) < 0){
290 clicon_err(OE_DAEMON, errno, "'%s' is not a valid user .\n", backend_user);
291 goto done;
292 }
293 /* get current backend userid, if already at this level OK */
294 if ((uid = getuid()) == newuid)
295 goto ok;
296 if (uid != 0){
297 clicon_err(OE_DAEMON, EPERM, "Privileges can only be dropped from root user (uid is %u)\n", uid);
298 goto done;
299 }
300 /* When dropping privileges, datastores are created if they do not exist.
301 * But when drops are not made, datastores are created on demand.
302 * XXX: move the creation to top-level so they are always created at init?
303 */
304 if (xmldb_exists(h, "running") != 1)
305 if (xmldb_create(h, "running") < 0)
306 goto done;
307 if (xmldb_drop_priv(h, "running", newuid, gid) < 0)
308 goto done;
309 if (xmldb_exists(h, "candidate") != 1)
310 if (xmldb_create(h, "candidate") < 0)
311 goto done;
312 if (xmldb_drop_priv(h, "candidate", newuid, gid) < 0)
313 goto done;
314 if (xmldb_exists(h, "startup") != 1)
315 if (xmldb_create(h, "startup") < 0)
316 goto done;
317 if (xmldb_drop_priv(h, "startup", newuid, gid) < 0)
318 goto done;
319
320 if (setgid(gid) == -1) {
321 clicon_err(OE_DAEMON, errno, "setgid %d", gid);
322 goto done;
323 }
324 switch (priv_mode){
325 case PM_DROP_PERM:
326 if (drop_priv_perm(newuid) < 0)
327 goto done;
328 /* Verify you cannot regain root privileges */
329 if (setuid(0) != -1){
330 clicon_err(OE_DAEMON, EPERM, "Could regain root privilieges");
331 goto done;
332 }
333 break;
334 case PM_DROP_TEMP:
335 if (drop_priv_temp(newuid) < 0)
336 goto done;
337 break;
338 case PM_NONE:
339 break; /* catched above */
340 }
341 ok:
342 retval = 0;
343 done:
344 return retval;
345 }
346
347 /*! Given a retval, transform to status or fatal error
348 *
349 * @param[in] ret Return value from xml validation function
350 * @param[out] status Transform status according to rules below
351 * @retval 0 OK, status set
352 * @retval -1 Fatal error outside scope of startup_status
353 * Transformation rules:
354 * 1) retval -1 assume clicon_errno/suberrno set. Special case from xml parser
355 * is clicon_suberrno = XMLPARSE_ERRNO which assumes an XML (non-fatal) parse
356 * error which translates to -> STARTUP_ERR
357 * All other error cases translates to fatal error
358 * 2) retval 0 is xml validation fails -> STARTUP_INVALID
359 * 3) retval 1 is OK -> STARTUP_OK
360 * 4) any other retval translates to fatal error
361 */
362 static int
ret2status(int ret,enum startup_status * status)363 ret2status(int ret,
364 enum startup_status *status)
365 {
366 int retval = -1;
367
368 switch (ret){
369 case -1:
370 if (clicon_suberrno != XMLPARSE_ERRNO)
371 goto done;
372 clicon_err_reset();
373 *status = STARTUP_ERR;
374 break;
375 case 0:
376 *status = STARTUP_INVALID;
377 break;
378 case 1:
379 *status = STARTUP_OK;
380 break;
381 default:
382 clicon_err(OE_CFG, EINVAL, "No such retval %d", retval);
383 } /* switch */
384 retval = 0;
385 done:
386 return retval;
387 }
388
389 /*! usage
390 */
391 static void
usage(clicon_handle h,char * argv0)392 usage(clicon_handle h,
393 char *argv0)
394 {
395 char *plgdir = clicon_backend_dir(h);
396 char *confsock = clicon_sock(h);
397 char *confpid = clicon_backend_pidfile(h);
398 char *group = clicon_sock_group(h);
399
400 fprintf(stderr, "usage:%s <options>*\n"
401 "where options are\n"
402 "\t-h\t\tHelp\n"
403 "\t-D <level>\tDebug level\n"
404 "\t-f <file>\tCLICON config file\n"
405 "\t-E <dir> \tExtra configuration file directory\n"
406 "\t-l (s|e|o|f<file>) Log on (s)yslog, std(e)rr or std(o)ut (stderr is default) Only valid if -F, if background syslog is on syslog.\n"
407 "\t-d <dir>\tSpecify backend plugin directory (default: %s)\n"
408 "\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n"
409 "\t-b <dir>\tSpecify XMLDB database directory\n"
410 "\t-F\t\tRun in foreground, do not run as daemon\n"
411 "\t-z\t\tKill other config daemon and exit\n"
412 "\t-a UNIX|IPv4|IPv6 Internal backend socket family\n"
413 "\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)(default: %s)\n"
414 "\t-P <file>\tPid filename (default: %s)\n"
415 "\t-1\t\tRun once and then quit (dont wait for events)\n"
416 "\t-s <mode>\tSpecify backend startup mode: none|startup|running|init)\n"
417 "\t-c <file>\tLoad extra xml configuration, but don't commit.\n"
418 "\t-q \t\tQuit startup directly after upgrading and print result on stdout\n"
419 "\t-U <user>\tRun backend daemon as this user AND drop privileges permanently\n"
420 "\t-g <group>\tClient membership required to this group (default: %s)\n"
421
422 "\t-y <file>\tLoad yang spec file (override yang main module)\n"
423 "\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
424 argv0,
425 plgdir ? plgdir : "none",
426 confsock ? confsock : "none",
427 confpid ? confpid : "none",
428 group ? group : "none"
429 );
430 exit(-1);
431 }
432
433 int
main(int argc,char ** argv)434 main(int argc,
435 char **argv)
436 {
437 int retval = -1;
438 int c;
439 int zap;
440 int foreground;
441 int once;
442 enum startup_mode_t startup_mode;
443 char *extraxml_file;
444 char *backend_group = NULL;
445 char *argv0 = argv[0];
446 struct stat st;
447 clicon_handle h;
448 int help = 0;
449 int pid;
450 char *pidfile;
451 char *sock;
452 int sockfamily;
453 char *nacm_mode;
454 int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR;
455 yang_stmt *yspec = NULL;
456 char *str;
457 int ss = -1; /* server socket */
458 cbuf *cbret = NULL; /* startup cbuf if invalid */
459 enum startup_status status = STARTUP_ERR; /* Startup status */
460 int ret;
461 char *dir;
462 gid_t gid = -1;
463 cvec *nsctx_global = NULL; /* Global namespace context */
464 size_t cligen_buflen;
465 size_t cligen_bufthreshold;
466 int dbg;
467
468 /* In the startup, logs to stderr & syslog and debug flag set later */
469 clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
470 /* Initiate CLICON handle */
471 if ((h = backend_handle_init()) == NULL)
472 return -1;
473
474 foreground = 0;
475 once = 0;
476 zap = 0;
477 extraxml_file = NULL;
478 dbg = 0;
479
480 /*
481 * Command-line options for help, debug, and config-file
482 */
483 opterr = 0;
484 optind = 1;
485 while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
486 switch (c) {
487 case 'h':
488 /* Defer the call to usage() to later. Reason is that for helpful
489 text messages, default dirs, etc, are not set until later.
490 But this measn that we need to check if 'help' is set before
491 exiting, and then call usage() before exit.
492 */
493 help = 1;
494 break;
495 case 'D' : /* debug */
496 if (sscanf(optarg, "%d", &dbg) != 1)
497 usage(h, argv[0]);
498 break;
499 case 'f': /* config file */
500 if (!strlen(optarg))
501 usage(h, argv[0]);
502 clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
503 break;
504 case 'E': /* extra config directory */
505 if (!strlen(optarg))
506 usage(h, argv[0]);
507 clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg);
508 break;
509 case 'l': /* Log destination: s|e|o */
510 if ((logdst = clicon_log_opt(optarg[0])) < 0)
511 usage(h, argv[0]);
512 if (logdst == CLICON_LOG_FILE &&
513 strlen(optarg)>1 &&
514 clicon_log_file(optarg+1) < 0)
515 goto done;
516 break;
517 }
518 /*
519 * Here we have the debug flag settings, use that.
520 * Syslogs also to stderr, but later turn stderr off in daemon mode.
521 * error only to syslog. debug to syslog
522 * XXX: if started in a start-daemon script, there will be irritating
523 * double syslogs until fork below.
524 */
525 clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
526 clicon_debug_init(dbg, NULL);
527
528 /* Find and read configfile */
529 if (clicon_options_main(h) < 0){
530 if (help)
531 usage(h, argv[0]);
532 goto done;
533 }
534 /* External NACM file? */
535 nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
536 if (nacm_mode && strcmp(nacm_mode, "external") == 0)
537 if (nacm_load_external(h) < 0)
538 goto done;
539
540 /* Now run through the operational args */
541 opterr = 1;
542 optind = 1;
543 while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
544 switch (c) {
545 case 'h' : /* help */
546 case 'D' : /* debug */
547 case 'f': /* config file */
548 case 'E': /* extra config dir */
549 case 'l' :
550 break; /* see above */
551 case 'd': /* Plugin directory */
552 if (!strlen(optarg))
553 usage(h, argv[0]);
554 if (clicon_option_add(h, "CLICON_BACKEND_DIR", optarg) < 0)
555 goto done;
556 break;
557 case 'b': /* XMLDB database directory */
558 if (!strlen(optarg))
559 usage(h, argv[0]);
560 if (clicon_option_add(h, "CLICON_XMLDB_DIR", optarg) < 0)
561 goto done;
562 break;
563 case 'p' : /* yang dir path */
564 if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
565 goto done;
566 break;
567 case 'F' : /* foreground */
568 foreground = 1;
569 break;
570 case 'z': /* Zap other process */
571 zap++;
572 break;
573 case 'a': /* internal backend socket address family */
574 if (clicon_option_add(h, "CLICON_SOCK_FAMILY", optarg) < 0)
575 goto done;
576 break;
577 case 'u': /* config unix domain path / ip address */
578 if (!strlen(optarg))
579 usage(h, argv[0]);
580 if (clicon_option_add(h, "CLICON_SOCK", optarg) < 0)
581 goto done;
582 break;
583 case 'P': /* pidfile */
584 if (clicon_option_add(h, "CLICON_BACKEND_PIDFILE", optarg) < 0)
585 goto done;
586 break;
587 case '1' : /* Quit after reading database once - dont wait for events */
588 once = 1;
589 break;
590 case 'q': /* Quit directly after startup upgrading and print result on stdout */
591 if (clicon_quit_upgrade_set(h, 1) < 0)
592 goto done;
593 break;
594 case 's' : /* startup mode */
595 if (clicon_option_add(h, "CLICON_STARTUP_MODE", optarg) < 0)
596 goto done;
597 if (clicon_startup_mode(h) < 0){
598 fprintf(stderr, "Invalid startup mode: %s\n", optarg);
599 usage(h, argv[0]);
600 }
601 break;
602 case 'c': /* Load application config */
603 extraxml_file = optarg;
604 break;
605 case 'U': /* config user (for socket and drop privileges) */
606 if (clicon_option_add(h, "CLICON_USER", optarg) < 0)
607 goto done;
608 if (clicon_option_add(h, "CLICON_BACKEND_PRIVILEGES", "drop_permanent") < 0)
609 goto done;
610 break;
611 case 'g': /* config socket group */
612 if (clicon_option_add(h, "CLICON_SOCK_GROUP", optarg) < 0)
613 goto done;
614 break;
615 case 'y' : /* Load yang absolute filename */
616 if (clicon_option_add(h, "CLICON_YANG_MAIN_FILE", optarg) < 0)
617 goto done;
618 break;
619 case 'o':{ /* Configuration option */
620 char *val;
621 if ((val = index(optarg, '=')) == NULL)
622 usage(h, argv0);
623 *val++ = '\0';
624 if (clicon_option_add(h, optarg, val) < 0)
625 goto done;
626 break;
627 }
628 default:
629 usage(h, argv[0]);
630 break;
631 }
632
633 argc -= optind;
634 argv += optind;
635
636 /* Access the remaining argv/argc options (after --) w clicon-argv_get() */
637 clicon_argv_set(h, argv0, argc, argv);
638
639 clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
640
641 /* Defer: Wait to the last minute to print help message */
642 if (help)
643 usage(h, argv[0]);
644
645 /* Init cligen buffers */
646 cligen_buflen = clicon_option_int(h, "CLICON_CLI_BUF_START");
647 cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD");
648 cbuf_alloc_set(cligen_buflen, cligen_bufthreshold);
649
650 #ifndef HAVE_LIBXML2
651 if (clicon_yang_regexp(h) == REGEXP_LIBXML2){
652 clicon_err(OE_FATAL, 0, "CLICON_YANG_REGEXP set to libxml2, but HAVE_LIBXML2 not set (Either change CLICON_YANG_REGEXP to posix, or run: configure --with-libxml2))");
653 goto done;
654 }
655 #endif
656 /* Check pid-file, if zap kil the old daemon, else return here */
657 if ((pidfile = clicon_backend_pidfile(h)) == NULL){
658 clicon_err(OE_FATAL, 0, "pidfile not set");
659 goto done;
660 }
661 sockfamily = clicon_sock_family(h);
662 if ((sock = clicon_sock(h)) == NULL){
663 clicon_err(OE_FATAL, 0, "sock not set");
664 goto done;
665 }
666 if (pidfile_get(pidfile, &pid) < 0)
667 return -1;
668 if (zap){
669 if (pid && pidfile_zapold(pid) < 0)
670 return -1;
671 if (lstat(pidfile, &st) == 0)
672 unlink(pidfile);
673 if (sockfamily==AF_UNIX && lstat(sock, &st) == 0)
674 unlink(sock);
675 backend_terminate(h);
676 exit(0); /* OK */
677 }
678 else
679 if (pid){
680 clicon_err(OE_DAEMON, 0, "Daemon already running with pid %d\n(Try killing it with %s -z)",
681 pid, argv0);
682 return -1; /* goto done deletes pidfile */
683 }
684
685 /* After this point we can goto done on error
686 * Here there is either no old process or we have killed it,..
687 */
688 if (lstat(pidfile, &st) == 0)
689 unlink(pidfile);
690 if (sockfamily==AF_UNIX && lstat(sock, &st) == 0)
691 unlink(sock);
692
693 /* Sanity check: backend group exists */
694 if ((backend_group = clicon_sock_group(h)) == NULL){
695 clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
696 return -1;
697 }
698 if (group_name2gid(backend_group, &gid) < 0){
699 clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.\n" /* \n required here due to multi-line log */
700 "The config demon requires a valid group to create a server UNIX socket\n"
701 "Define a valid CLICON_SOCK_GROUP in %s or via the -g option\n"
702 "or create the group and add the user to it. Check documentation for how to do this on your platform",
703 backend_group,
704 clicon_configfile(h));
705 goto done;
706 }
707
708 /* Treat unknown XML as anydata */
709 if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1)
710 xml_bind_yang_unknown_anydata(1);
711
712 /* Publish stream on pubsub channels.
713 * CLICON_STREAM_PUB should be set to URL to where streams are published
714 * and configure should be run with --enable-publish
715 */
716 if (clicon_option_exists(h, "CLICON_STREAM_PUB") &&
717 stream_publish_init() < 0)
718 goto done;
719 /* Connect to plugin to get a handle */
720 if (xmldb_connect(h) < 0)
721 goto done;
722
723 /* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
724 xml_nsctx_namespace_netconf_default(h);
725
726 /* Add (hardcoded) netconf features in case ietf-netconf loaded here
727 * Otherwise it is loaded in netconf_module_load below
728 */
729 if (netconf_module_features(h) < 0)
730 goto done;
731
732 /* Create top-level yang spec and store as option */
733 if ((yspec = yspec_new()) == NULL)
734 goto done;
735 clicon_dbspec_yang_set(h, yspec);
736
737 /* Load backend plugins before yangs are loaded (eg extension callbacks) */
738 if ((dir = clicon_backend_dir(h)) != NULL &&
739 clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir,
740 clicon_option_str(h, "CLICON_BACKEND_REGEXP")) < 0)
741 goto done;
742
743 /* Load Yang modules
744 * 1. Load a yang module as a specific absolute filename */
745 if ((str = clicon_yang_main_file(h)) != NULL)
746 if (yang_spec_parse_file(h, str, yspec) < 0)
747 goto done;
748 /* 2. Load a (single) main module */
749 if ((str = clicon_yang_module_main(h)) != NULL)
750 if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h),
751 yspec) < 0)
752 goto done;
753 /* 3. Load all modules in a directory (will not overwrite file loaded ^) */
754 if ((str = clicon_yang_main_dir(h)) != NULL)
755 if (yang_spec_load_dir(h, str, yspec) < 0)
756 goto done;
757 /* Load clixon lib yang module */
758 if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
759 goto done;
760 /* Load yang module library, RFC7895 */
761 if (yang_modules_init(h) < 0)
762 goto done;
763 /* Add netconf yang spec, used by netconf client and as internal protocol
764 */
765 if (netconf_module_load(h) < 0)
766 goto done;
767 /* Load yang restconf module */
768 if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
769 goto done;
770 /* Load yang Restconf stream discovery */
771 if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
772 yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0)
773 goto done;
774 /* Load yang Netconf stream discovery */
775 if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
776 yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
777 goto done;
778 /* Load yang YANG module state */
779 if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE") &&
780 yang_spec_parse_module(h, "ietf-yang-library", NULL, yspec)< 0)
781 goto done;
782 /* Here all modules are loaded
783 * Compute and set canonical namespace context
784 */
785 if (xml_nsctx_yangspec(yspec, &nsctx_global) < 0)
786 goto done;
787 if (clicon_nsctx_global_set(h, nsctx_global) < 0)
788 goto done;
789
790 /* Initialize server socket and save it to handle */
791 if (backend_rpc_init(h) < 0)
792 goto done;
793
794 /* Must be after netconf_module_load, but before startup code */
795 if (clicon_option_bool(h, "CLICON_XML_CHANGELOG"))
796 if (clixon_xml_changelog_init(h) < 0)
797 goto done;
798
799 /* Save modules state of the backend (server). Compare with startup XML */
800 if (startup_module_state(h, yspec) < 0)
801 goto done;
802 /* Startup mode needs to be defined, */
803 startup_mode = clicon_startup_mode(h);
804 if ((int)startup_mode == -1){
805 clicon_log(LOG_ERR, "Startup mode undefined. Specify option CLICON_STARTUP_MODE or specify -s option to clicon_backend.");
806 goto done;
807 }
808 /* Check that netconf :startup is enabled */
809 if (startup_mode == SM_STARTUP &&
810 !if_feature(yspec, "ietf-netconf", "startup")){
811 clicon_log(LOG_ERR, "Startup mode selected but Netconf :startup feature is not enabled. Enable with option: <CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>");
812 goto done;
813 }
814
815 /* Init running db if it is not there
816 */
817 if (xmldb_exists(h, "running") != 1)
818 if (xmldb_create(h, "running") < 0)
819 return -1;
820 xmldb_delete(h, "candidate");
821 /* If startup fails, lib functions report invalidation info in a cbuf */
822 if ((cbret = cbuf_new()) == NULL){
823 clicon_err(OE_XML, errno, "cbuf_new");
824 goto done;
825 }
826 switch (startup_mode){
827 case SM_INIT: /* Scratch running and start from empty */
828 /* [Delete and] create running db */
829 if (xmldb_db_reset(h, "running") < 0)
830 goto done;
831 case SM_NONE: /* Fall through *
832 * Load plugins and call plugin_init() */
833 status = STARTUP_OK;
834 break;
835 case SM_RUNNING: /* Use running as startup */
836 /* Copy original running to tmp as backup (restore if error) */
837 if (xmldb_copy(h, "running", "tmp") < 0)
838 goto done;
839 ret = startup_mode_startup(h, "tmp", cbret);
840 /* If ret fails, copy tmp back to running */
841 if (ret != 1)
842 if (xmldb_copy(h, "tmp", "running") < 0)
843 goto done;
844 if (ret2status(ret, &status) < 0)
845 goto done;
846 break;
847 case SM_STARTUP:
848 /* Copy original running to tmp as backup (restore if error) */
849 if (xmldb_copy(h, "running", "tmp") < 0)
850 goto done;
851 /* Load and commit from startup */
852 ret = startup_mode_startup(h, "startup", cbret);
853 /* If ret fails, copy tmp back to running */
854 if (ret != 1)
855 if (xmldb_copy(h, "tmp", "running") < 0)
856 goto done;
857 /* clear startup dbcache */
858 xmldb_clear(h, "startup");
859 if (ret2status(ret, &status) < 0)
860 goto done;
861 /* if status = STARTUP_INVALID, cbret contains info */
862 }
863 /* Quit after upgrade catch-all, running/startup quits in upgrade code */
864 if (clicon_quit_upgrade_get(h) == 1)
865 goto done;
866
867 /*
868 * Disable unknown to anydata auto-creation after startup
869 */
870 if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1){
871 clicon_option_bool_set(h, "CLICON_YANG_UNKNOWN_ANYDATA", 0);
872 xml_bind_yang_unknown_anydata(0);
873 }
874 /* Merge extra XML from file and reset function to running
875 */
876 if (status == STARTUP_OK && startup_mode != SM_NONE){
877 if ((ret = startup_extraxml(h, extraxml_file, cbret)) < 0)
878 goto done;
879 if (ret2status(ret, &status) < 0)
880 goto done;
881 /* if status = STARTUP_INVALID, cbret contains info */
882 }
883
884 if (status != STARTUP_OK){
885 if (cbuf_len(cbret))
886 clicon_log(LOG_NOTICE, "%s: %u %s", __PROGRAM__, getpid(), cbuf_get(cbret));
887 if (startup_failsafe(h) < 0){
888 goto done;
889 }
890 }
891
892 /* Initiate the shared candidate. */
893 if (xmldb_copy(h, "running", "candidate") < 0)
894 goto done;
895 xmldb_modified_set(h, "candidate", 0);
896
897 /* Set startup status */
898 if (clicon_startup_status_set(h, status) < 0)
899 goto done;
900
901 if (status == STARTUP_INVALID && cbuf_len(cbret))
902 clicon_log(LOG_NOTICE, "%s: %u %s", __PROGRAM__, getpid(), cbuf_get(cbret));
903
904 /* Call backend plugin_start with user -- options */
905 if (clixon_plugin_start_all(h) < 0)
906 goto done;
907 /* -1 option to run only once */
908 if (once)
909 goto ok;
910
911 /* Daemonize and initiate logging. Note error is initiated here to make
912 demonized errors OK. Before this stage, errors are logged on stderr
913 also */
914 if (foreground==0){
915 clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO,
916 logdst==CLICON_LOG_FILE?CLICON_LOG_FILE:CLICON_LOG_SYSLOG);
917 if (daemon(0, 0) < 0){
918 fprintf(stderr, "config: daemon");
919 exit(-1);
920 }
921
922 }
923 /* Call plugin callbacks when in background and before dropped privileges */
924 if (clixon_plugin_daemon_all(h) < 0)
925 goto done;
926
927 /* Write pid-file */
928 if ((pid = pidfile_write(pidfile)) < 0)
929 goto done;
930
931 clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
932 if (set_signal(SIGTERM, backend_sig_term, NULL) < 0){
933 clicon_err(OE_DAEMON, errno, "Setting signal");
934 goto done;
935 }
936 if (set_signal(SIGINT, backend_sig_term, NULL) < 0){
937 clicon_err(OE_DAEMON, errno, "Setting signal");
938 goto done;
939 }
940
941 /* Initialize server socket and save it to handle */
942 if ((ss = backend_server_socket(h)) < 0)
943 goto done;
944 if (clicon_socket_set(h, ss) < 0)
945 goto done;
946 if (dbg)
947 clicon_option_dump(h, dbg);
948 /* Depending on configure setting, privileges may be dropped here after
949 * initializations */
950 if (check_drop_priv(h, gid) < 0)
951 goto done;
952
953 /* Start session-id for clients */
954 clicon_session_id_set(h, 0);
955
956 if (stream_timer_setup(0, h) < 0)
957 goto done;
958 if (clixon_event_loop() < 0)
959 goto done;
960 ok:
961 retval = 0;
962 done:
963 if (cbret)
964 cbuf_free(cbret);
965 clicon_log(LOG_NOTICE, "%s: %u Terminated retval:%d", __PROGRAM__, getpid(), retval);
966 backend_terminate(h); /* Cannot use h after this */
967
968 return retval;
969 }
970