core/sems.cpp
a2a334f1
 /*
  * Copyright (C) 2002-2003 Fhg Fokus
  *
7dcb7e2a
  * This file is part of SEMS, a free SIP media server.
a2a334f1
  *
7dcb7e2a
  * SEMS is free software; you can redistribute it and/or modify
a2a334f1
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
7dcb7e2a
  * (at your option) any later version. This program is released under
  * the GPL with the additional exemption that compiling, linking,
  * and/or using OpenSSL is allowed.
a2a334f1
  *
7dcb7e2a
  * For a license to use the SEMS software under conditions
a2a334f1
  * other than those described here, or to purchase support for this
  * software, please contact iptel.org by e-mail at the following addresses:
  *    info@iptel.org
  *
7dcb7e2a
  * SEMS is distributed in the hope that it will be useful,
a2a334f1
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License 
  * along with this program; if not, write to the Free Software 
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "sems.h"
 #include "AmUtils.h"
 #include "AmConfig.h"
 #include "AmPlugIn.h"
37abd537
 #include "AmSessionContainer.h"
bbfe61da
 #include "AmMediaProcessor.h"
a2a334f1
 #include "AmRtpReceiver.h"
d9884e05
 #include "AmEventDispatcher.h"
f3e1cc8b
 #include "AmSessionProcessor.h"
a2a334f1
 
ccec6fe3
 #ifdef WITH_ZRTP
 # include "AmZRTP.h"
 #endif
6f44a030
 
00aa71fa
 #include "SipCtrlInterface.h"
 
a2a334f1
 #include "log.h"
 
 #include <unistd.h>
 #include <stdio.h>
 #include <errno.h>
 #include <signal.h>
 
 #include <grp.h>
 #include <pwd.h>
 
6aaeb6ec
 //#include <sys/wait.h>
 //#include <sys/socket.h>
37abd537
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
a2a334f1
 
 #include <string>
 using std::string;
 
37abd537
 
ccec6fe3
 const char* progname = NULL;    /**< Program name (actually argv[0])*/
 int main_pid = 0;               /**< Main process PID */
a2a334f1
 
ccec6fe3
 /** SIP stack (controller interface) */
 static SipCtrlInterface sip_ctrl;
a2a334f1
 
 
ccec6fe3
 static void print_usage(bool short_=false)
 {
   if (short_) {
     printf("Usage: %s [OPTIONS]\n"
            "Try `%s -h' for more information.\n",
            progname, progname);
   }
   else {
     printf(
         DEFAULT_SIGNATURE "\n"
         "Usage: %s [OPTIONS]\n"
         "Available options:\n"
         "    -f <file>       Set configuration file\n"
         "    -x <dir>        Set path for plug-ins\n"
         "    -d <device/ip>  Set network device (or IP address) for media advertising\n"
 #ifndef DISABLE_DAEMON_MODE
         "    -E              Enable debug mode (do not daemonize, log to stderr).\n"
         "    -P <file>       Set PID file\n"
         "    -u <uid>        Set user ID\n"
         "    -g <gid>        Set group ID\n"
 #else
         "    -E              Enable debug mode (log to stderr)\n"
 #endif
         "    -D <level>      Set log level (0=error, 1=warning, 2=info, 3=debug; default=%d)\n"
         "    -v              Print version\n"
         "    -h              Print this help\n",
         progname, AmConfig::LogLevel
     );
   }
 }
a2a334f1
 
ccec6fe3
 /* Note: The function should not use log because it is called before logging is initialized. */
2ee9a904
 static bool parse_args(int argc, char* argv[], std::map<char,string>& args)
a2a334f1
 {
2ee9a904
 #ifndef DISABLE_DAEMON_MODE
     static const char* opts = ":hvEf:x:d:D:u:g:P:";
 #else    
     static const char* opts = ":hvEf:x:d:D:";
 #endif    
 
     opterr = 0;
     
     while (true) {
 	int c = getopt(argc, argv, opts);
 	switch (c) {
 	case -1:
 	    return true;
 	    
 	case ':':
 	    fprintf(stderr, "%s: missing argument for option '-%c'\n", progname, optopt);
 	    return false;
 	    
 	case '?':
 	    fprintf(stderr, "%s: unknown option '-%c'\n", progname, optopt);
 	    return false;
 	    
 	default:
 	    args[c] = (optarg ? optarg : "yes");
 	}
ccec6fe3
     }
 }
d31ef867
 
