stap-serverd.cxx - systemtap
Global variables defined
Functions defined
Macros defined
Source code
#include "config.h"
#include <fstream>
#include <string>
#include <cerrno>
#include <cassert>
#include <climits>
#include <iostream>
#include <map>
extern "C" {
#include <unistd.h>
#include <getopt.h>
#include <wordexp.h>
#include <glob.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <pwd.h>
#include <semaphore.h>
#include <nspr.h>
#include <ssl.h>
#include <nss.h>
#include <keyhi.h>
#include <regex.h>
#include <dirent.h>
#include <string.h>
#include <sys/ioctl.h>
#if HAVE_AVAHI
#include <avahi-client/publish.h>
#include <avahi-common/alternative.h>
#include <avahi-common/thread-watch.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h>
#include <avahi-common/domain.h>
#include <sys/inotify.h>
#endif
}
#include "util.h"
#include "nsscommon.h"
#include "cscommon.h"
#include "cmdline.h"
using namespace std;
static void cleanup ();
static PRStatus spawn_and_wait (const vector<string> &argv, int *result,
const char* fd0, const char* fd1, const char* fd2,
const char *pwd, const vector<string>& envVec = vector<string> ());
#define MOK_PUBLIC_CERT_NAME "signing_key.x509"
#define MOK_PUBLIC_CERT_FILE "/" MOK_PUBLIC_CERT_NAME
#define MOK_PRIVATE_CERT_NAME "signing_key.priv"
#define MOK_PRIVATE_CERT_FILE "/" MOK_PRIVATE_CERT_NAME
#define MOK_CONFIG_FILE "/x509.genkey"
#define MOK_CONFIG_TEXT \
"[ req ]\n" \
"default_bits = 4096\n" \
"distinguished_name = req_distinguished_name\n" \
"prompt = no\n" \
"x509_extensions = myexts\n" \
"\n" \
"[ req_distinguished_name ]\n" \
"O = Systemtap\n" \
"CN = Systemtap module signing key\n" \
"\n" \
"[ myexts ]\n" \
"basicConstraints=critical,CA:FALSE\n" \
"keyUsage=digitalSignature\n" \
"subjectKeyIdentifier=hash\n" \
"authorityKeyIdentifier=keyid\n"
extern int optind;
static bool use_db_password;
static unsigned short port;
static long max_threads;
static string cert_db_path;
static string stap_options;
static string uname_r;
static string kernel_build_tree;
static string arch;
static string cert_serial_number;
static string B_options;
static string I_options;
static string R_option;
static string D_options;
static bool keep_temp;
static string mok_path;
sem_t sem_client;
static int pending_interrupts;
#define CONCURRENCY_TIMEOUT_S 3
static void
server_error (const string &msg, int logit = true)
{
cerr << msg << endl << flush;
if (logit && log_ok ())
log (msg);
}
static void
client_error (const string &msg, string stapstderr)
{
server_error (msg);
if (! stapstderr.empty ())
{
ofstream errfile;
errfile.open (stapstderr.c_str (), ios_base::app);
if (! errfile.good ())
server_error (_F("Could not open client stderr file %s: %s", stapstderr.c_str (),
strerror (errno)));
else
errfile << "Server: " << msg << endl;
}
}
extern "C"
void
nsscommon_error (const char *msg, int logit)
{
server_error (msg, logit);
}
static void
fatal (const string &msg)
{
server_error (msg);
cleanup ();
exit (1);
}
static void
process_a (const string &arg)
{
arch = arg;
stap_options += " -a " + arg;
}
static void
process_r (const string &arg)
{
if (arg[0] == '/') {
kernel_build_tree = arg;
uname_r = kernel_release_from_build_tree (arg);
}
else
{
kernel_build_tree = "/lib/modules/" + arg + "/build";
uname_r = arg;
}
stap_options += " -r " + arg; }
static void
process_log (const char *arg)
{
start_log (arg);
}
static void
parse_options (int argc, char **argv)
{
optind = 1;
while (true)
{
char *num_endptr;
long port_tmp;
enum {
LONG_OPT_PORT = 256,
LONG_OPT_SSL,
LONG_OPT_LOG,
LONG_OPT_MAXTHREADS
};
static struct option long_options[] = {
{ "port", 1, NULL, LONG_OPT_PORT },
{ "ssl", 1, NULL, LONG_OPT_SSL },
{ "log", 1, NULL, LONG_OPT_LOG },
{ "max-threads", 1, NULL, LONG_OPT_MAXTHREADS },
{ NULL, 0, NULL, 0 }
};
int grc = getopt_long (argc, argv, "a:B:D:I:kPr:R:", long_options, NULL);
if (grc < 0)
break;
switch (grc)
{
case 'a':
process_a (optarg);
break;
case 'B':
B_options += string (" -") + (char)grc + optarg;
stap_options += string (" -") + (char)grc + optarg;
break;
case 'D':
D_options += string (" -") + (char)grc + optarg;
stap_options += string (" -") + (char)grc + optarg;
break;
case 'I':
I_options += string (" -") + (char)grc + optarg;
stap_options += string (" -") + (char)grc + optarg;
break;
case 'k':
keep_temp = true;
break;
case 'P':
use_db_password = true;
break;
case 'r':
process_r (optarg);
break;
case 'R':
R_option = string (" -") + (char)grc + optarg;
stap_options += string (" -") + (char)grc + optarg;
break;
case LONG_OPT_PORT:
port_tmp = strtol (optarg, &num_endptr, 10);
if (*num_endptr != '\0')
fatal (_F("%s: cannot parse number '--port=%s'", argv[0], optarg));
else if (port_tmp < 0 || port_tmp > 65535)
fatal (_F("%s: invalid entry: port must be between 0 and 65535 '--port=%s'", argv[0],
optarg));
else
port = (unsigned short) port_tmp;
break;
case LONG_OPT_SSL:
cert_db_path = optarg;
break;
case LONG_OPT_LOG:
process_log (optarg);
break;
case LONG_OPT_MAXTHREADS:
max_threads = strtol (optarg, &num_endptr, 0);
if (*num_endptr != '\0')
fatal (_F("%s: cannot parse number '--max-threads=%s'", argv[0], optarg));
else if (max_threads < 0)
fatal (_F("%s: invalid entry: max threads must not be negative '--max-threads=%s'",
argv[0], optarg));
break;
case '?':
break;
default:
if (optarg)
server_error (_F("%s: unhandled option '%c %s'", argv[0], (char)grc, optarg));
else
server_error (_F("%s: unhandled option '%c'", argv[0], (char)grc));
break;
}
}
for (int i = optind; i < argc; i++)
server_error (_F("%s: unrecognized argument '%s'", argv[0], argv[i]));
}
static string
server_cert_file ()
{
return server_cert_db_path () + "/stap.cert";
}
extern "C"
void
handle_interrupt (int sig)
{
pending_interrupts++;
if(pending_interrupts >= 2)
{
log (_F("Received another signal %d, exiting (forced)", sig));
_exit(0);
}
log (_F("Received signal %d, exiting", sig));
}
static void
setup_signals (sighandler_t handler)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sigemptyset (&sa.sa_mask);
if (handler != SIG_IGN)
{
sigaddset (&sa.sa_mask, SIGHUP);
sigaddset (&sa.sa_mask, SIGPIPE);
sigaddset (&sa.sa_mask, SIGINT);
sigaddset (&sa.sa_mask, SIGTERM);
sigaddset (&sa.sa_mask, SIGTTIN);
sigaddset (&sa.sa_mask, SIGTTOU);
sigaddset (&sa.sa_mask, SIGXFSZ);
sigaddset (&sa.sa_mask, SIGXCPU);
}
sa.sa_flags = SA_RESTART;
sigaction (SIGHUP, &sa, NULL);
sigaction (SIGPIPE, &sa, NULL);
sigaction (SIGINT, &sa, NULL);
sigaction (SIGTERM, &sa, NULL);
sigaction (SIGTTIN, &sa, NULL);
sigaction (SIGTTOU, &sa, NULL);
sigaction (SIGXFSZ, &sa, NULL);
sigaction (SIGXCPU, &sa, NULL);
}
bool
mok_dir_valid_p (string mok_fingerprint, bool verbose)
{
string mok_dir = mok_path + "/" + mok_fingerprint;
DIR *dirp = opendir (mok_dir.c_str());
if (dirp == NULL)
{
if (verbose)
server_error (_F("Could not open server MOK fingerprint directory %s: %s",
mok_dir.c_str(), strerror(errno)));
return false;
}
bool priv_found = false;
bool cert_found = false;
struct dirent *direntp;
while ((direntp = readdir (dirp)) != NULL)
{
if (! priv_found && direntp->d_type == DT_REG
&& strcmp (direntp->d_name, MOK_PRIVATE_CERT_NAME) == 0)
{
priv_found = true;
continue;
}
if (! cert_found && direntp->d_type == DT_REG
&& strcmp (direntp->d_name, MOK_PUBLIC_CERT_NAME) == 0)
{
cert_found = true;
continue;
}
if (priv_found && cert_found)
break;
}
closedir (dirp);
if (! priv_found || ! cert_found)
{
if (verbose)
server_error (_F("Could not find server MOK files in directory %s",
mok_dir.c_str ()));
return false;
}
string fingerprint;
if (read_cert_info_from_file (mok_dir + MOK_PUBLIC_CERT_FILE, fingerprint)
== SECSuccess)
{
if (fingerprint != mok_fingerprint)
{
if (verbose)
server_error (_F("Server MOK directory name '%s' doesn't match fingerprint from certificate %s",
mok_dir.c_str(), fingerprint.c_str()));
return false;
}
}
return true;
}
static void
get_server_mok_fingerprints(vector<string> &mok_fingerprints, bool verbose,
bool only_one_needed)
{
DIR *dirp;
struct dirent *direntp;
vector<string> temp;
mok_fingerprints.clear ();
dirp = opendir (mok_path.c_str ());
if (dirp == NULL)
{
if (errno != ENOENT)
server_error (_F("Could not open server MOK directory %s: %s",
mok_path.c_str (), strerror (errno)));
return;
}
regex_t checkre;
if ((regcomp (&checkre, "^[0-9a-f]{2}(:[0-9a-f]{2})+$",
REG_EXTENDED | REG_NOSUB) != 0))
{
server_error (_F("Error in MOK fingerprint regcomp: %s",
strerror (errno)));
closedir (dirp);
return;
}
while ((direntp = readdir (dirp)) != NULL)
{
if (direntp->d_type != DT_DIR)
continue;
if ((regexec (&checkre, direntp->d_name, (size_t) 0, NULL, 0) != 0))
continue;
temp.push_back (string (direntp->d_name));
}
regfree (&checkre);
closedir (dirp);
vector<string>::const_iterator it;
for (it = temp.begin (); it != temp.end (); it++)
{
if (mok_dir_valid_p (*it, true))
{
mok_fingerprints.push_back (*it);
if (verbose)
server_error (_F("Found MOK with fingerprint '%s'", it->c_str ()));
if (only_one_needed)
break;
}
}
return;
}
#if HAVE_AVAHI
static AvahiEntryGroup *avahi_group = NULL;
static AvahiThreadedPoll *avahi_threaded_poll = NULL;
static char *avahi_service_name = NULL;
static const char * const avahi_service_tag = "_stap._tcp";
static AvahiClient *avahi_client = 0;
static int avahi_collisions = 0;
static int inotify_fd = -1;
static AvahiWatch *avahi_inotify_watch = NULL;
static void create_services (AvahiClient *c);
static int
rename_service ()
{
++avahi_collisions;
if (avahi_collisions >= 65535) {
server_error (_F("Too many service name collisions for Avahi service %s",
avahi_service_tag));
return -EBUSY;
}
char *n = avahi_alternative_service_name(avahi_service_name);
server_error (_F("Avahi service name collision, renaming service '%s' to '%s'",
avahi_service_name, n));
avahi_free(avahi_service_name);
avahi_service_name = n;
return 0;
}
static void
entry_group_callback (
AvahiEntryGroup *g,
AvahiEntryGroupState state,
AVAHI_GCC_UNUSED void *userdata
) {
assert(g == avahi_group || avahi_group == NULL);
avahi_group = g;
switch (state)
{
case AVAHI_ENTRY_GROUP_ESTABLISHED:
log (_F("Avahi service '%s' successfully established.", avahi_service_name));
break;
case AVAHI_ENTRY_GROUP_COLLISION:
if (rename_service () == 0)
create_services (avahi_entry_group_get_client (g));
break;
case AVAHI_ENTRY_GROUP_FAILURE:
server_error (_F("Avahi entry group failure: %s",
avahi_strerror (avahi_client_errno (avahi_entry_group_get_client (g)))));
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
case AVAHI_ENTRY_GROUP_REGISTERING:
break;
}
}
static void
create_services (AvahiClient *c)
{
assert (c);
if (! avahi_group)
{
if (! (avahi_group = avahi_entry_group_new (c, entry_group_callback, NULL)))
{
server_error (_F("avahi_entry_group_new () failed: %s",
avahi_strerror (avahi_client_errno (c))));
return;
}
}
else
avahi_entry_group_reset(avahi_group);
log (_F("Adding Avahi service '%s'", avahi_service_name));
string sysinfo = "sysinfo=" + uname_r + " " + arch;
string certinfo = "certinfo=" + cert_serial_number;
string version = string ("version=") + CURRENT_CS_PROTOCOL_VERSION;;
string optinfo = "optinfo=";
string separator;
if (! R_option.empty ())
{
optinfo += R_option.substr(1);
separator = " ";
}
if (! B_options.empty ())
{
optinfo += separator + B_options.substr(1);
separator = " ";
}
if (! D_options.empty ())
{
optinfo += separator + D_options.substr(1);
separator = " ";
}
if (! I_options.empty ())
optinfo += separator + I_options.substr(1);
vector<string> mok_fingerprints;
AvahiStringList *strlst = avahi_string_list_new(sysinfo.c_str (),
optinfo.c_str (),
version.c_str (),
certinfo.c_str (), NULL);
if (strlst == NULL)
{
server_error (_("Failed to allocate string list"));
goto fail;
}
get_server_mok_fingerprints (mok_fingerprints, true, false);
if (! mok_fingerprints.empty())
{
vector<string>::const_iterator it;
for (it = mok_fingerprints.begin(); it != mok_fingerprints.end(); it++)
{
string tmp = "mok_info=" + *it;
strlst = avahi_string_list_add(strlst, tmp.c_str ());
if (strlst == NULL)
{
server_error (_("Failed to add a string to the list"));
goto fail;
}
}
}
int ret;
for (;;) {
ret = avahi_entry_group_add_service_strlst (avahi_group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC,
(AvahiPublishFlags)0,
avahi_service_name,
avahi_service_tag,
NULL, NULL, port, strlst);
if (ret == AVAHI_OK)
break;
if (ret == AVAHI_ERR_COLLISION)
{
if (rename_service () < 0) {
goto fail;
}
continue; }
server_error (_F("Failed to add %s service: %s",
avahi_service_tag, avahi_strerror (ret)));
goto fail;
}
if ((ret = avahi_entry_group_commit (avahi_group)) < 0)
{
server_error (_F("Failed to commit avahi entry group: %s", avahi_strerror (ret)));
goto fail;
}
avahi_string_list_free(strlst);
return;
fail:
avahi_entry_group_reset (avahi_group);
avahi_string_list_free(strlst);
}
static void avahi_cleanup_client () {
if (avahi_client) {
avahi_client_free (avahi_client);
avahi_client = 0;
avahi_group = 0;
}
}
static void
client_callback (AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata)
{
assert(c);
switch (state)
{
case AVAHI_CLIENT_S_RUNNING:
create_services (c);
break;
case AVAHI_CLIENT_FAILURE:
server_error (_F("Avahi client failure: %s", avahi_strerror (avahi_client_errno (c))));
if (avahi_client_errno (c) == AVAHI_ERR_DISCONNECTED)
{
avahi_cleanup_client ();
int error;
avahi_client = avahi_client_new (avahi_threaded_poll_get (avahi_threaded_poll),
(AvahiClientFlags)AVAHI_CLIENT_NO_FAIL,
client_callback, NULL, & error);
}
break;
case AVAHI_CLIENT_S_COLLISION:
case AVAHI_CLIENT_S_REGISTERING:
if (avahi_group)
avahi_entry_group_reset (avahi_group);
break;
case AVAHI_CLIENT_CONNECTING:
server_error (_F("The Avahi daemon is not running. Avahi service '%s' will be established when the deamon is started", avahi_service_name));
break;
}
}
static void
inotify_callback (AvahiWatch *w, int fd, AvahiWatchEvent event, void *userdata)
{
struct inotify_event in_events[10];
ssize_t rc;
do
{
rc = read (fd, in_events, sizeof (in_events));
} while (rc > 0);
if (avahi_client && (avahi_client_get_state (avahi_client)
== AVAHI_CLIENT_S_RUNNING))
create_services (avahi_client);
}
static void
avahi_cleanup ()
{
if (avahi_service_name)
log (_F("Removing Avahi service '%s'", avahi_service_name));
if (avahi_threaded_poll)
avahi_threaded_poll_stop (avahi_threaded_poll);
avahi_cleanup_client ();
if (avahi_inotify_watch)
{
const AvahiPoll *poll = avahi_threaded_poll_get (avahi_threaded_poll);
if (poll)
poll->watch_free (avahi_inotify_watch);
avahi_inotify_watch = NULL;
}
if (inotify_fd >= 0)
{
close (inotify_fd);
inotify_fd = -1;
}
if (avahi_threaded_poll) {
avahi_threaded_poll_free (avahi_threaded_poll);
avahi_threaded_poll = 0;
}
if (avahi_service_name) {
avahi_free (avahi_service_name);
avahi_service_name = 0;
}
}
static void
avahi_publish_service (CERTCertificate *cert)
{
cert_serial_number = get_cert_serial_number (cert);
char host[HOST_NAME_MAX + 1];
gethostname (host, sizeof(host));
host[sizeof(host) - 1] = '\0';
string buf;
buf = string ("Systemtap Compile Server on ") + host;
const char *initial_service_name = buf.c_str ();
if (! avahi_is_valid_service_name (initial_service_name)) {
assert (strlen (initial_service_name) >= AVAHI_LABEL_MAX);
buf = buf.substr (0, AVAHI_LABEL_MAX - 1);
initial_service_name = buf.c_str ();
assert (avahi_is_valid_service_name (initial_service_name));
}
avahi_service_name = avahi_strdup (initial_service_name);
if (! (avahi_threaded_poll = avahi_threaded_poll_new ()))
{
server_error (_("Failed to create avahi threaded poll object."));
return;
}
int error;
avahi_client = avahi_client_new (avahi_threaded_poll_get (avahi_threaded_poll),
(AvahiClientFlags)AVAHI_CLIENT_NO_FAIL,
client_callback, NULL, & error);
if (! avahi_client)
{
server_error (_F("Failed to create avahi client: %s", avahi_strerror(error)));
return;
}
#if defined(IN_CLOEXEC) && defined(IN_NONBLOCK)
inotify_fd = inotify_init1 (IN_CLOEXEC|IN_NONBLOCK);
#else
if ((inotify_fd = inotify_init ()) >= 0)
{
fcntl(inotify_fd, F_SETFD, FD_CLOEXEC);
fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
}
#endif
if (inotify_fd < 0)
server_error (_F("Failed to initialize inotify: %s", strerror (errno)));
else
{
if (create_dir (mok_path.c_str (), 0755) != 0)
server_error (_F("Unable to find or create the MOK directory %s: %s",
mok_path.c_str (), strerror (errno)));
else if (inotify_add_watch (inotify_fd, mok_path.c_str (),
#ifdef IN_ONLYDIR
IN_ONLYDIR|
#endif
IN_CLOSE_WRITE|IN_DELETE|IN_DELETE_SELF|IN_MOVE)
< 0)
server_error (_F("Failed to add inotify watch: %s", strerror (errno)));
else
{
const AvahiPoll *poll = avahi_threaded_poll_get (avahi_threaded_poll);
if (!poll
|| ! (avahi_inotify_watch = poll->watch_new (poll, inotify_fd,
AVAHI_WATCH_IN,
inotify_callback,
NULL)))
server_error (_("Failed to create inotify watcher"));
}
}
avahi_threaded_poll_start (avahi_threaded_poll);
return;
}
#endif
static void
advertise_presence (CERTCertificate *cert __attribute ((unused)))
{
#if HAVE_AVAHI
avahi_publish_service (cert);
#else
server_error (_("Unable to advertise presence on the network. Avahi is not available"));
#endif
}
static void
unadvertise_presence ()
{
#if HAVE_AVAHI
avahi_cleanup ();
#endif
}
static void
initialize (int argc, char **argv) {
pending_interrupts = 0;
setup_signals (& handle_interrupt);
srand (time (NULL));
use_db_password = false;
port = 0;
max_threads = sysconf( _SC_NPROCESSORS_ONLN ); keep_temp = false;
struct utsname utsname;
uname (& utsname);
uname_r = utsname.release;
kernel_build_tree = "/lib/modules/" + uname_r + "/build";
arch = normalize_machine (utsname.machine);
parse_options (argc, argv);
if (! getenv ("STAP_PR11197_OVERRIDE")) {
if (geteuid () == 0)
fatal ("For security reasons, invocation of stap-serverd as root is not supported.");
}
struct passwd *pw = getpwuid (geteuid ());
if (! pw)
fatal (_F("Unable to determine effective user name: %s", strerror (errno)));
string username = pw->pw_name;
pid_t pid = getpid ();
log (_F("===== compile server pid %d starting as %s =====", pid, username.c_str ()));
if (cert_db_path.empty ())
cert_db_path = server_cert_db_path ();
PR_Init (PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
PK11_SetPasswordFunc (nssPasswordCallback);
mok_path = server_cert_db_path() + "/moks";
}
static void
cleanup ()
{
unadvertise_presence ();
end_log ();
}
static PRInt32
readDataFromSocket(PRFileDesc *sslSocket, const char *requestFileName)
{
PRFileDesc *local_file_fd = 0;
PRInt32 numBytesExpected;
PRInt32 numBytesRead;
PRInt32 numBytesWritten;
PRInt32 totalBytes = 0;
#define READ_BUFFER_SIZE 4096
char buffer[READ_BUFFER_SIZE];
XXX numBytesRead = PR_Read_Complete (sslSocket, & numBytesExpected,
(PRInt32)sizeof (numBytesExpected));
if (numBytesRead == 0) {
server_error (_("Error reading size of request file"));
goto done;
}
if (numBytesRead < 0)
{
server_error (_("Error in PR_Read"));
nssError ();
goto done;
}
numBytesExpected = ntohl (numBytesExpected);
if (numBytesExpected == 0)
return 0;
local_file_fd = PR_Open(requestFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
PR_IRUSR | PR_IWUSR);
if (local_file_fd == NULL)
{
server_error (_F("Could not open output file %s", requestFileName));
nssError ();
return -1;
}
for (totalBytes = 0; totalBytes < numBytesExpected; totalBytes += numBytesRead)
{
numBytesRead = PR_Read (sslSocket, buffer, READ_BUFFER_SIZE);
if (numBytesRead == 0)
break; if (numBytesRead < 0)
{
server_error (_("Error in PR_Read"));
nssError ();
goto done;
}
numBytesWritten = PR_Write(local_file_fd, buffer, numBytesRead);
if (numBytesWritten < 0 || (numBytesWritten != numBytesRead))
{
server_error (_F("Could not write to output file %s", requestFileName));
nssError ();
goto done;
}
}
if (totalBytes != numBytesExpected)
{
server_error (_F("Expected %d bytes, got %d while reading client request from socket",
numBytesExpected, totalBytes));
goto done;
}
done:
if (local_file_fd)
PR_Close (local_file_fd);
return totalBytes;
}
static PRFileDesc *
setupSSLSocket (PRFileDesc *tcpSocket, CERTCertificate *cert, SECKEYPrivateKey *privKey)
{
PRFileDesc *sslSocket;
SSLKEAType certKEA;
SECStatus secStatus;
sslSocket = SSL_ImportFD (NULL, tcpSocket);
if (sslSocket == NULL)
{
server_error (_("Could not import socket into SSL"));
nssError ();
return NULL;
}
secStatus = SSL_OptionSet (sslSocket, SSL_SECURITY, PR_TRUE);
if (secStatus != SECSuccess)
{
server_error (_("Error setting SSL security for socket"));
nssError ();
return NULL;
}
secStatus = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_SERVER, PR_TRUE);
if (secStatus != SECSuccess)
{
server_error (_("Error setting handshake as server for socket"));
nssError ();
return NULL;
}
secStatus = SSL_OptionSet(sslSocket, SSL_REQUEST_CERTIFICATE, PR_FALSE);
if (secStatus != SECSuccess)
{
server_error (_("Error setting SSL client authentication mode for socket"));
nssError ();
return NULL;
}
secStatus = SSL_OptionSet(sslSocket, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
if (secStatus != SECSuccess)
{
server_error (_("Error setting SSL client authentication mode for socket"));
nssError ();
return NULL;
}
#if 0#endif
#if 0#endif
#if 0#endif
certKEA = NSS_FindCertKEAType (cert);
secStatus = SSL_ConfigSecureServer (sslSocket, cert, privKey, certKEA);
if (secStatus != SECSuccess)
{
server_error (_("Error configuring SSL server"));
nssError ();
return NULL;
}
return sslSocket;
}
#if 0
#endif
static SECStatus
writeDataToSocket(PRFileDesc *sslSocket, const char *responseFileName)
{
PRFileDesc *local_file_fd = PR_Open (responseFileName, PR_RDONLY, 0);
if (local_file_fd == NULL)
{
server_error (_F("Could not open input file %s", responseFileName));
nssError ();
return SECFailure;
}
int numBytes = PR_TransmitFile (sslSocket, local_file_fd,
NULL, 0,
PR_TRANSMITFILE_KEEP_OPEN,
PR_INTERVAL_NO_TIMEOUT);
SECStatus secStatus = SECSuccess;
if (numBytes < 0)
{
server_error (_("Error writing response to socket"));
nssError ();
secStatus = SECFailure;
}
PR_Close (local_file_fd);
return secStatus;
}
static void
get_stap_locale (const string &staplang, vector<string> &envVec, string stapstderr, cs_protocol_version *client_version)
{
if (*client_version < "1.6")
return;
ifstream langfile;
langfile.open(staplang.c_str());
if (!langfile.is_open())
{
server_error(_F("Unable to open file %s for reading: %s", staplang.c_str(),
strerror (errno)));
return;
}
map<string, string> envMap; string line;
const set<string> &locVars = localization_variables();
if(environ != NULL)
{
for (unsigned i=0; environ[i]; i++)
{
string line = (string)environ[i];
size_t pos = line.find("=");
if(pos != string::npos)
envMap[line.substr(0, pos)] = line.substr(pos+1);
}
}
regex_t checkre;
if ((regcomp(&checkre, "^[a-zA-Z0-9@_.=-]*$", REG_EXTENDED | REG_NOSUB) != 0))
{
server_error(_F("Error in regcomp: %s", strerror (errno)));
return;
}
while (1)
{
getline(langfile, line);
if (!langfile.good())
break;
string key;
string value;
size_t pos;
pos = line.find("=");
if (pos == string::npos)
{
client_error(_F("Localization key=value line '%s' cannot be parsed", line.c_str()), stapstderr);
continue;
}
key = line.substr(0, pos);
pos++;
value = line.substr(pos);
if (locVars.find(key) == locVars.end())
{
client_error(_F("Localization key '%s' not found in global list", key.c_str()), stapstderr);
continue;
}
if ((regexec(&checkre, value.c_str(), (size_t) 0, NULL, 0) != 0))
{
client_error(_F("Localization value '%s' contains illegal characters", value.c_str()), stapstderr);
continue;
}
envMap[key] = value;
}
if (!langfile.eof())
{
server_error(_F("Error reading file %s: %s", staplang.c_str(), strerror (errno)));
}
regfree(&checkre);
for (map<string, string>::iterator it = envMap.begin(); it != envMap.end(); it++)
envVec.push_back(it->first + "=" + it->second);
}
static void
get_client_mok_fingerprints (const string &filename,
vector<string> &mok_fingerprints,
string stapstderr,
cs_protocol_version *client_version)
{
if (*client_version < "1.6") {
return;
}
ifstream file;
file.open(filename.c_str());
if (! file.is_open())
return;
regex_t checkre;
if (regcomp(&checkre, "^([0-9a-f]{2}(:[0-9a-f]{2})+)$", REG_EXTENDED)
!= 0)
{
server_error(_F("Error in MOK fingerprint regcomp: %s",
strerror (errno)));
return;
}
string line;
regmatch_t matches[3];
while (getline (file, line))
{
string fingerprint;
if ((regexec(&checkre, line.c_str(), 3, matches, 0) != 0))
{
client_error(_F("MOK fingerprint value '%s' isn't in the correct forma",
line.c_str()), stapstderr);
continue;
}
if (matches[1].rm_so >= 0)
fingerprint = line.substr(matches[1].rm_so,
matches[1].rm_eo - matches[1].rm_so);
if (! fingerprint.empty())
mok_fingerprints.push_back(fingerprint);
}
regfree(&checkre);
}
bool
mok_sign_file (std::string &mok_fingerprint,
const std::string &kernel_build_tree,
const std::string &name,
std::string stapstderr)
{
vector<string> cmd;
int rc;
string mok_directory = mok_path + "/" + mok_fingerprint;
cmd.clear();
cmd.push_back (kernel_build_tree + "/scripts/sign-file");
cmd.push_back ("sha512");
cmd.push_back (mok_directory + MOK_PRIVATE_CERT_FILE);
cmd.push_back (mok_directory + MOK_PUBLIC_CERT_FILE);
cmd.push_back (name);
rc = stap_system (0, cmd);
if (rc != 0)
{
client_error (_F("Running sign-file failed, rc = %d", rc), stapstderr);
return false;
}
else
{
client_error (_F("Module signed with MOK, fingerprint \"%s\"",
mok_fingerprint.c_str()), stapstderr);
return true;
}
}
static void
filter_response_file (const string &file_name, const string &responseDirName)
{
vector<string> cmd;
cmd.clear();
cmd.push_back ("sed");
cmd.push_back ("-i");
cmd.push_back (string ("s,") + get_home_directory () + ",<server>,g");
cmd.push_back (file_name);
stap_system (0, cmd);
cmd.clear();
cmd.push_back ("sed");
cmd.push_back ("-i");
cmd.push_back (string ("s,") + responseDirName + ",<server>,g");
cmd.push_back (file_name);
stap_system (0, cmd);
}
static privilege_t
getRequestedPrivilege (const vector<string> &stapargv)
{
int argc = stapargv.size();
char ** argv = new char *[argc + 1];
for (unsigned i = 0; i < stapargv.size(); ++i)
argv[i] = (char *)stapargv[i].c_str();
argv[argc] = NULL;
privilege_t privilege = pr_highest; optind = 1;
while (true)
{
int grc = getopt_long (argc, argv, STAP_SHORT_OPTIONS, stap_long_options, NULL);
if (grc < 0)
break;
switch (grc)
{
default:
break;
case LONG_OPT_PRIVILEGE:
if (strcmp (optarg, "stapdev") == 0)
privilege = pr_stapdev;
else if (strcmp (optarg, "stapsys") == 0)
privilege = pr_stapsys;
else if (strcmp (optarg, "stapusr") == 0)
privilege = pr_stapusr;
else
{
server_error (_F("Invalid argument '%s' for --privilege", optarg));
privilege = pr_highest;
}
goto done; case LONG_OPT_UNPRIVILEGED:
privilege = pr_unprivileged;
goto done; }
}
done:
delete[] argv;
return privilege;
}
static void
generate_mok(string &mok_fingerprint)
{
vector<string> cmd;
int rc;
char tmpdir[PATH_MAX] = { '\0' };
string public_cert_path, private_cert_path, destdir;
mode_t old_umask;
mok_fingerprint.clear ();
old_umask = umask(077);
string config_path = mok_path + MOK_CONFIG_FILE;
if (! file_exists (config_path))
{
ofstream config_stream;
config_stream.open (config_path.c_str ());
if (! config_stream.good ())
{
server_error (_F("Could not open MOK config file %s: %s",
config_path.c_str (), strerror (errno)));
goto cleanup;
}
config_stream << MOK_CONFIG_TEXT;
config_stream.close ();
}
snprintf (tmpdir, PATH_MAX, "%s/stap-server.XXXXXX", mok_path.c_str ());
if (mkdtemp (tmpdir) == NULL)
{
server_error (_F("Could not create temporary directory %s: %s", tmpdir,
strerror (errno)));
tmpdir[0] = '\0';
goto cleanup;
}
public_cert_path = tmpdir + string (MOK_PUBLIC_CERT_FILE);
private_cert_path = tmpdir + string (MOK_PRIVATE_CERT_FILE);
cmd.push_back ("openssl");
cmd.push_back ("req");
cmd.push_back ("-new");
cmd.push_back ("-nodes");
cmd.push_back ("-utf8");
cmd.push_back ("-sha256");
cmd.push_back ("-days");
cmd.push_back ("36500");
cmd.push_back ("-batch");
cmd.push_back ("-x509");
cmd.push_back ("-config");
cmd.push_back (config_path);
cmd.push_back ("-outform");
cmd.push_back ("DER");
cmd.push_back ("-out");
cmd.push_back (public_cert_path);
cmd.push_back ("-keyout");
cmd.push_back (private_cert_path);
rc = stap_system (0, cmd);
if (rc != 0)
{
server_error (_F("Generating MOK failed, rc = %d", rc));
goto cleanup;
}
if (read_cert_info_from_file (public_cert_path, mok_fingerprint)
!= SECSuccess)
goto cleanup;
destdir = mok_path + "/" + mok_fingerprint;
if (rename (tmpdir, destdir.c_str ()) < 0)
{
server_error (_F("Could not rename temporary directory %s to %s: %s",
tmpdir, destdir.c_str (), strerror (errno)));
goto cleanup;
}
umask(old_umask);
return;
cleanup:
cmd.clear ();
cmd.push_back ("rm");
cmd.push_back ("-rf");
cmd.push_back (tmpdir);
rc = stap_system (0, cmd);
if (rc != 0)
server_error (_("Error in tmpdir cleanup"));
mok_fingerprint.clear ();
umask(old_umask);
return;
}
static void
handleRequest (const string &requestDirName, const string &responseDirName, string stapstderr)
{
vector<string> stapargv;
cs_protocol_version client_version = "1.0"; int rc;
wordexp_t words;
unsigned u;
unsigned i;
FILE* f;
string stapversion = responseDirName + "/version";
f = fopen (stapversion.c_str (), "w");
if (f)
{
fputs (CURRENT_CS_PROTOCOL_VERSION, f);
fclose(f);
}
else
server_error (_F("Unable to open client version file %s", stapversion.c_str ()));
string filename = requestDirName + "/version";
if (file_exists (filename))
read_from_file (filename, client_version);
log (_F("Client version is %s", client_version.v));
stapargv.push_back ((char *)(getenv ("SYSTEMTAP_STAP") ?: STAP_PREFIX "/bin/stap"));
TODO rc = wordexp (stap_options.c_str (), & words, WRDE_NOCMD|WRDE_UNDEF);
if (rc)
{
server_error (_("Cannot parse stap options"));
return;
}
for (u=0; u<words.we_wordc; u++)
stapargv.push_back (words.we_wordv[u]);
string new_staptmpdir = responseDirName + "/stap000000";
rc = mkdir(new_staptmpdir.c_str(), 0700);
if (rc)
server_error(_F("Could not create temporary directory %s", new_staptmpdir.c_str()));
stapargv.push_back("--tmpdir=" + new_staptmpdir);
stapargv.push_back ("--client-options");
for (i=1 ; ; i++)
{
char stapargfile[PATH_MAX];
FILE* argfile;
struct stat st;
char *arg;
snprintf (stapargfile, PATH_MAX, "%s/argv%d", requestDirName.c_str (), i);
rc = stat(stapargfile, & st);
if (rc) break;
arg = (char *)malloc (st.st_size+1);
if (!arg)
{
server_error (_("Out of memory"));
return;
}
argfile = fopen(stapargfile, "r");
if (! argfile)
{
free(arg);
server_error (_F("Error opening %s: %s", stapargfile, strerror (errno)));
return;
}
rc = fread(arg, 1, st.st_size, argfile);
if (rc != st.st_size)
{
free(arg);
fclose(argfile);
server_error (_F("Error reading %s: %s", stapargfile, strerror (errno)));
return;
}
arg[st.st_size] = '\0';
stapargv.push_back (arg);
free (arg);
fclose (argfile);
}
string stapstdout = responseDirName + "/stdout";
XXX
string staplang = requestDirName + "/locale";
vector<string> envVec;
get_stap_locale (staplang, envVec, stapstderr, &client_version);
vector<string> client_mok_fingerprints;
get_client_mok_fingerprints(requestDirName + "/mok_fingerprints",
client_mok_fingerprints, stapstderr,
&client_version);
string mok_fingerprint;
if (! client_mok_fingerprints.empty())
{
vector<string>::const_iterator it;
for (it = client_mok_fingerprints.begin();
it != client_mok_fingerprints.end(); it++)
{
if (mok_dir_valid_p (*it, false))
{
mok_fingerprint = *it;
break;
}
}
}
int staprc;
rc = spawn_and_wait(stapargv, &staprc, "/dev/null", stapstdout.c_str (),
stapstderr.c_str (), requestDirName.c_str (), envVec);
if (rc != PR_SUCCESS)
{
server_error(_("Failed spawning translator"));
return;
}
privilege_t privilege = getRequestedPrivilege (stapargv);
if (staprc == 0 && (pr_contains (privilege, pr_stapusr)
|| pr_contains (privilege, pr_stapsys)
|| ! client_mok_fingerprints.empty ()))
{
glob_t globber;
char pattern[PATH_MAX];
snprintf (pattern, PATH_MAX, "%s/*.ko", new_staptmpdir.c_str());
rc = glob (pattern, GLOB_ERR, NULL, &globber);
if (rc)
server_error (_F("Unable to find a module in %s", new_staptmpdir.c_str()));
else if (globber.gl_pathc != 1)
server_error (_F("Too many modules (%zu) in %s", globber.gl_pathc, new_staptmpdir.c_str()));
else
{
if (pr_contains (privilege, pr_stapusr)
|| pr_contains (privilege, pr_stapsys))
sign_file (cert_db_path, server_cert_nickname(),
globber.gl_pathv[0],
string(globber.gl_pathv[0]) + ".sgn");
if (! mok_fingerprint.empty ())
{
if (! mok_sign_file (mok_fingerprint, kernel_build_tree,
globber.gl_pathv[0], stapstderr))
staprc = 1;
}
else if (! client_mok_fingerprints.empty ())
{
client_error (_("No matching machine owner key (MOK) available on the server to sign the\n module."), stapstderr);
vector<string> mok_fingerprints;
get_server_mok_fingerprints(mok_fingerprints, false, true);
if (mok_fingerprints.empty ())
{
generate_mok(mok_fingerprint);
}
else
{
mok_fingerprint = *mok_fingerprints.begin ();
}
if (! mok_fingerprint.empty ())
{
string mok_directory = mok_path + "/" + mok_fingerprint;
string src = mok_directory + MOK_PUBLIC_CERT_FILE;
string dst = responseDirName + MOK_PUBLIC_CERT_FILE;
if (copy_file (src, dst, true))
client_error ("The server has no machine owner key (MOK) in common with this\nsystem. Use the following command to import a server MOK into this\nsystem, then reboot:\n\n\tmokutil --import signing_key.x509", stapstderr);
else
client_error ("The server has no machine owner key (MOK) in common with this\nsystem. The server failed to return a certificate.", stapstderr);
}
else
{
client_error ("The server has no machine owner keys (MOK) in common with this\nsystem. The server could not generate a new MOK.", stapstderr);
}
staprc = 1;
}
}
}
ofstream ofs((responseDirName + "/rc").c_str());
ofs << staprc;
ofs.close();
string uprobes_ko = new_staptmpdir + "/uprobes/uprobes.ko";
if (get_file_size(uprobes_ko) > 0)
{
string uprobes_response;
if (client_version < "1.6")
{
uprobes_response = (string)responseDirName + "/uprobes.ko";
rc = symlink(uprobes_ko.c_str(), uprobes_response.c_str());
if (rc != 0)
server_error (_F("Could not link to %s from %s",
uprobes_ko.c_str(), uprobes_response.c_str()));
}
else
uprobes_response = uprobes_ko;
if (! pr_contains (privilege, pr_stapdev))
{
sign_file (cert_db_path, server_cert_nickname(),
uprobes_response, uprobes_response + ".sgn");
}
if (! mok_fingerprint.empty ())
mok_sign_file (mok_fingerprint, kernel_build_tree, uprobes_response,
stapstderr);
}
wordfree (& words);
filter_response_file (stapstdout, responseDirName);
filter_response_file (stapstderr, responseDirName);
}
static PRStatus
spawn_and_wait (const vector<string> &argv, int *spawnrc,
const char* fd0, const char* fd1, const char* fd2,
const char *pwd, const vector<string>& envVec)
{
pid_t pid;
int rc;
posix_spawn_file_actions_t actions;
int dotfd = -1;
#define CHECKRC(msg) do { if (rc) { server_error (_(msg)); return PR_FAILURE; } } while (0)
rc = posix_spawn_file_actions_init (& actions);
CHECKRC ("Error in spawn file actions ctor");
if (fd0) {
rc = posix_spawn_file_actions_addopen(& actions, 0, fd0, O_RDONLY, 0600);
CHECKRC ("Error in spawn file actions fd0");
}
if (fd1) {
rc = posix_spawn_file_actions_addopen(& actions, 1, fd1, O_WRONLY|O_CREAT, 0600);
CHECKRC ("Error in spawn file actions fd1");
}
if (fd2) {
rc = posix_spawn_file_actions_addopen(& actions, 2, fd2, O_WRONLY|O_APPEND|O_CREAT, 0600);
CHECKRC ("Error in spawn file actions fd2");
}
if (pwd)
{
dotfd = open (".", O_RDONLY);
if (dotfd < 0)
{
server_error (_("Error in spawn getcwd"));
return PR_FAILURE;
}
rc = chdir (pwd);
if (rc)
{
close(dotfd);
server_error(_("Error in spawn chdir"));
return PR_FAILURE;
}
}
pid = stap_spawn (0, argv, & actions, envVec);
if (pwd && dotfd >= 0)
{
int subrc;
subrc = fchdir (dotfd);
subrc |= close (dotfd);
if (subrc)
server_error (_("Error in spawn unchdir"));
}
if (pid == -1)
{
server_error (_F("Error in spawn: %s", strerror (errno)));
return PR_FAILURE;
}
*spawnrc = stap_waitpid (0, pid);
if (*spawnrc == -1) {
server_error (_("Error in waitpid"));
return PR_FAILURE;
}
rc = posix_spawn_file_actions_destroy (&actions);
CHECKRC ("Error in spawn file actions dtor");
return PR_SUCCESS;
#undef CHECKRC
}
void *
handle_connection (void *arg)
{
PRFileDesc * sslSocket = NULL;
SECStatus secStatus = SECFailure;
PRStatus prStatus;
int rc;
char *rc1;
char tmpdir[PATH_MAX];
char requestFileName[PATH_MAX];
char requestDirName[PATH_MAX];
char responseDirName[PATH_MAX];
char responseFileName[PATH_MAX];
string stapstderr; vector<string> argv;
PRInt32 bytesRead;
if(max_threads > 0)
pthread_detach(pthread_self());
thread_arg *t_arg = (thread_arg *) arg;
PRFileDesc *tcpSocket = t_arg->tcpSocket;
CERTCertificate *cert = t_arg->cert;
SECKEYPrivateKey *privKey = t_arg->privKey;
PRNetAddr addr = t_arg->addr;
tmpdir[0]='\0';
#if 0#endif
secStatus = SECFailure;
sslSocket = setupSSLSocket (tcpSocket, cert, privKey);
if (sslSocket == NULL)
{
goto cleanup;
}
secStatus = SSL_ResetHandshake(sslSocket, PR_TRUE);
if (secStatus != SECSuccess)
{
server_error (_("Error resetting SSL handshake"));
nssError ();
goto cleanup;
}
#if 0#endif
secStatus = SECFailure;
snprintf(tmpdir, PATH_MAX, "%s/stap-server.XXXXXX", getenv("TMPDIR") ?: "/tmp");
rc1 = mkdtemp(tmpdir);
if (! rc1)
{
server_error (_F("Could not create temporary directory %s: %s", tmpdir, strerror(errno)));
tmpdir[0]=0; goto cleanup;
}
snprintf (requestFileName, PATH_MAX, "%s/request.zip", tmpdir);
snprintf (requestDirName, PATH_MAX, "%s/request", tmpdir);
rc = mkdir(requestDirName, 0700);
if (rc)
{
server_error (_F("Could not create temporary directory %s: %s", requestDirName, strerror (errno)));
goto cleanup;
}
snprintf (responseDirName, PATH_MAX, "%s/response", tmpdir);
rc = mkdir(responseDirName, 0700);
if (rc)
{
server_error (_F("Could not create temporary directory %s: %s", responseDirName, strerror (errno)));
goto cleanup;
}
stapstderr = string(responseDirName) + "/stderr";
snprintf (responseFileName, PATH_MAX, "%s/response.zip", tmpdir);
bytesRead = readDataFromSocket(sslSocket, requestFileName);
if (bytesRead < 0) goto cleanup;
if (bytesRead == 0) {
secStatus = SECSuccess;
goto cleanup;
}
#if 0#endif
secStatus = SECFailure;
argv.push_back ("unzip");
argv.push_back ("-q");
argv.push_back ("-d");
argv.push_back (requestDirName);
argv.push_back (requestFileName);
rc = stap_system (0, argv);
if (rc != 0)
{
server_error (_("Unable to extract client request"));
goto cleanup;
}
handleRequest(requestDirName, responseDirName, stapstderr);
int ziprc;
argv.clear ();
argv.push_back ("zip");
argv.push_back ("-q");
argv.push_back ("-r");
argv.push_back (responseFileName);
argv.push_back (".");
rc = spawn_and_wait (argv, &ziprc, NULL, NULL, NULL, responseDirName);
if (rc != PR_SUCCESS || ziprc != 0)
{
server_error (_("Unable to compress server response"));
goto cleanup;
}
secStatus = writeDataToSocket (sslSocket, responseFileName);
cleanup:
if (sslSocket)
if (PR_Close (sslSocket) != PR_SUCCESS)
{
server_error (_("Error closing ssl socket"));
nssError ();
}
if (tmpdir[0])
{
if (keep_temp)
log (_F("Keeping temporary directory %s", tmpdir));
else
{
argv.clear ();
argv.push_back ("rm");
argv.push_back ("-r");
argv.push_back (tmpdir);
rc = stap_system (0, argv);
if (rc != 0)
server_error (_("Error in tmpdir cleanup"));
}
}
if (secStatus != SECSuccess)
server_error (_("Error processing client request"));
char buf[1024];
prStatus = PR_NetAddrToString (& addr, buf, sizeof (buf));
if (prStatus == PR_SUCCESS)
{
if (addr.raw.family == PR_AF_INET)
log (_F("Request from %s:%d complete", buf, addr.inet.port));
else if (addr.raw.family == PR_AF_INET6)
log (_F("Request from [%s]:%d complete", buf, addr.ipv6.port));
}
free(t_arg);
if (max_threads > 0)
{
sem_post(&sem_client);
pthread_exit(0);
}
else
return 0;
}
static SECStatus
accept_connections (PRFileDesc *listenSocket, CERTCertificate *cert)
{
PRNetAddr addr;
PRFileDesc *tcpSocket;
PRStatus prStatus;
SECStatus secStatus;
CERTCertDBHandle *dbHandle;
pthread_t tid;
thread_arg *t_arg;
dbHandle = CERT_GetDefaultCertDB ();
SECKEYPrivateKey *privKey = PK11_FindKeyByAnyCert (cert, (void*)cert_db_path.c_str ());
if (privKey == NULL)
{
server_error (_("Unable to obtain certificate private key"));
nssError ();
return SECFailure;
}
while (pending_interrupts == 0)
{
tcpSocket = PR_Accept (listenSocket, &addr, PR_INTERVAL_MIN);
if (tcpSocket == NULL)
{
if(PR_GetError() == PR_IO_TIMEOUT_ERROR)
continue;
else
{
server_error (_("Error accepting client connection"));
break;
}
}
char buf[1024];
prStatus = PR_NetAddrToString (&addr, buf, sizeof (buf));
if (prStatus == PR_SUCCESS)
{
if (addr.raw.family == PR_AF_INET)
log (_F("Accepted connection from %s:%d", buf, addr.inet.port));
else if (addr.raw.family == PR_AF_INET6)
log (_F("Accepted connection from [%s]:%d", buf, addr.ipv6.port));
}
XXX
if(max_threads >0)
{
int idle_threads;
sem_getvalue(&sem_client, &idle_threads);
if(idle_threads <= 0)
log(_("Server is overloaded. Processing times may be longer than normal."));
else if (idle_threads == max_threads)
log(_("Processing 1 request..."));
else
log(_F("Processing %d concurrent requests...", ((int)max_threads - idle_threads) + 1));
sem_wait(&sem_client);
}
t_arg = (thread_arg *)malloc(sizeof(*t_arg));
if (t_arg == 0)
fatal(_("No memory available for new thread arg!"));
t_arg->tcpSocket = tcpSocket;
t_arg->cert = cert;
t_arg->privKey = privKey;
t_arg->addr = addr;
if (max_threads > 0)
pthread_create(&tid, NULL, handle_connection, t_arg);
else
handle_connection(t_arg);
secStatus = CERT_VerifyCertNow (dbHandle, cert, PR_TRUE,
certUsageSSLServer, NULL);
if (secStatus != SECSuccess)
{
break;
}
}
SECKEY_DestroyPrivateKey (privKey);
return SECSuccess;
}
static SECStatus
server_main (PRFileDesc *listenSocket)
{
int idle_threads;
int timeout = 0;
SECStatus secStatus = nssInit (cert_db_path.c_str ());
if (secStatus != SECSuccess)
{
return secStatus;
}
CERTCertificate *cert = NULL;
bool serverCacheConfigured = false;
do {
const PRUint16 *cipher;
for (cipher = SSL_ImplementedCiphers; *cipher != 0; ++cipher)
SSL_CipherPolicySet(*cipher, SSL_ALLOWED);
} while (0);
secStatus = SSL_ConfigServerSessionIDCache (0, 0, 0, NULL);
if (secStatus != SECSuccess)
{
server_error (_("Unable to configure SSL server session ID cache"));
nssError ();
goto done;
}
serverCacheConfigured = true;
cert = PK11_FindCertFromNickname (server_cert_nickname (), NULL);
if (cert == NULL)
{
server_error (_F("Unable to find our certificate in the database at %s",
cert_db_path.c_str ()));
nssError ();
goto done;
}
advertise_presence (cert);
secStatus = accept_connections (listenSocket, cert);
unadvertise_presence ();
sem_getvalue(&sem_client, &idle_threads);
if(idle_threads < max_threads)
log(_F("Waiting for %d outstanding requests to complete...", (int)max_threads - idle_threads));
while(idle_threads < max_threads)
{
if(pending_interrupts && timeout++ > CONCURRENCY_TIMEOUT_S)
{
log(_("Timeout reached, exiting (forced)"));
kill_stap_spawn (SIGTERM);
cleanup ();
_exit(0);
}
sleep(1);
sem_getvalue(&sem_client, &idle_threads);
}
done:
if (cert)
CERT_DestroyCertificate (cert);
if (serverCacheConfigured && SSL_ShutdownServerSessionIDCache () != SECSuccess)
{
server_error (_("Unable to shut down server session ID cache"));
nssError ();
}
nssCleanup (cert_db_path.c_str ());
return secStatus;
}
static void
listen ()
{
PRFileDesc *listenSocket = PR_OpenTCPSocket (PR_AF_INET6); if (listenSocket == NULL)
{
server_error (_("Error creating socket"));
nssError ();
return;
}
PRSocketOptionData socketOption;
socketOption.option = PR_SockOpt_Nonblocking;
socketOption.value.non_blocking = PR_FALSE;
PRStatus prStatus = PR_SetSocketOption (listenSocket, & socketOption);
if (prStatus != PR_SUCCESS)
{
server_error (_("Error setting socket properties"));
nssError ();
goto done;
}
socketOption.option = PR_SockOpt_Reuseaddr;
socketOption.value.reuse_addr = PR_TRUE;
prStatus = PR_SetSocketOption (listenSocket, & socketOption);
if (prStatus != PR_SUCCESS)
{
server_error (_("Error setting socket properties"));
nssError ();
goto done;
}
PRNetAddr addr;
memset (& addr, 0, sizeof(addr));
prStatus = PR_InitializeNetAddr (PR_IpAddrAny, port, & addr);
addr.ipv6.family = PR_AF_INET6;
#if 0
#endif
for (;;)
{
prStatus = PR_Bind (listenSocket, & addr);
if (prStatus == PR_SUCCESS)
break;
PRErrorCode errorNumber = PR_GetError ();
switch (errorNumber)
{
case PR_ADDRESS_NOT_AVAILABLE_ERROR:
if (port == 0)
{
server_error (_F("Network port %hu is unavailable. Trying another port", port));
continue;
}
break;
case PR_ADDRESS_IN_USE_ERROR:
if (port == 0)
{
server_error (_F("Network port %hu is busy. Trying another port", port));
continue;
}
break;
default:
break;
}
server_error (_("Error setting socket address"));
nssError ();
goto done;
}
prStatus = PR_GetSockName (listenSocket, &addr);
if (prStatus != PR_SUCCESS)
{
server_error (_("Unable to obtain socket address"));
nssError ();
goto done;
}
char buf[1024];
prStatus = PR_NetAddrToString (&addr, buf, sizeof (buf));
port = PR_ntohs (addr.ipv6.port);
log (_F("Using network address [%s]:%hu", buf, port));
if (max_threads > 0)
log (_F("Using a maximum of %ld threads", max_threads));
else
log (_("Concurrency disabled"));
prStatus = PR_Listen (listenSocket, 5);
if (prStatus != PR_SUCCESS)
{
server_error (_("Error listening on socket"));
nssError ();
goto done;
}
sem_init(&sem_client, 0, max_threads);
while(!pending_interrupts)
{
if (check_cert (cert_db_path, server_cert_nickname (), use_db_password) != 0)
{
goto done;
}
SECStatus secStatus = add_client_cert (server_cert_file (),
local_client_cert_db_path ());
if (secStatus != SECSuccess)
{
server_error (_("Unable to authorize certificate for the local client"));
}
secStatus = server_main (listenSocket);
}
done:
sem_destroy(&sem_client); if (PR_Close (listenSocket) != PR_SUCCESS)
{
server_error (_("Error closing listen socket"));
nssError ();
}
}
int
main (int argc, char **argv) {
initialize (argc, argv);
listen ();
cleanup ();
return 0;
}