bes Updated for version 3.20.13
daemon.cc
1// daemon.cc
2
3// This file is part of bes, A C++ back-end server implementation framework
4// for the OPeNDAP Data Access Protocol.
5
6// Copyright (c) 2004-2009 University Corporation for Atmospheric Research
7// Author: Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
8//
9// This library is free software; you can redistribute it and/or
10// modify it under the terms of the GNU Lesser General Public
11// License as published by the Free Software Foundation; either
12// version 2.1 of the License, or (at your option) any later version.
13//
14// This library is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17// Lesser General Public License for more details.
18//
19// You should have received a copy of the GNU Lesser General Public
20// License along with this library; if not, write to the Free Software
21// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22//
23// You can contact University Corporation for Atmospheric Research at
24// 3080 Center Green Drive, Boulder, CO 80301
25
26// (c) COPYRIGHT University Corporation for Atmospheric Research 2004-2005
27// Please read the full copyright statement in the file COPYRIGHT_UCAR.
28//
29// Authors:
30// pwest Patrick West <pwest@ucar.edu>
31// jgarcia Jose Garcia <jgarcia@ucar.edu>
32
33#include "config.h"
34
35#include <unistd.h> // for getopt fork setsid execvp access geteuid
36
37#include <grp.h> // for getgrnam
38#include <pwd.h> // for getpwnam
39
40#include <sys/wait.h> // for waitpid
41#include <sys/stat.h> // for chmod
42#include <cctype> // for isdigit
43#include <csignal>
44
45#include <iostream>
46#include <string>
47#include <sstream>
48#include <cstring>
49#include <cstdlib>
50#include <cerrno>
51#include <map>
52#include <vector>
53
54#include "ServerExitConditions.h"
55#include "SocketListener.h"
56#include "TcpSocket.h"
57#include "UnixSocket.h"
58#include "PPTServer.h"
59#include "BESModuleApp.h"
60#include "DaemonCommandHandler.h"
61#include "BESServerUtils.h"
62#include "BESScrub.h"
63#include "BESError.h"
64#include "BESDebug.h"
65#include "TheBESKeys.h"
66#include "BESLog.h"
67#include "BESDaemonConstants.h"
68#include "BESUtil.h"
69
70#define BES_SERVER "/beslistener"
71#define BES_SERVER_PID "/bes.pid"
72#define DAEMON_PORT_STR "BES.DaemonPort"
73#define DAEMON_UNIX_SOCK_STR "BES.DaemonUnixSocket"
74
75using namespace std;
76
77// Defined in setgroups.c
78extern "C" int set_sups(int target_sups_size, gid_t* target_sups_list);
79
80// These are called from DaemonCommandHandler
81void block_signals();
82void unblock_signals();
83int start_master_beslistener();
84bool stop_all_beslisteners(int sig);
85
86static string daemon_name;
87
88// This two variables are set by load_names
89static string beslistener_path;
90static string file_for_daemon_pid;
91
92// This can be used to see if HUP or TERM has been sent to the master bes
93volatile int master_beslistener_status = BESLISTENER_STOPPED;
94#if 0
95volatile int num_children = 0;
96#endif
97static volatile int master_beslistener_pid = -1; // This is also the process group id
98
99typedef map<string, string> arg_map;
100static arg_map global_args;
101static string debug_sink;
102
103static TcpSocket *my_socket = nullptr;
104static UnixSocket *unix_socket = nullptr;
105static PPTServer *command_server = nullptr;
106
107// These are set to 1 by their respective handlers and then processed in the
108// signal processing loop. jhrg 3/5/14
109static volatile sig_atomic_t sigchild = 0;
110static volatile sig_atomic_t sigterm = 0;
111static volatile sig_atomic_t sighup = 0;
112
113static string errno_str(const string &msg)
114{
115 ostringstream oss;
116 oss << daemon_name << msg;
117 const char *perror_string = strerror(errno);
118 if (perror_string) oss << perror_string;
119 oss << endl;
120 return oss.str();
121}
122
131static int pr_exit(int status)
132{
133 if (WIFEXITED(status)) {
134 switch (WEXITSTATUS(status)) {
135 case SERVER_EXIT_NORMAL_SHUTDOWN:
136 return 0;
137
138 case SERVER_EXIT_FATAL_CANNOT_START:
139 cerr << daemon_name << ": server cannot start, exited with status " << WEXITSTATUS(status) << endl;
140 cerr << "Please check all error messages " << "and adjust server installation" << endl;
141 return 1;
142
143 case SERVER_EXIT_ABNORMAL_TERMINATION:
144 cerr << daemon_name << ": abnormal server termination, exited with status " << WEXITSTATUS(status) << endl;
145 return 1;
146
147 case SERVER_EXIT_RESTART:
148 cerr << daemon_name << ": server has been requested to re-start." << endl;
149 return SERVER_EXIT_RESTART;
150
151 default:
152 return 1;
153 }
154 }
155 else if (WIFSIGNALED(status)) {
156 cerr << daemon_name << ": abnormal server termination, signaled with signal number " << WTERMSIG(status)
157 << endl;
158#ifdef WCOREDUMP
159 if (WCOREDUMP(status)) {
160 cerr << daemon_name << ": server dumped core." << endl;
161 return 1;
162 }
163#endif
164 return 1;
165 }
166 else if (WIFSTOPPED(status)) {
167 cerr << daemon_name << ": abnormal server termination, stopped with signal number " << WSTOPSIG(status) << endl;
168 return 1;
169 }
170
171 return 0;
172}
173
178void block_signals()
179{
180 sigset_t set;
181 sigemptyset(&set);
182 sigaddset(&set, SIGCHLD);
183 sigaddset(&set, SIGHUP);
184 sigaddset(&set, SIGTERM);
185
186 if (sigprocmask(SIG_BLOCK, &set, 0) < 0) {
187 cerr << errno_str(": sigprocmask error, blocking signals in stop_all_beslisteners ");
188 }
189}
190
192void unblock_signals()
193{
194 sigset_t set;
195 sigemptyset(&set);
196 sigaddset(&set, SIGCHLD);
197 sigaddset(&set, SIGHUP);
198 sigaddset(&set, SIGTERM);
199
200 if (sigprocmask(SIG_UNBLOCK, &set, 0) < 0) {
201 cerr << errno_str(": sigprocmask error unblocking signals in stop_all_beslisteners ");
202 }
203}
204
218bool stop_all_beslisteners(int sig)
219{
220 BESDEBUG("besdaemon", "besdaemon: stopping listeners" << endl);
221
222 block_signals();
223
224 BESDEBUG("besdaemon", "besdaemon: master_beslistener_pid " << master_beslistener_pid << endl);
225 // Send 'sig' to all members of the process group with/of the master bes.
226 // The master beslistener pid is the group id of all the beslisteners.
227 int status = killpg(master_beslistener_pid, sig);
228 switch (status) {
229 case EINVAL:
230 cerr << "The sig argument is not a valid signal number." << endl;
231 break;
232
233 case EPERM:
234 cerr
235 << "The sending process is not the super-user and one or more of the target processes has an effective user ID different from that of the sending process."
236 << endl;
237 break;
238
239 case ESRCH:
240 cerr << "No process can be found in the process group specified by the process group ("
241 << master_beslistener_pid << ")." << endl;
242 break;
243
244 default: // No error
245 break;
246 }
247
248 bool mbes_status_caught = false;
249 int pid;
250 while ((pid = wait(&status)) > 0) {
251 BESDEBUG("besdaemon", "besdaemon: caught listener: " << pid << " raw status: " << status << endl);
252 if (pid == master_beslistener_pid) {
253 master_beslistener_status = pr_exit(status);
254 mbes_status_caught = true;
255 BESDEBUG("besdaemon",
256 "besdaemon: caught master beslistener: " << pid << " status: " << master_beslistener_status << endl);
257 }
258 }
259
260 BESDEBUG("besdaemon", "besdaemon: done catching listeners (last pid:" << pid << ")" << endl);
261
262 unblock_signals();
263
264 BESDEBUG("besdaemon", "besdaemon: unblocking signals " << endl);
265
266 return mbes_status_caught;
267}
268
276char **update_beslistener_args()
277{
278 char **arguments = new char*[global_args.size() * 2 + 1];
279
280 // Marshal the arguments to the listener from the command line
281 // arguments to the daemon
282 arguments[0] = strdup(global_args["beslistener"].c_str());
283
284 int i = 1;
285 arg_map::iterator it;
286 for (it = global_args.begin(); it != global_args.end(); ++it) {
287 BESDEBUG("besdaemon", "besdaemon; global_args " << (*it).first << " => " << (*it).second << endl);
288 // Build the complete command line args for the beslistener, with
289 // special case code for -d and to omit the 'beslistener' line
290 // since it's already set in arguments[0].
291 if ((*it).first == "-d") {
292 arguments[i++] = strdup("-d");
293 // This is where the current debug/log settings are grabbed and
294 // used to build the correct '-d' option value for the new
295 // beslistener.
296 string debug_opts = debug_sink + "," + BESDebug::GetOptionsString();
297 arguments[i++] = strdup(debug_opts.c_str());
298 }
299 else if ((*it).first != "beslistener") {
300 arguments[i++] = strdup((*it).first.c_str());
301 arguments[i++] = strdup((*it).second.c_str());
302 }
303 }
304 arguments[i] = 0; // terminal null
305
306 return arguments;
307}
308
321int start_master_beslistener()
322{
323 // The only certain way to know that the beslistener master has started is
324 // to pass back its status once it is initialized. Use a pipe for that.
325 int pipefd[2];
326 if (pipe(pipefd) < 0) {
327 cerr << errno_str(": pipe error ");
328 return 0;
329 }
330
331 int pid;
332 if ((pid = fork()) < 0) {
333 cerr << errno_str(": fork error ");
334 return 0;
335 }
336 else if (pid == 0) { // child process (the master beslistener)
337 // See 'int ServerApp::run()' for the place where the program exec'd
338 // below writes the pid value to the pipe.
339
340 close(pipefd[0]); // Close the read end of the pipe in the child
341
342 // dup2 so we know the FD to write to in the child (the beslistener).
343 // BESLISTENER_PIPE_FD is '1' which is stdout; since beslistener is a
344 // daemon process both stdin and out have been closed so these descriptors
345 // are available. Using higher numbers can cause problems (see ticket
346 // 1783). jhrg 7/15/11
347 if (dup2(pipefd[1], MASTER_TO_DAEMON_PIPE_FD) != MASTER_TO_DAEMON_PIPE_FD) {
348 cerr << errno_str(": dup2 error ");
349 return 0;
350 }
351
352 // We don't have to free this because this is a different process
353 // than the parent.
354 char **arguments = update_beslistener_args();
355
356 BESDEBUG("besdaemon", "Starting: " << arguments[0] << endl);
357
358 // Close the socket for the besdaemon here. This keeps it from being
359 // passed into the master beslistener and then entering the state
360 // CLOSE_WAIT once the besdaemon's client closes its end.
361 if (command_server) command_server->closeConnection();
362
363 // This is where beslistener - the master listener - is started
364 execvp(arguments[0], arguments);
365
366 // if we are still here, it's an error...
367 cerr << errno_str(": mounting listener, subprocess failed: ");
368 exit(1); //NB: This exits from the child process.
369 }
370
371 // parent process (the besdaemon)
372
373 // The daemon records the pid of the master beslistener, but only does so
374 // when that process writes its status to the pipe 'fd'.
375
376 close(pipefd[1]); // close the write end of the pipe in the parent.
377
378 BESDEBUG("besdaemon", "besdaemon: master beslistener pid: " << pid << endl);
379
380 // Read the status from the child (beslistener).
381 int beslistener_start_status;
382 long status = read(pipefd[0], &beslistener_start_status, sizeof(beslistener_start_status));
383
384 if (status < 0) {
385 cerr << "Could not read master beslistener status; the master pid was not changed." << endl;
386 close(pipefd[0]);
387 return 0;
388 }
389 else if (beslistener_start_status != BESLISTENER_RUNNING) {
390 cerr << "The beslistener status is not 'BESLISTENER_RUNNING' (it is '" << beslistener_start_status
391 << "') the master pid was not changed." << endl;
392 close(pipefd[0]);
393 return 0;
394 }
395 else {
396 BESDEBUG("besdaemon", "besdaemon: master beslistener start status: " << beslistener_start_status << endl);
397 // Setting master_beslistener_pid here and not forcing callers to use the
398 // return value means that this global can be local to this file.
399 master_beslistener_pid = pid;
400 master_beslistener_status = BESLISTENER_RUNNING;
401 }
402
403 close(pipefd[0]);
404 return pid;
405}
406
410static void cleanup_resources()
411{
412 // TOCTOU error. Since the code ignores the error code from
413 // remove(), we might as well drop the test. We could test for an
414 // error and print a warning to the log... jhrg 10/23/15
415#if 0
416 if (!access(file_for_daemon_pid.c_str(), F_OK)) {
417 (void) remove(file_for_daemon_pid.c_str());
418 }
419#endif
420
421 (void) remove(file_for_daemon_pid.c_str());
422}
423
424// Note that SIGCHLD, SIGTERM and SIGHUP are blocked while in these three
425// signal handlers below.
426
427static void catch_sig_child(int signal)
428{
429 if (signal == SIGCHLD) {
430 sigchild = 1;
431 }
432}
433
434static void catch_sig_hup(int signal)
435{
436 if (signal == SIGHUP) {
437 sighup = 1;
438 }
439}
440
441static void catch_sig_term(int signal)
442{
443 if (signal == SIGTERM) {
444 sigterm = 1;
445 }
446}
447
448static void process_signals()
449{
450 block_signals();
451
452 // Process SIGCHLD. This is used to detect if the HUP signal was sent to the
453 // master listener and it has returned SERVER_EXIT_RESTART by recording
454 // that value in the global 'master_beslistener_status'. Other code needs
455 // to test that (static) global to see if the beslistener should be restarted.
456 if (sigchild) {
457 int status;
458 int pid = wait(&status);
459
460 // Decode and record the exit status, but only if it really is the
461 // master beslistener this daemon is using. If two or more Start commands
462 // are sent in a row, a master beslistener will start, fail to bind to
463 // the port (because another master beslstener is already bound to it)
464 // and exit. We don't want to record that second process's exit status here.
465 if (pid == master_beslistener_pid) master_beslistener_status = pr_exit(status);
466
467 sigchild = 0;
468 }
469
470 // The two following signals implement a simple stop/restart behavior
471 // for the daemon. The TERM signal (which is the default for the 'kill'
472 // command) is used to stop the entire server, including the besdaemon. The HUP
473 // signal is used to stop all beslisteners and then restart the master
474 // beslistener, forcing a re-read of the config file. Note that the daemon
475 // does not re-read the config file.
476
477 // When the daemon gets the HUP signal, it forwards that to each beslistener.
478 // They then all exit, returning the 'restart' code so that the daemon knows
479 // to restart the master beslistener.
480 if (sighup) {
481 // restart the beslistener(s); read their exit status
482 stop_all_beslisteners(SIGHUP);
483
484 // FIXME jhrg 3/5/14
485 if (start_master_beslistener() == 0) {
486 cerr << "Could not restart the master beslistener." << endl;
487 stop_all_beslisteners(SIGTERM);
488 cleanup_resources();
489 exit(1);
490 }
491
492 sighup = 0;
493 }
494
495 // When TERM (the default for 'kill') is sent to this process, send it also
496 // to each beslistener. This will cause the beslisteners to all exit with a zero
497 // value (the code for 'do not restart').
498 if (sigterm) {
499 // Stop all the beslistener(s); read their exit status
500 stop_all_beslisteners(SIGTERM);
501
502 // FIXME jhrg 3/5/14
503 cleanup_resources();
504 // Once all the child exit status values are read, exit the daemon
505 exit(0);
506 }
507
508 unblock_signals();
509}
510
522static int start_command_processor(DaemonCommandHandler &handler)
523{
524 BESDEBUG("besdaemon", "besdaemon: Starting command processor." << endl);
525
526 try {
527 SocketListener listener;
528
529 string port_str;
530 bool port_found;
531 long port = 0;
532 TheBESKeys::TheKeys()->get_value(DAEMON_PORT_STR, port_str, port_found);
533 if (port_found) {
534 char *ptr;
535 port = strtol(port_str.c_str(), &ptr, 10);
536 if (port == 0) {
537 cerr << "Invalid port number for daemon command interface: " << port_str << endl;
538 exit(1);
539 }
540 }
541
542 if (port) {
543 BESDEBUG("besdaemon", "besdaemon: listening on port: " << port << endl);
544 my_socket = new TcpSocket(port);
545 listener.listen(my_socket);
546 }
547
548 string usock_str;
549 bool usock_found;
550 TheBESKeys::TheKeys()->get_value(DAEMON_UNIX_SOCK_STR, usock_str, usock_found);
551
552 if (!usock_str.empty()) {
553 BESDEBUG("besdaemon", "besdaemon: listening on unix socket: " << usock_str << endl);
554 unix_socket = new UnixSocket(usock_str);
555 listener.listen(unix_socket);
556 }
557
558 if (!port_found && !usock_found) {
559 BESDEBUG("besdaemon", "Neither a port nor a unix socket was set for the daemon command interface." << endl);
560 return 0;
561 }
562
563 BESDEBUG("besdaemon", "besdaemon: starting command interface on port: " << port << endl);
564 command_server = new PPTServer(&handler, &listener, /*is_secure*/false);
565
566 // Once initialized, 'handler' loops until it's told to exit.
567 while (true) {
568 process_signals();
569
570 command_server->initConnection();
571 }
572 }
573 catch (BESError &se) {
574 cerr << "daemon: " << se.get_message() << endl;
575 }
576 catch (...) {
577 cerr << "daemon: " << "caught unknown exception" << endl;
578 }
579
580 // Once the handler exits, close sockets and free memory
581 command_server->closeConnection();
582
583 delete command_server;
584 command_server = nullptr;
585
586 // delete closes the sockets
587 delete my_socket;
588 my_socket = nullptr;
589 delete unix_socket;
590 unix_socket = nullptr;
591
592 // When/if the command interpreter exits, stop the all listeners.
593 stop_all_beslisteners(SIGTERM);
594
595 return 1;
596}
597
606static void register_signal_handlers()
607{
608 struct sigaction act;
609
610 // block child, term and hup in the handlers
611 sigemptyset(&act.sa_mask);
612 sigaddset(&act.sa_mask, SIGCHLD);
613 sigaddset(&act.sa_mask, SIGTERM);
614 sigaddset(&act.sa_mask, SIGHUP);
615 act.sa_flags = 0;
616#ifdef SA_RESTART
617 BESDEBUG("besdaemon", "besdaemon: setting restart for sigchld." << endl);
618 act.sa_flags |= SA_RESTART;
619#endif
620
621 act.sa_handler = catch_sig_child;
622 if (sigaction(SIGCHLD, &act, 0)) {
623 cerr << "Could not register a handler to catch beslistener status." << endl;
624 exit(1);
625 }
626
627 act.sa_handler = catch_sig_term;
628 if (sigaction(SIGTERM, &act, 0) < 0) {
629 cerr << "Could not register a handler to catch the terminate signal." << endl;
630 exit(1);
631 }
632
633 act.sa_handler = catch_sig_hup;
634 if (sigaction(SIGHUP, &act, 0) < 0) {
635 cerr << "Could not register a handler to catch the hang-up signal." << endl;
636 exit(1);
637 }
638}
639
646static int daemon_init()
647{
648 pid_t pid;
649 if ((pid = fork()) < 0) // error
650 return -1;
651 else if (pid != 0) // parent exits
652 exit(0);
653 setsid(); // child establishes its own process group
654 return 0;
655}
656
663static void store_daemon_id(int pid)
664{
665 ofstream f(file_for_daemon_pid.c_str());
666 if (!f) {
667 cerr << errno_str(": unable to create pid file " + file_for_daemon_pid + ": ");
668 }
669 else {
670 f << pid << endl;
671 f.close();
672 mode_t new_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
673 (void) chmod(file_for_daemon_pid.c_str(), new_mode);
674 }
675}
676
685static bool load_names(const string &install_dir, const string &pid_dir)
686{
687 string bindir = "/bin";
688 if (!pid_dir.empty()) {
689 file_for_daemon_pid = pid_dir;
690 }
691
692 if (!install_dir.empty()) {
693 beslistener_path = install_dir;
694 beslistener_path += bindir;
695 if (file_for_daemon_pid.empty()) {
696 file_for_daemon_pid = install_dir + "/var/run";
697 // Added jhrg 2/9/12 ... and removed 1/31/19. The special dir breaks
698 // systemctl/systemd on CentOS 7. We might be able to tweak things so
699 // it would work, but I'm switching back to what other daemons do. jhrg
700 // file_for_daemon_pid = install_dir + "/var/run/bes";
701
702 }
703 }
704 else {
705 string prog = daemon_name;
706 string::size_type slash = prog.find_last_of('/');
707 if (slash != string::npos) {
708 beslistener_path = prog.substr(0, slash);
709 slash = prog.find_last_of('/');
710 if (slash != string::npos) {
711 string root = prog.substr(0, slash);
712 if (file_for_daemon_pid.empty()) {
713 file_for_daemon_pid = root + "/var/run";
714 // Added jhrg 2/9/12. See about 1/31/19 jhrg
715 // file_for_daemon_pid = root + "/var/run/bes";
716 }
717 }
718 else {
719 if (file_for_daemon_pid.empty()) {
720 file_for_daemon_pid = beslistener_path;
721 }
722 }
723 }
724 }
725
726 if (beslistener_path.empty()) {
727 beslistener_path = ".";
728 if (file_for_daemon_pid.empty()) {
729 file_for_daemon_pid = "./run";
730 }
731 }
732
733 beslistener_path += BES_SERVER;
734 file_for_daemon_pid += BES_SERVER_PID;
735
736 if (access(beslistener_path.c_str(), F_OK) != 0) {
737 cerr << daemon_name << ": cannot find " << beslistener_path << endl
738 << "Please either pass -i <install_dir> on the command line." << endl;
739 return false;
740 }
741
742 // Record the name for use when building the arg list for the beslistener
743 global_args["beslistener"] = beslistener_path;
744
745 return true;
746}
747
748static void set_group_id()
749{
750#if !defined(OS2) && !defined(TPF)
751 // OS/2 and TPF don't support groups.
752
753 // get group id or name from BES configuration file
754 // If BES.Group begins with # then it is a group id,
755 // else it is a group name and look up the id.
756 BESDEBUG("server", "beslistener: Setting group id ... " << endl);
757 bool found = false;
758 string key = "BES.Group";
759 string group_str;
760 try {
761 TheBESKeys::TheKeys()->get_value(key, group_str, found);
762 }
763 catch (BESError &e) {
764 BESDEBUG("server", "beslistener: FAILED" << endl);
765 string err = string("FAILED: ") + e.get_message();
766 cerr << err << endl;
767 ERROR_LOG(err << endl);
768 exit(SERVER_EXIT_FATAL_CANNOT_START);
769 }
770
771 if (!found || group_str.empty()) {
772 BESDEBUG("server", "beslistener: FAILED" << endl);
773 string err = "FAILED: Group not specified in BES configuration file";
774 cerr << err << endl;
775 ERROR_LOG(err << endl);
776 exit(SERVER_EXIT_FATAL_CANNOT_START);
777 }
778 BESDEBUG("server", "to " << group_str << " ... " << endl);
779
780 gid_t new_gid = 0;
781 if (group_str[0] == '#') {
782 // group id starts with a #, so is a group id
783 const char *group_c = group_str.c_str();
784 group_c++;
785 new_gid = atoi(group_c);
786 }
787 else {
788 // specified group is a group name
789#if 0
790 struct group *ent;
791 // FIXME replace getgrname() and getpwnam() with the _r versions. jhrg 8/11/21
792 ent = getgrnam(group_str.c_str());
793#endif
794 struct group in;
795 struct group *result = nullptr;
796 vector<char> buffer(1024);
797 int rc = getgrnam_r(group_str.c_str(), &in, buffer.data(), buffer.size(), &result);
798 if (rc != 0 || result == nullptr) {
799 BESDEBUG("server", "beslistener: FAILED" << endl);
800 string err = string( "FAILED: Group ") + group_str + " does not exist (" + strerror(errno) + ").";
801 cerr << err << endl;
802 ERROR_LOG(err << endl);
803 exit(SERVER_EXIT_FATAL_CANNOT_START);
804 }
805 new_gid = result->gr_gid;
806 }
807
808 if (new_gid < 1) {
809 BESDEBUG("server", "beslistener: FAILED" << endl);
810 ostringstream err;
811 err << "FAILED: Group id " << new_gid << " not a valid group id for BES";
812 cerr << err.str() << endl;
813 ERROR_LOG(err.str() << endl);
814 exit(SERVER_EXIT_FATAL_CANNOT_START);
815 }
816
817 BESDEBUG("server", "to id " << new_gid << " ... " << endl);
818 if (setgid(new_gid) == -1) {
819 BESDEBUG("server", "beslistener: FAILED" << endl);
820 ostringstream err;
821 err << "FAILED: unable to set the group id to " << new_gid;
822 cerr << err.str() << endl;
823 ERROR_LOG(err.str() << endl);
824 exit(SERVER_EXIT_FATAL_CANNOT_START);
825 }
826
827 BESDEBUG("server", "OK" << endl);
828#else
829 BESDEBUG( "server", "beslistener: Groups not supported in this OS" << endl );
830#endif
831}
832
833static void set_user_id()
834{
835 BESDEBUG("server", "beslistener: Setting user id ... " << endl);
836
837 // Get user name or id from the BES configuration file.
838 // If the BES.User value begins with # then it is a user
839 // id, else it is a user name and need to look up the
840 // user id.
841 bool found = false;
842 string key = "BES.User";
843 string user_str;
844 try {
845 TheBESKeys::TheKeys()->get_value(key, user_str, found);
846 }
847 catch (BESError &e) {
848 BESDEBUG("server", "beslistener: FAILED" << endl);
849 string err = (string) "FAILED: " + e.get_message();
850 cerr << err << endl;
851 ERROR_LOG(err << endl);
852 exit(SERVER_EXIT_FATAL_CANNOT_START);
853 }
854
855 if (!found || user_str.empty()) {
856 BESDEBUG("server", "beslistener: FAILED" << endl);
857 auto err = (string) "FAILED: User not specified in BES config file";
858 cerr << err << endl;
859 ERROR_LOG(err << endl);
860 exit(SERVER_EXIT_FATAL_CANNOT_START);
861 }
862 BESDEBUG("server", "to " << user_str << " ... " << endl);
863
864 uid_t new_id = 0;
865 if (user_str[0] == '#') {
866 const char *user_str_c = user_str.c_str();
867 user_str_c++;
868 new_id = atoi(user_str_c);
869 }
870 else {
871#if 0
872 struct passwd *ent;
873 ent = getpwnam(user_str.c_str());
874#endif
875
876 struct passwd in;
877 struct passwd *result = nullptr;
878 vector<char> buffer(1024);
879 int rc = getpwnam_r(user_str.c_str(), &in, buffer.data(), buffer.size(), &result);
880 if (rc != 0 || result == nullptr) {
881 BESDEBUG("server", "beslistener: FAILED" << endl);
882 string err = (string) "FAILED: Bad user name specified: " + user_str + "(" + strerror(errno) + ").";
883 cerr << err << endl;
884 ERROR_LOG(err << endl);
885 exit(SERVER_EXIT_FATAL_CANNOT_START);
886 }
887 new_id = result->pw_uid;
888 }
889
890 // new user id cannot be root (0)
891 if (!new_id) {
892 BESDEBUG("server", "beslistener: FAILED" << endl);
893 auto err = (string) "FAILED: BES cannot run as root";
894 cerr << err << endl;
895 ERROR_LOG(err << endl);
896 exit(SERVER_EXIT_FATAL_CANNOT_START);
897 }
898
899 // Right before we relinquish root, remove any 'supplementary groups'
900 //int set_sups(const int target_sups_size, const gid_t* const target_sups_list)
901 vector<gid_t> groups(1);
902 groups.at(0) = getegid();
903 if (set_sups(groups.size(), groups.data()) == -1) {
904 BESDEBUG("server", "beslistener: FAILED" << endl);
905 ostringstream err;
906 err << "FAILED: Unable to relinquish supplementary groups (" << new_id << ")";
907 cerr << err.str() << endl;
908 ERROR_LOG(err.str() << endl);
909 exit(SERVER_EXIT_FATAL_CANNOT_START);
910 }
911
912 BESDEBUG("server", "to " << new_id << " ... " << endl);
913 if (setuid(new_id) == -1) {
914 BESDEBUG("server", "beslistener: FAILED" << endl);
915 ostringstream err;
916 err << "FAILED: Unable to set user id to " << new_id;
917 cerr << err.str() << endl;
918 ERROR_LOG(err.str() << endl);
919 exit(SERVER_EXIT_FATAL_CANNOT_START);
920 }
921
922 BESDEBUG("server", "OK" << endl);
923}
924
928int main(int argc, char *argv[])
929{
930 uid_t curr_euid = geteuid();
931
932#ifndef BES_DEVELOPER
933 // must be root to run this app and to set user id and group id later
934 if (curr_euid) {
935 cerr << "FAILED: Must be root to run BES" << endl;
936 exit(SERVER_EXIT_FATAL_CANNOT_START);
937 }
938#else
939 cerr << "Developer Mode: Not testing if BES is run by root" << endl;
940#endif
941
942 daemon_name = "besdaemon";
943
944 string install_dir;
945 string pid_dir;
946
947 bool become_daemon = true;
948
949 // there are 16 arguments allowed to the daemon, including the program
950 // name. 3 options do not have arguments and 6 have arguments
951 if (argc > 16) {
952 // the show_usage method exits
953 BESServerUtils::show_usage(daemon_name);
954 }
955
956 try {
957 // Most of the argument processing is just for vetting the arguments
958 // that will be passed onto the beslistener(s), but we do grab some info
959 string config_file;
960 // argv[0] is the name of the program, so start num_args at 1
961 unsigned short num_args = 1;
962
963 // If you change the getopt statement below, be sure to make the
964 // corresponding change in ServerApp.cc and besctl.in
965 int c = 0;
966 while ((c = getopt(argc, argv, "hvsd:c:p:u:i:r:n")) != -1) {
967 switch (c) {
968 case 'v': // version
969 BESServerUtils::show_version(daemon_name);
970 break;
971 case '?': // unknown option
972 case 'h': // help
973 BESServerUtils::show_usage(daemon_name);
974 break;
975 case 'n': // no-daemon (Do Not Become A daemon process)
976 become_daemon=false;
977 cerr << "Running in foreground!" << endl;
978 num_args++;
979 break;
980 case 'i': // BES install directory
981 install_dir = optarg;
982 if (!BESScrub::pathname_ok(install_dir, true)) {
983 cout << "The specified install directory (-i option) "
984 << "is incorrectly formatted. Must be less than "
985 << "255 characters and include the characters " << "[0-9A-z_./-]" << endl;
986 return 1;
987 }
988 global_args["-i"] = install_dir;
989 num_args += 2;
990 break;
991 case 's': // secure server
992 global_args["-s"] = "";
993 num_args++;
994 break;
995 case 'r': // where to write the pid file
996 pid_dir = optarg;
997 if (!BESScrub::pathname_ok(pid_dir, true)) {
998 cout << "The specified state directory (-r option) "
999 << "is incorrectly formatted. Must be less than "
1000 << "255 characters and include the characters " << "[0-9A-z_./-]" << endl;
1001 return 1;
1002 }
1003 global_args["-r"] = pid_dir;
1004 num_args += 2;
1005 break;
1006 case 'c': // configuration file
1007 config_file = optarg;
1008 if (!BESScrub::pathname_ok(config_file, true)) {
1009 cout << "The specified configuration file (-c option) "
1010 << "is incorrectly formatted. Must be less than "
1011 << "255 characters and include the characters " << "[0-9A-z_./-]" << endl;
1012 return 1;
1013 }
1014 global_args["-c"] = config_file;
1015 num_args += 2;
1016 break;
1017 case 'u': // unix socket
1018 {
1019 string check_path = optarg;
1020 if (!BESScrub::pathname_ok(check_path, true)) {
1021 cout << "The specified unix socket (-u option) " << "is incorrectly formatted. Must be less than "
1022 << "255 characters and include the characters " << "[0-9A-z_./-]" << endl;
1023 return 1;
1024 }
1025 global_args["-u"] = check_path;
1026 num_args += 2;
1027 break;
1028 }
1029 case 'p': // TCP port
1030 {
1031 string port_num = optarg;
1032 for (unsigned int i = 0; i < port_num.length(); i++) {
1033 if (!isdigit(port_num[i])) {
1034 cout << "The specified port contains non-digit " << "characters: " << port_num << endl;
1035 return 1;
1036 }
1037 }
1038 global_args["-p"] = port_num;
1039 num_args += 2;
1040 }
1041 break;
1042 case 'd': // debug
1043 {
1044 string check_arg = optarg;
1045 if (!BESScrub::command_line_arg_ok(check_arg)) {
1046 cout << "The specified debug options \"" << check_arg << "\" contains invalid characters" << endl;
1047 return 1;
1048 }
1049 BESDebug::SetUp(check_arg);
1050 global_args["-d"] = check_arg;
1051 debug_sink = check_arg.substr(0, check_arg.find(','));
1052 num_args += 2;
1053 break;
1054 }
1055 default:
1056 BESServerUtils::show_usage(daemon_name);
1057 break;
1058 }
1059 }
1060
1061 // if the number of arguments is greater than the number of allowed arguments
1062 // then extra arguments were passed that aren't options. Show usage and
1063 // exit.
1064 if (argc > num_args) {
1065 cout << daemon_name << ": too many arguments passed to the BES";
1066 BESServerUtils::show_usage(daemon_name);
1067 }
1068
1069 if (pid_dir.empty()) {
1070 pid_dir = install_dir;
1071 }
1072
1073 // If the -c option was passed, set the config file name in TheBESKeys
1074 if (!config_file.empty()) {
1075 TheBESKeys::ConfigFile = config_file;
1076 }
1077
1078 // If the -c option was not passed, but the -i option
1079 // was passed, then use the -i option to construct
1080 // the path to the config file
1081 if (config_file.empty() && !install_dir.empty()) {
1083 string conf_file = install_dir + "etc/bes/bes.conf";
1084 TheBESKeys::ConfigFile = conf_file;
1085 }
1086 }
1087 catch (BESError &e) {
1088 // (*BESLog::TheLog())
1089 // BESLog::TheLog throws exceptions...
1090 cerr << "Caught BES Error while processing the daemon's options: " << e.get_message() << endl;
1091 return 1;
1092 }
1093 catch (const std::exception &e) {
1094 cerr << "Caught C++ error while processing the daemon's options: " << e.what() << endl;
1095 return 2;
1096 }
1097 catch (...) {
1098 cerr << "Caught unknown error while processing the daemon's options." << endl;
1099 return 3;
1100 }
1101
1102 try {
1103 // Set the name of the listener and the file for the listener pid
1104 if (!load_names(install_dir, pid_dir)) return 1;
1105
1106 if (!access(file_for_daemon_pid.c_str(), F_OK)) {
1107 ifstream temp(file_for_daemon_pid.c_str());
1108 cout << daemon_name << ": there seems to be a BES daemon already running at ";
1109 char buf[500];
1110 temp.getline(buf, 500);
1111 cout << buf << endl;
1112 temp.close();
1113 return 1;
1114 }
1115
1116 if(become_daemon){
1117 daemon_init();
1118 }
1119
1120 store_daemon_id(getpid());
1121
1122 if (curr_euid == 0) {
1123#ifdef BES_DEVELOPER
1124 cerr << "Developer Mode: Running as root - setting group and user ids" << endl;
1125#endif
1126 set_group_id();
1127 set_user_id();
1128 }
1129 else {
1130 cerr << "Developer Mode: Not setting group or user ids" << endl;
1131 }
1132
1133 register_signal_handlers();
1134
1135 // Load the modules in the conf file(s) so that the debug (log) contexts
1136 // will be available to the BESDebug singleton so we can tell the OLFS/HAI
1137 // about them. Then Register the 'besdaemon' context.
1138 BESModuleApp app;
1139 if (app.initialize(argc, argv) != 0) {
1140 cerr << "Could not initialize the modules to get the log contexts." << endl;
1141 }
1142 BESDebug::Register("besdaemon");
1143
1144 // These are from the beslistener - they are valid contexts but are not
1145 // registered by a module. See ServerApp.cc
1146 BESDebug::Register("server");
1147 BESDebug::Register("ppt");
1148
1149
1150 // The stuff in global_args is used whenever a call to start_master_beslistener()
1151 // is made, so any time the BESDebug contexts are changed, a change to the
1152 // global_args will change the way the the beslistener is started. In fact,
1153 // it's not limited to the debug stuff, but that's we're using it for now.
1154 // jhrg 6/16/11
1155
1156 // The -d option was not given; add one setting up a default log sink using
1157 // the log file from the bes.conf file or the name "LOG".
1158 if (global_args.count("-d") == 0) {
1159 bool found = false;
1160 TheBESKeys::TheKeys()->get_value("BES.LogName", debug_sink, found);
1161 if (!found) {
1162 // This is a crude fallback that avoids a value without any name
1163 // for a log file (which would be a syntax error).
1164 global_args["-d"] = "cerr," + BESDebug::GetOptionsString();
1165 }
1166 else {
1167 // I use false for the 'created' flag so that subsequent changes to the
1168 // debug stream won't do odd things like delete the ostream pointer.
1169 // Note that the beslistener has to recognize that "LOG" means to use
1170 // the bes.log file for a debug/log sink
1171 BESDebug::SetStrm(BESLog::TheLog()->get_log_ostream(), false);
1172
1173 global_args["-d"] = debug_sink + "," + BESDebug::GetOptionsString();
1174 }
1175 }
1176 // The option was given; use the token read from the options for the sink
1177 // so that the beslistener will open the correct thing.
1178 else {
1179 global_args["-d"] = debug_sink + "," + BESDebug::GetOptionsString();
1180 }
1181
1182 // master_beslistener_pid is global so that the signal handlers can use it;
1183 // it is actually assigned a value in start_master_beslistener but it's
1184 // assigned here to make it clearer what's going on.
1185 master_beslistener_pid = start_master_beslistener();
1186 if (master_beslistener_pid == 0) {
1187 cerr << daemon_name << ": server cannot mount at first try (core dump). "
1188 << "Please correct problems on the process manager " << beslistener_path << endl;
1189 return master_beslistener_pid;
1190 }
1191
1192 BESDEBUG("besdaemon", "besdaemon: master_beslistener_pid: " << master_beslistener_pid << endl);
1193 }
1194 catch (BESError &e) {
1195 cerr << "Caught BES Error during initialization: " << e.get_message() << endl;
1196 return 1;
1197 }
1198 catch (const std::exception &e) {
1199 cerr << "Caught C++ error during initialization: " << e.what() << endl;
1200 return 2;
1201 }
1202 catch (...) {
1203 cerr << "Caught unknown error during initialization." << endl;
1204 return 3;
1205 }
1206
1207 int status = 0;
1208 try {
1209 // start_command_processor() does not return unless all commands have been
1210 // processed and the daemon has been told to exit (status == 1) or the
1211 // bes.conf file was set so that the processor never starts (status == 0).
1213 status = start_command_processor(handler);
1214
1215 // if the command processor does not start, drop into this loop which
1216 // implements the simple restart-on-HUP behavior of the daemon.
1217 if (status == 0) {
1218 bool done = false;
1219 while (!done) {
1220 pause();
1221
1222 process_signals();
1223
1224 BESDEBUG("besdaemon", "besdaemon: master_beslistener_status: " << master_beslistener_status << endl);
1225 if (master_beslistener_status == BESLISTENER_RESTART) {
1226 master_beslistener_status = BESLISTENER_STOPPED;
1227 // master_beslistener_pid = start_master_beslistener();
1228 start_master_beslistener();
1229 }
1230 // If the status is not 'restart' and not running, then exit loop
1231 else if (master_beslistener_status != BESLISTENER_RUNNING) {
1232 done = true;
1233 }
1234 }
1235 }
1236 }
1237 catch (BESError &e) {
1238 status = 1;
1239 // (*BESLog::TheLog())
1240 // BESLog::TheLog throws exceptions...
1241 cerr << "Caught BES Error while starting the command handler: " << e.get_message() << endl;
1242 }
1243 catch (const std::exception &e) {
1244 status = 2;
1245 cerr << "Caught C++ error while starting the command handler: " << e.what() << endl;
1246 }
1247 catch (...) {
1248 status = 3;
1249 cerr << "Caught unknown error while starting the command handler." << endl;
1250 }
1251
1252 BESDEBUG("besdaemon", "besdaemon: past the command processor start" << endl);
1253
1254 cleanup_resources();
1255
1256 return status;
1257}
1258
static void SetStrm(std::ostream *strm, bool created)
set the debug output stream to the specified stream
Definition: BESDebug.h:209
static void SetUp(const std::string &values)
Sets up debugging for the bes.
Definition: BESDebug.cc:98
static void Register(const std::string &flagName)
register the specified debug flag
Definition: BESDebug.h:149
static std::string GetOptionsString()
Definition: BESDebug.cc:196
Base exception class for the BES with basic string message.
Definition: BESError.h:59
std::string get_message() const
get the error message for this exception
Definition: BESError.h:111
Base application object for all BES applications.
Definition: BESModuleApp.h:55
int initialize(int argC, char **argV) override
Load and initialize any BES modules.
Definition: BESModuleApp.cc:71
static bool pathname_ok(const std::string &path, bool strict)
Does the string name a potentailly valid pathname? Test the given pathname to verfiy that it is a val...
Definition: BESScrub.cc:92
static bool command_line_arg_ok(const std::string &arg)
sanitize command line arguments
Definition: BESScrub.cc:56
static void trim_if_trailing_slash(std::string &value)
If the string ends in a slash, remove it This function works for empty strings (doing nothing)....
Definition: BESUtil.cc:110
void initConnection() override
Definition: PPTServer.cc:140
void get_value(const std::string &s, std::string &val, bool &found)
Retrieve the value of a given key, if set.
Definition: TheBESKeys.cc:340
static TheBESKeys * TheKeys()
Definition: TheBESKeys.cc:71
static std::string ConfigFile
Definition: TheBESKeys.h:185