ccec6fe3
 /* Note: The function should not use logging because it is called before
    the logging is initialized. */
 static bool apply_args(std::map<char,string>& args)
 {
   for(std::map<char,string>::iterator it = args.begin();
       it != args.end(); ++it){
d9884e05
 
ccec6fe3
     switch( it->first ){
     case 'd':
6aaeb6ec
       AmConfig::Ifs[0].LocalIP = it->second;
ccec6fe3
       break;
d9884e05
 
ccec6fe3
     case 'D':
       if (!AmConfig::setLogLevel(it->second)) {
         fprintf(stderr, "%s: invalid log level: %s\n", progname, it->second.c_str());
         return false;
       }
       break;
e78c8992
 
ccec6fe3
     case 'E':
 #ifndef DISABLE_DAEMON_MODE
      AmConfig::DaemonMode = false;
 #endif
      if (!AmConfig::setLogStderr("yes")) {
        return false;
      }
      break;
d9884e05
 
ccec6fe3
     case 'f':
       AmConfig::ConfigurationFile = it->second;
       break;
d9884e05
 
ccec6fe3
     case 'x':
       AmConfig::PlugInPath = it->second;
       break;
d9884e05
 
ccec6fe3
 #ifndef DISABLE_DAEMON_MODE
     case 'P':
       AmConfig::DaemonPidFile = it->second;
       break;
d8b9692f
 
ccec6fe3
     case 'u':
       AmConfig::DaemonUid = it->second;
       break;
 
     case 'g':
       AmConfig::DaemonGid = it->second;
       break;
 #endif
 
     case 'h':
     case 'v':
     default:
       /* nothing to apply */
       break;
     }
7c964b9b
   }
a2a334f1
 
ccec6fe3
   return true;
a2a334f1
 }
 
ccec6fe3
 /** Flag to mark the shutdown is in progress (in the main process) */
 static AmCondition<bool> is_shutting_down(false);
 
 static void signal_handler(int sig)
a2a334f1
 {
5a577215
   if (sig == SIGUSR1 || sig == SIGUSR2) {
     DBG("brodcasting User event to %u sessions...\n",
 	AmSession::getSessionNum());
     AmEventDispatcher::instance()->
       broadcast(new AmSystemEvent(sig == SIGUSR1? 
 				  AmSystemEvent::User1 : AmSystemEvent::User2));
f1b8362e
     return;
   }
 
ccec6fe3
   if (sig == SIGCHLD && AmConfig::IgnoreSIGCHLD) {
     return;
7c964b9b
   }
a2a334f1
 
f300e8ec
   if (sig == SIGPIPE && AmConfig::IgnoreSIGPIPE) {
     return;
   }
 
5a577215
   WARN("Signal %s (%d) received.\n", strsignal(sig), sig);
 
   if (sig == SIGHUP) {
     AmSessionContainer::instance()->broadcastShutdown();
     return;
   }
 
434cba1c
   if (sig == SIGTERM) {
     AmSessionContainer::instance()->enableUncleanShutdown();
   }
 
ccec6fe3
   if (main_pid == getpid()) {
     if(!is_shutting_down.get()) {
       is_shutting_down.set(true);
 
       INFO("Stopping SIP stack after signal\n");
       sip_ctrl.stop();
     }
7c964b9b
   }
ccec6fe3
   else {
     /* exit other processes immediately */
     exit(0);
   }
 }
a2a334f1
 
ccec6fe3
 int set_sighandler(void (*handler)(int))
 {
   static int sigs[] = {
5a577215
     SIGHUP, SIGPIPE, SIGINT, SIGTERM, SIGCHLD, SIGUSR1, SIGUSR2, 0
ccec6fe3
   };
 
   for (int* sig = sigs; *sig; sig++) {
     if (signal(*sig, handler) == SIG_ERR ) {
       ERROR("Cannot install signal handler for %s.\n", strsignal(*sig));
       return -1;
     }
7c964b9b
   }
a2a334f1
 
7c964b9b
   return 0;
a2a334f1
 }
 
ccec6fe3
 #ifndef DISABLE_DAEMON_MODE
 
 static int write_pid_file()
a2a334f1
 {
ccec6fe3
   FILE* fpid = fopen(AmConfig::DaemonPidFile.c_str(), "w");
a2a334f1
 
23fdab68
   if (fpid) {
7c964b9b
     string spid = int2str((int)getpid());
23fdab68
     fwrite(spid.c_str(), spid.length(), 1, fpid);
7c964b9b
     fclose(fpid);
     return 0;
   }
   else {
ccec6fe3
     ERROR("Cannot write PID file '%s': %s.\n",
         AmConfig::DaemonPidFile.c_str(), strerror(errno));
7c964b9b
   }
a2a334f1
 
7c964b9b
   return -1;
a2a334f1
 }
 
ccec6fe3
 #endif /* !DISABLE_DAEMON_MODE */
 
 
a2a334f1
 
ccec6fe3
 /*
  * Main
  */
