From 1b86b9a5efcfc462b36ad8602c666b228392575f Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 10 Apr 2011 20:02:04 +0200 Subject: [PATCH] RAW connector cleanup, added configfile support, added setuid support, added some more comments other places --- Connector_HTTP/main.cpp | 1 + Connector_RAW/main.cpp | 14 +--- Connector_RTMP/main.cpp | 1 + util/ddv_socket.cpp | 2 + util/server_setup.cpp | 142 ++++++++++++++++++++++++++++++---------- 5 files changed, 114 insertions(+), 46 deletions(-) diff --git a/Connector_HTTP/main.cpp b/Connector_HTTP/main.cpp index ea43a9ba..67f21841 100644 --- a/Connector_HTTP/main.cpp +++ b/Connector_HTTP/main.cpp @@ -354,4 +354,5 @@ namespace Connector_HTTP{ // Load main server setup file, default port 8080, handler is Connector_HTTP::Connector_HTTP #define DEFAULT_PORT 8080 #define MAINHANDLER Connector_HTTP::Connector_HTTP +#define CONFIGSECT HTTP #include "../util/server_setup.cpp" diff --git a/Connector_RAW/main.cpp b/Connector_RAW/main.cpp index 15f2b9b9..51898ec0 100644 --- a/Connector_RAW/main.cpp +++ b/Connector_RAW/main.cpp @@ -1,19 +1,12 @@ #include #include "../util/ddv_socket.h" -#include -#include -#include -#include -#include -#include int main(int argc, char ** argv) { if (argc < 2){ std::cout << "Usage: " << argv[0] << " stream_name" << std::endl; return 1; } - std::string input; - input = "/tmp/shared_socket_"; + std::string input = "/tmp/shared_socket_"; input += argv[1]; DDV::Socket S(input); if (!S.connected()){ @@ -21,10 +14,7 @@ int main(int argc, char ** argv) { return 1; } char buffer[50000]; - int msg; - while(std::cout.good() && S.read(buffer,50000)){ - std::cout.write(buffer,50000); - } + while(std::cout.good() && S.read(buffer,50000)){std::cout.write(buffer,50000);} S.close(); return 0; } diff --git a/Connector_RTMP/main.cpp b/Connector_RTMP/main.cpp index 8a70bec0..3cc33d29 100644 --- a/Connector_RTMP/main.cpp +++ b/Connector_RTMP/main.cpp @@ -171,4 +171,5 @@ namespace Connector_RTMP{ // Load main server setup file, default port 1935, handler is Connector_RTMP::Connector_RTMP #define DEFAULT_PORT 1935 #define MAINHANDLER Connector_RTMP::Connector_RTMP +#define CONFIGSECT RTMP #include "../util/server_setup.cpp" diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index 6c5027b7..76c2ee8a 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -309,6 +309,8 @@ DDV::ServerSocket::ServerSocket(std::string address, bool nonblock){ DDV::Socket DDV::ServerSocket::accept(bool nonblock){ if (sock < 0){return DDV::Socket(-1);} int r = ::accept(sock, 0, 0); + //set the socket to be nonblocking, if requested. + //we could do this through accept4 with a flag, but that call is non-standard... if ((r >= 0) && nonblock){ int flags = fcntl(r, F_GETFL, 0); flags |= O_NONBLOCK; diff --git a/util/server_setup.cpp b/util/server_setup.cpp index bd9e9390..daa9e6c6 100644 --- a/util/server_setup.cpp +++ b/util/server_setup.cpp @@ -1,9 +1,18 @@ #include #include "ddv_socket.h" //DDVTech Socket wrapper #include "flv_tag.h" //FLV parsing with DDVTech Socket wrapper +#include +#include +#include +#define defstr(x) #x //converts a define name to string +#define defstrh(x) "[" defstr(x) "]" //converts define name to [string] DDV::ServerSocket server_socket(-1); -void termination_handler (int signum){ +/// Basic signal handler. Disconnects the server_socket if it receives +/// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE. +/// Disconnecting the server_socket will terminate the main listening loop +/// and cleanly shut down the process. +void signal_handler (int signum){ if (!server_socket.connected()) return; switch (signum){ case SIGINT: break; @@ -12,57 +21,104 @@ void termination_handler (int signum){ default: return; break; } server_socket.close(); -} +}//signal_handler +/// Generic main entry point and loop for DDV Connectors. +/// This sets up the proper termination handler, checks commandline options, +/// parses config files and opens a listening socket on the requested port. +/// Any incoming connections will be accepted and start up the function MAINHANDLER, +/// which should be #defined before including server_setup.cpp. +/// The default port is set by #define DEFAULT_PORT. +/// The configuration file section is set by #define CONFIGSECT. int main(int argc, char ** argv){ - DDV::Socket CONN_fd(-1); - + DDV::Socket S;//placeholder for incoming connections + //setup signal handler struct sigaction new_action; - new_action.sa_handler = termination_handler; + new_action.sa_handler = signal_handler; sigemptyset (&new_action.sa_mask); new_action.sa_flags = 0; sigaction(SIGINT, &new_action, NULL); sigaction(SIGHUP, &new_action, NULL); sigaction(SIGTERM, &new_action, NULL); sigaction(SIGPIPE, &new_action, NULL); - + + //default values int listen_port = DEFAULT_PORT; bool daemon_mode = true; std::string interface = "0.0.0.0"; + std::string configfile = "/etc/ddvtech.conf"; + std::string username = "root"; + bool ignore_daemon = false; + bool ignore_interface = false; + bool ignore_port = false; + bool ignore_user = false; int opt = 0; - static const char *optString = "np:i:h?"; + static const char *optString = "ndp:i:u:c:h?"; static const struct option longOpts[] = { {"help",0,0,'h'}, {"port",1,0,'p'}, {"interface",1,0,'i'}, - {"no-daemon",0,0,'n'} + {"username",1,0,'u'}, + {"no-daemon",0,0,'n'}, + {"daemon",0,0,'d'}, + {"configfile",1,0,'c'} }; while ((opt = getopt_long(argc, argv, optString, longOpts, 0)) != -1){ switch (opt){ - case 'p': - listen_port = atoi(optarg); - break; - case 'i': - interface = optarg; - break; - case 'n': - daemon_mode = false; - break; + case 'p': listen_port = atoi(optarg); ignore_port = true; break; + case 'i': interface = optarg; ignore_interface = true; break; + case 'n': daemon_mode = false; ignore_daemon = true; break; + case 'd': daemon_mode = true; ignore_daemon = true; break; + case 'c': configfile = optarg; break; + case 'u': username = optarg; ignore_user = true; break; case 'h': case '?': - printf("Options: -h[elp], -?, -n[o-daemon], -p[ort] #\n"); + printf("Options: -h[elp], -?, -n[odaemon], -d[aemon], -p[ort] VAL, -i[nterface] VAL, -c[onfigfile] VAL, -u[sername] VAL\n"); + printf("Defaults:\n interface: 0.0.0.0\n port: %i\n daemon mode: true\n configfile: /etc/ddvtech.conf\n username: root\n", listen_port); + printf("Username root means no change to UID, no matter what the UID is.\n"); + printf("If the configfile exists, it is always loaded first. Commandline settings then overwrite the config file.\n"); + printf("\nThis process takes it directives from the %s section of the configfile.\n", defstrh(CONFIGSECT)); return 1; break; } - } - + }//commandline options parser + + std::ifstream conf(configfile.c_str(), std::ifstream::in); + std::string tmpstr; + bool acc_comm = false; + size_t foundeq; + if (conf.fail()){ + #if DEBUG >= 3 + fprintf(stderr, "Configuration file %s not found - using build-in defaults...\n", configfile.c_str()); + #endif + }else{ + while (conf.good()){ + getline(conf, tmpstr); + if (tmpstr[0] == '['){//new section? check if we care. + if (tmpstr == defstrh(CONFIGSECT)){acc_comm = true;}else{acc_comm = false;} + }else{ + if (!acc_comm){break;}//skip all lines in this section if we do not care about it + foundeq = tmpstr.find('='); + if (foundeq != std::string::npos){ + if ((tmpstr.substr(0, foundeq) == "port") && !ignore_port){listen_port = atoi(tmpstr.substr(foundeq+1).c_str());} + if ((tmpstr.substr(0, foundeq) == "interface") && !ignore_interface){interface = tmpstr.substr(foundeq+1);} + if ((tmpstr.substr(0, foundeq) == "username") && !ignore_user){username = tmpstr.substr(foundeq+1);} + if ((tmpstr.substr(0, foundeq) == "daemon") && !ignore_daemon){daemon_mode = true;} + if ((tmpstr.substr(0, foundeq) == "nodaemon") && !ignore_daemon){daemon_mode = false;} + }//found equals sign + }//section contents + }//configfile line loop + }//configuration + + //setup a new server socket, for the correct interface and port server_socket = DDV::ServerSocket(listen_port, interface); #if DEBUG >= 3 fprintf(stderr, "Made a listening socket on %s:%i...\n", interface.c_str(), listen_port); #endif if (server_socket.connected()){ + //if setup success, enter daemon mode if requested if (daemon_mode){ daemon(1, 0); #if DEBUG >= 3 @@ -75,23 +131,41 @@ int main(int argc, char ** argv){ #endif return 1; } - int status; - while (server_socket.connected()){ - waitpid((pid_t)-1, &status, WNOHANG); - CONN_fd = server_socket.accept(); - if (CONN_fd.connected()){ - pid_t myid = fork(); - if (myid == 0){ - break; + + if (username != "root"){ + struct passwd * user_info = getpwnam(username.c_str()); + if (!user_info){ + #if DEBUG >= 1 + fprintf(stderr, "Error: could not setuid %s: could not get PID\n", username.c_str()); + #endif + return 1; + }else{ + if (setuid(user_info->pw_uid) != 0){ + #if DEBUG >= 1 + fprintf(stderr, "Error: could not setuid %s: not allowed\n", username.c_str()); + #endif }else{ #if DEBUG >= 3 - fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, CONN_fd.getSocket()); + fprintf(stderr, "Changed user to %s\n", username.c_str()); #endif } } } - if (!server_socket.connected()){ - return 0; - } - return MAINHANDLER(CONN_fd); -} + + int status; + while (server_socket.connected()){ + while (waitpid((pid_t)-1, &status, WNOHANG) > 0){}//clean up all child processes + S = server_socket.accept(); + if (S.connected()){//check if the new connection is valid + pid_t myid = fork(); + if (myid == 0){//if new child, start MAINHANDLER + return MAINHANDLER(S); + }else{//otherwise, do nothing or output debugging text + #if DEBUG >= 3 + fprintf(stderr, "Spawned new process %i for socket %i\n", (int)myid, S.getSocket()); + #endif + } + } + }//while connected + return 0; +}//main