a2a334f1
 int main(int argc, char* argv[])
 {
ccec6fe3
   int success = false;
9411ad3a
   std::map<char,string> args;
a2a334f1
 
ccec6fe3
   progname = strrchr(argv[0], '/');
   progname = (progname == NULL ? argv[0] : progname + 1);
 
2ee9a904
   if(!parse_args(argc, argv, args)){
ccec6fe3
     print_usage(true);
     return 1;
7c964b9b
   }
a2a334f1
 
7c964b9b
   if(args.find('h') != args.end()){
ccec6fe3
     print_usage();
7c964b9b
     return 0;
   }
a2a334f1
 
7c964b9b
   if(args.find('v') != args.end()){
ccec6fe3
     printf("%s\n", DEFAULT_SIGNATURE);
7c964b9b
     return 0;
   }
a0ee8055
 
6aaeb6ec
   // Init default interface
   AmConfig::Ifs.push_back(AmConfig::IP_interface());
 
ccec6fe3
   /* apply command-line options */
   if(!apply_args(args)){
     print_usage(true);
     goto error;
   }
a2a334f1
 
ccec6fe3
   init_logging();
a2a334f1
 
ccec6fe3
   /* load and apply configuration file */
791132ad
   if(AmConfig::readConfiguration()){
     ERROR("Errors occured while reading configuration file: exiting.");
     return -1;
   }
 
ccec6fe3
   log_level = AmConfig::LogLevel;
   log_stderr = AmConfig::LogStderr;
 
   /* re-apply command-line options to override configuration file */
   if(!apply_args(args)){
     goto error;
7c964b9b
   }
 
6aaeb6ec
   if(AmConfig::finalizeIPConfig() < 0)
ccec6fe3
     goto error;
7c964b9b
 
ccec6fe3
   printf("Configuration:\n"
 #ifdef _DEBUG
          "       log level:           %s (%i)\n"
          "       log to stderr:       %s\n"
 #endif
 	 "       configuration file:  %s\n"
 	 "       plug-in path:        %s\n"
 #ifndef DISABLE_DAEMON_MODE
          "       daemon mode:         %s\n"
          "       daemon UID:          %s\n"
          "       daemon GID:          %s\n"
 #endif
 	 "       application:         %s\n"
 	 "\n",
 #ifdef _DEBUG
 	 log_level2str[AmConfig::LogLevel], AmConfig::LogLevel,
          AmConfig::LogStderr ? "yes" : "no",
 #endif
 	 AmConfig::ConfigurationFile.c_str(),
 	 AmConfig::PlugInPath.c_str(),
 #ifndef DISABLE_DAEMON_MODE
 	 AmConfig::DaemonMode ? "yes" : "no",
 	 AmConfig::DaemonUid.empty() ? "<not set>" : AmConfig::DaemonUid.c_str(),
 	 AmConfig::DaemonGid.empty() ? "<not set>" : AmConfig::DaemonGid.c_str(),
 #endif
 	 AmConfig::Application.empty() ? "<not set>" : AmConfig::Application.c_str());
7c964b9b
 
6aaeb6ec
   AmConfig::dump_Ifs();
 
ccec6fe3
 #ifndef DISABLE_DAEMON_MODE
7c964b9b
 
ccec6fe3
   if(AmConfig::DaemonMode){
     if(!AmConfig::DaemonGid.empty()){
7c964b9b
       unsigned int gid;
726ad0b3
       if(str2i(AmConfig::DaemonGid, gid)){
 	struct group* grnam = getgrnam(AmConfig::DaemonGid.c_str());
7c964b9b
 	if(grnam != NULL){
 	  gid = grnam->gr_gid;
 	}
 	else{
e37e499c
 	  ERROR("Cannot find group '%s' in the group database.\n",
726ad0b3
 		AmConfig::DaemonGid.c_str());
ccec6fe3
 	  goto error;
7c964b9b
 	}
       }
       if(setgid(gid)<0){
ccec6fe3
 	ERROR("Cannot change GID to %i: %s.",
7c964b9b
 	      gid,
 	      strerror(errno));
ccec6fe3
 	goto error;
7c964b9b
       }
a2a334f1
     }
 
ccec6fe3
     if(!AmConfig::DaemonUid.empty()){
7c964b9b
       unsigned int uid;
726ad0b3
       if(str2i(AmConfig::DaemonUid, uid)){
 	struct passwd* pwnam = getpwnam(AmConfig::DaemonUid.c_str());
7c964b9b
 	if(pwnam != NULL){
 	  uid = pwnam->pw_uid;
a2a334f1
 	}
7c964b9b
 	else{
e37e499c
 	  ERROR("Cannot find user '%s' in the user database.\n",
726ad0b3
 		AmConfig::DaemonUid.c_str());
ccec6fe3
 	  goto error;
a2a334f1
 	}
7c964b9b
       }
       if(setuid(uid)<0){
ccec6fe3
 	ERROR("Cannot change UID to %i: %s.",
7c964b9b
 	      uid,
 	      strerror(errno));
ccec6fe3
 	goto error;
7c964b9b
       }
     }
a2a334f1
 
7c964b9b
     /* fork to become!= group leader*/
     int pid;
     if ((pid=fork())<0){
23fdab68
       ERROR("Cannot fork: %s.\n", strerror(errno));
ccec6fe3
       goto error;
7c964b9b
     }else if (pid!=0){
       /* parent process => exit*/
       return 0;
     }
     /* become session leader to drop the ctrl. terminal */
     if (setsid()<0){
23fdab68
       ERROR("setsid failed: %s.\n", strerror(errno));
7c964b9b
     }
     /* fork again to drop group  leadership */
     if ((pid=fork())<0){
23fdab68
       ERROR("Cannot fork: %s.\n", strerror(errno));
ccec6fe3
       goto error;
7c964b9b
     }else if (pid!=0){
       /*parent process => exit */
       return 0;
     }
 	
ccec6fe3
     if(write_pid_file()<0) {
       goto error;
7c964b9b
     }
a2a334f1
 
7c964b9b
     /* try to replace stdin, stdout & stderr with /dev/null */
     if (freopen("/dev/null", "r", stdin)==0){
ccec6fe3
       ERROR("Cannot replace stdin with /dev/null: %s.\n",
7c964b9b
 	    strerror(errno));
       /* continue, leave it open */
     };
     if (freopen("/dev/null", "w", stdout)==0){
ccec6fe3
       ERROR("Cannot replace stdout with /dev/null: %s.\n",
7c964b9b
 	    strerror(errno));
       /* continue, leave it open */
     };
     /* close stderr only if log_stderr=0 */
ccec6fe3
     if ((!log_stderr) && (freopen("/dev/null", "w", stderr)==0)){
       ERROR("Cannot replace stderr with /dev/null: %s.\n",
7c964b9b
 	    strerror(errno));
       /* continue, leave it open */
     };
   }
 
ccec6fe3
 #endif /* DISABLE_DAEMON_MODE */
7c964b9b
 
ccec6fe3
   main_pid = getpid();
4a82d793
 
ccec6fe3
   init_random();
a2a334f1
 
ccec6fe3
   if(set_sighandler(signal_handler))
     goto error;
     
   INFO("Loading plug-ins\n");
94350923
   AmPlugIn::instance()->init();
7c964b9b
   if(AmPlugIn::instance()->load(AmConfig::PlugInPath, AmConfig::LoadPlugins))
ccec6fe3
     goto error;
a2a334f1
 
6f44a030
 #ifdef WITH_ZRTP
   if (AmZRTP::init()) {
ccec6fe3
     ERROR("Cannot initialize ZRTP\n");
     goto error;
6f44a030
   }
 #endif
 
ccec6fe3
   INFO("Starting session container\n");
7c964b9b
   AmSessionContainer::instance()->start();
ceb2030a
   
 #ifdef SESSION_THREADPOOL
ccec6fe3
   INFO("Starting session processor threads\n");
ceb2030a
   AmSessionProcessor::addThreads(AmConfig::SessionProcessorThreads);
 #endif 
a2a334f1
 
ccec6fe3
   INFO("Starting media processor\n");
7c964b9b
   AmMediaProcessor::instance()->init();
a2a334f1
 
ccec6fe3
   INFO("Starting RTP receiver\n");
7c964b9b
   AmRtpReceiver::instance()->start();
a2a334f1
 
ccec6fe3
   INFO("Starting SIP stack (control interface)\n");
00aa71fa
   sip_ctrl.load();
ccec6fe3
   
34665040
   if(sip_ctrl.run() != -1)
     success = true;
a2a334f1
 
75be85ab
   // session container stops active sessions
   INFO("Disposing session container\n");
   AmSessionContainer::dispose();
 
ccec6fe3
   INFO("Disposing RTP receiver\n");
   AmRtpReceiver::dispose();
a0ee8055
 
ccec6fe3
   INFO("Disposing media processor\n");
   AmMediaProcessor::dispose();
a2a334f1
 
ccec6fe3
   INFO("Disposing event dispatcher\n");
   AmEventDispatcher::dispose();
ff58969c
 
ccec6fe3
  error:
   INFO("Disposing plug-ins\n");
   AmPlugIn::dispose();
ff58969c
 
ccec6fe3
 #ifndef DISABLE_DAEMON_MODE
   if (AmConfig::DaemonMode) {
     unlink(AmConfig::DaemonPidFile.c_str());
7c964b9b
   }
9c62df49
 #endif
a2a334f1
 
75be85ab
   sip_ctrl.cleanup();
 
ccec6fe3
   INFO("Exiting (%s)\n", success ? "success" : "failure");
   return (success ? EXIT_SUCCESS : EXIT_FAILURE);
a2a334f1
 }