]>
Commit | Line | Data |
---|---|---|
aeb9cc10 DB |
1 | /* |
2 | SSL server program listens on a port, accepts client connection, reads | |
3 | the data into a temporary file, calls the systemtap translator and | |
4 | then transmits the resulting file back to the client. | |
5 | ||
6cb00a60 | 6 | Copyright (C) 2011-2018 Red Hat Inc. |
aeb9cc10 DB |
7 | |
8 | This file is part of systemtap, and is free software. You can | |
9 | redistribute it and/or modify it under the terms of the GNU General Public | |
10 | License as published by the Free Software Foundation; either version 2 of the | |
11 | License, or (at your option) any later version. | |
12 | ||
13 | This program is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
e8daaf60 | 19 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
aeb9cc10 DB |
20 | */ |
21 | #include "config.h" | |
22 | ||
c89fe89c | 23 | #include <fstream> |
aeb9cc10 DB |
24 | #include <string> |
25 | #include <cerrno> | |
26 | #include <cassert> | |
fd14bd8f | 27 | #include <climits> |
e4e3d6b7 | 28 | #include <iostream> |
e4e3d6b7 | 29 | #include <map> |
636e55b8 | 30 | #include <thread> |
aeb9cc10 DB |
31 | |
32 | extern "C" { | |
e14c865b | 33 | #include <unistd.h> |
aeb9cc10 DB |
34 | #include <getopt.h> |
35 | #include <wordexp.h> | |
36 | #include <glob.h> | |
37 | #include <fcntl.h> | |
38 | #include <sys/stat.h> | |
39 | #include <sys/utsname.h> | |
38452904 DB |
40 | #include <sys/types.h> |
41 | #include <pwd.h> | |
49398925 | 42 | #include <semaphore.h> |
aeb9cc10 DB |
43 | |
44 | #include <nspr.h> | |
45 | #include <ssl.h> | |
46 | #include <nss.h> | |
47 | #include <keyhi.h> | |
e4e3d6b7 | 48 | #include <regex.h> |
b3367f63 | 49 | #include <dirent.h> |
86faa85b DS |
50 | #include <string.h> |
51 | #include <sys/ioctl.h> | |
aeb9cc10 DB |
52 | |
53 | #if HAVE_AVAHI | |
54 | #include <avahi-client/publish.h> | |
55 | #include <avahi-common/alternative.h> | |
56a0fe43 | 56 | #include <avahi-common/thread-watch.h> |
aeb9cc10 DB |
57 | #include <avahi-common/malloc.h> |
58 | #include <avahi-common/error.h> | |
1c82016e | 59 | #include <avahi-common/domain.h> |
86faa85b | 60 | #include <sys/inotify.h> |
aeb9cc10 DB |
61 | #endif |
62 | } | |
63 | ||
64 | #include "util.h" | |
65 | #include "nsscommon.h" | |
cc7c72cd | 66 | #include "cscommon.h" |
73f52eb4 | 67 | #include "cmdline.h" |
aeb9cc10 DB |
68 | |
69 | using namespace std; | |
70 | ||
71 | static void cleanup (); | |
e7d52b3b | 72 | static PRStatus spawn_and_wait (const vector<string> &argv, int *result, |
5ed19be2 | 73 | const char* fd0, const char* fd1, const char* fd2, |
878b2f3f | 74 | const char *pwd, const vector<string>& envVec = vector<string> ()); |
aeb9cc10 | 75 | |
a32d236c | 76 | |
aeb9cc10 DB |
77 | /* getopt variables */ |
78 | extern int optind; | |
79 | ||
4a044a5e | 80 | /* File scope statics. Set during argument parsing and initialization. */ |
aeb9cc10 | 81 | static bool use_db_password; |
49398925 CM |
82 | static unsigned short port; |
83 | static long max_threads; | |
ed4873c2 | 84 | static size_t max_uncompressed_req_size; |
c4798c0f | 85 | static size_t max_compressed_req_size; |
aeb9cc10 DB |
86 | static string cert_db_path; |
87 | static string stap_options; | |
3532c11d | 88 | static map<string,string> kernel_build_tree; // Kernel version -> build tree |
aeb9cc10 DB |
89 | static string arch; |
90 | static string cert_serial_number; | |
91 | static string B_options; | |
92 | static string I_options; | |
93 | static string R_option; | |
071de8a6 | 94 | static string D_options; |
71a522b5 | 95 | static bool keep_temp; |
95fe6e8d | 96 | static string mok_path; |
aeb9cc10 | 97 | |
49398925 | 98 | sem_t sem_client; |
26a39006 | 99 | static int pending_interrupts; |
26a39006 | 100 | #define CONCURRENCY_TIMEOUT_S 3 |
16560657 DB |
101 | |
102 | // Message handling. | |
103 | // Server_error messages are printed to stderr and logged, if requested. | |
104 | static void | |
105 | server_error (const string &msg, int logit = true) | |
aeb9cc10 | 106 | { |
16560657 | 107 | cerr << msg << endl << flush; |
aeb9cc10 DB |
108 | // Log it, but avoid repeated messages to the terminal. |
109 | if (logit && log_ok ()) | |
110 | log (msg); | |
111 | } | |
112 | ||
16560657 DB |
113 | // client_error messages are treated as server errors and also printed to the client's stderr. |
114 | static void | |
49398925 | 115 | client_error (const string &msg, string stapstderr) |
16560657 DB |
116 | { |
117 | server_error (msg); | |
118 | if (! stapstderr.empty ()) | |
119 | { | |
120 | ofstream errfile; | |
121 | errfile.open (stapstderr.c_str (), ios_base::app); | |
122 | if (! errfile.good ()) | |
123 | server_error (_F("Could not open client stderr file %s: %s", stapstderr.c_str (), | |
124 | strerror (errno))); | |
125 | else | |
126 | errfile << "Server: " << msg << endl; | |
127 | // NB: No need to close errfile | |
128 | } | |
129 | } | |
130 | ||
131 | // Messages from the nss common code are treated as server errors. | |
132 | extern "C" | |
133 | void | |
134 | nsscommon_error (const char *msg, int logit) | |
135 | { | |
136 | server_error (msg, logit); | |
137 | } | |
138 | ||
139 | // Fatal errors are treated as server errors but also result in termination | |
aeb9cc10 DB |
140 | // of the server. |
141 | static void | |
142 | fatal (const string &msg) | |
143 | { | |
16560657 | 144 | server_error (msg); |
aeb9cc10 DB |
145 | cleanup (); |
146 | exit (1); | |
147 | } | |
148 | ||
149 | // Argument handling | |
150 | static void | |
151 | process_a (const string &arg) | |
152 | { | |
153 | arch = arg; | |
154 | stap_options += " -a " + arg; | |
155 | } | |
156 | ||
157 | static void | |
158 | process_r (const string &arg) | |
159 | { | |
160 | if (arg[0] == '/') // fully specified path | |
3532c11d | 161 | kernel_build_tree.insert({kernel_release_from_build_tree(arg), arg}); |
aeb9cc10 | 162 | else |
3532c11d | 163 | kernel_build_tree.insert({arg, "/lib/modules/" + arg + "/build" }); |
aeb9cc10 DB |
164 | } |
165 | ||
166 | static void | |
167 | process_log (const char *arg) | |
168 | { | |
169 | start_log (arg); | |
170 | } | |
171 | ||
172 | static void | |
173 | parse_options (int argc, char **argv) | |
174 | { | |
73f52eb4 DB |
175 | // Examine the command line. This is the command line for us (stap-serverd) not the command |
176 | // line for spawned stap instances. | |
177 | optind = 1; | |
aeb9cc10 DB |
178 | while (true) |
179 | { | |
aeb9cc10 | 180 | char *num_endptr; |
49398925 | 181 | long port_tmp; |
ed4873c2 | 182 | long maxsize_tmp; |
372c6458 DB |
183 | // NB: The values of these enumerators must not conflict with the values of ordinary |
184 | // characters, since those are returned by getopt_long for short options. | |
185 | enum { | |
186 | LONG_OPT_PORT = 256, | |
187 | LONG_OPT_SSL, | |
188 | LONG_OPT_LOG, | |
ed4873c2 | 189 | LONG_OPT_MAXTHREADS, |
88ce17cc AJ |
190 | LONG_OPT_MAXREQSIZE = 254, |
191 | LONG_OPT_MAXCOMPRESSEDREQ = 255 /* need to set a value otherwise there are conflicts */ | |
372c6458 | 192 | }; |
aeb9cc10 | 193 | static struct option long_options[] = { |
372c6458 DB |
194 | { "port", 1, NULL, LONG_OPT_PORT }, |
195 | { "ssl", 1, NULL, LONG_OPT_SSL }, | |
196 | { "log", 1, NULL, LONG_OPT_LOG }, | |
197 | { "max-threads", 1, NULL, LONG_OPT_MAXTHREADS }, | |
ed4873c2 | 198 | { "max-request-size", 1, NULL, LONG_OPT_MAXREQSIZE}, |
88ce17cc | 199 | { "max-compressed-request", 1, NULL, LONG_OPT_MAXCOMPRESSEDREQ}, |
aeb9cc10 DB |
200 | { NULL, 0, NULL, 0 } |
201 | }; | |
071de8a6 | 202 | int grc = getopt_long (argc, argv, "a:B:D:I:kPr:R:", long_options, NULL); |
aeb9cc10 DB |
203 | if (grc < 0) |
204 | break; | |
205 | switch (grc) | |
206 | { | |
207 | case 'a': | |
208 | process_a (optarg); | |
209 | break; | |
210 | case 'B': | |
071de8a6 DB |
211 | B_options += string (" -") + (char)grc + optarg; |
212 | stap_options += string (" -") + (char)grc + optarg; | |
213 | break; | |
214 | case 'D': | |
215 | D_options += string (" -") + (char)grc + optarg; | |
aeb9cc10 DB |
216 | stap_options += string (" -") + (char)grc + optarg; |
217 | break; | |
218 | case 'I': | |
071de8a6 | 219 | I_options += string (" -") + (char)grc + optarg; |
aeb9cc10 DB |
220 | stap_options += string (" -") + (char)grc + optarg; |
221 | break; | |
71a522b5 DB |
222 | case 'k': |
223 | keep_temp = true; | |
224 | break; | |
aeb9cc10 DB |
225 | case 'P': |
226 | use_db_password = true; | |
227 | break; | |
228 | case 'r': | |
229 | process_r (optarg); | |
230 | break; | |
231 | case 'R': | |
071de8a6 | 232 | R_option = string (" -") + (char)grc + optarg; |
aeb9cc10 DB |
233 | stap_options += string (" -") + (char)grc + optarg; |
234 | break; | |
372c6458 DB |
235 | case LONG_OPT_PORT: |
236 | port_tmp = strtol (optarg, &num_endptr, 10); | |
237 | if (*num_endptr != '\0') | |
238 | fatal (_F("%s: cannot parse number '--port=%s'", argv[0], optarg)); | |
239 | else if (port_tmp < 0 || port_tmp > 65535) | |
240 | fatal (_F("%s: invalid entry: port must be between 0 and 65535 '--port=%s'", argv[0], | |
241 | optarg)); | |
242 | else | |
243 | port = (unsigned short) port_tmp; | |
244 | break; | |
245 | case LONG_OPT_SSL: | |
246 | cert_db_path = optarg; | |
247 | break; | |
248 | case LONG_OPT_LOG: | |
249 | process_log (optarg); | |
250 | break; | |
251 | case LONG_OPT_MAXTHREADS: | |
252 | max_threads = strtol (optarg, &num_endptr, 0); | |
253 | if (*num_endptr != '\0') | |
254 | fatal (_F("%s: cannot parse number '--max-threads=%s'", argv[0], optarg)); | |
255 | else if (max_threads < 0) | |
256 | fatal (_F("%s: invalid entry: max threads must not be negative '--max-threads=%s'", | |
257 | argv[0], optarg)); | |
258 | break; | |
ed4873c2 AJ |
259 | case LONG_OPT_MAXREQSIZE: |
260 | maxsize_tmp = strtoul(optarg, &num_endptr, 0); // store as a long for now | |
261 | if (*num_endptr != '\0') | |
262 | fatal (_F("%s: cannot parse number '--max-request-size=%s'", argv[0], optarg)); | |
263 | else if (maxsize_tmp < 1) | |
8c074dc4 | 264 | fatal (_F("%s: invalid entry: max (uncompressed) request size must be greater than 0 '--max-request-size=%s'", |
ed4873c2 AJ |
265 | argv[0], optarg)); |
266 | max_uncompressed_req_size = (size_t) maxsize_tmp; // convert the long to an unsigned | |
267 | break; | |
88ce17cc AJ |
268 | case LONG_OPT_MAXCOMPRESSEDREQ: |
269 | maxsize_tmp = strtoul(optarg, &num_endptr, 0); // store as a long for now | |
270 | if (*num_endptr != '\0') | |
271 | fatal (_F("%s: cannot parse number '--max-compressed-request=%s'", argv[0], optarg)); | |
272 | else if (maxsize_tmp < 1) | |
8c074dc4 | 273 | fatal (_F("%s: invalid entry: max compressed request size must be greater than 0 '--max-compressed-request=%s'", |
88ce17cc AJ |
274 | argv[0], optarg)); |
275 | max_compressed_req_size = (size_t) maxsize_tmp; // convert the long to an unsigned | |
276 | break; | |
aeb9cc10 DB |
277 | case '?': |
278 | // Invalid/unrecognized option given. Message has already been issued. | |
279 | break; | |
280 | default: | |
281 | // Reached when one added a getopt option but not a corresponding switch/case: | |
282 | if (optarg) | |
16560657 | 283 | server_error (_F("%s: unhandled option '%c %s'", argv[0], (char)grc, optarg)); |
aeb9cc10 | 284 | else |
16560657 | 285 | server_error (_F("%s: unhandled option '%c'", argv[0], (char)grc)); |
aeb9cc10 | 286 | break; |
aeb9cc10 DB |
287 | } |
288 | } | |
289 | ||
290 | for (int i = optind; i < argc; i++) | |
16560657 | 291 | server_error (_F("%s: unrecognized argument '%s'", argv[0], argv[i])); |
aeb9cc10 DB |
292 | } |
293 | ||
3dc20443 DB |
294 | static string |
295 | server_cert_file () | |
296 | { | |
297 | return server_cert_db_path () + "/stap.cert"; | |
298 | } | |
299 | ||
aeb9cc10 DB |
300 | // Signal handling. When an interrupt is received, kill any spawned processes |
301 | // and exit. | |
302 | extern "C" | |
303 | void | |
304 | handle_interrupt (int sig) | |
305 | { | |
26a39006 | 306 | pending_interrupts++; |
26a39006 CM |
307 | if(pending_interrupts >= 2) |
308 | { | |
309 | log (_F("Received another signal %d, exiting (forced)", sig)); | |
310 | _exit(0); | |
311 | } | |
5ed19be2 | 312 | log (_F("Received signal %d, exiting", sig)); |
aeb9cc10 DB |
313 | } |
314 | ||
315 | static void | |
316 | setup_signals (sighandler_t handler) | |
317 | { | |
318 | struct sigaction sa; | |
319 | ||
11c6c509 | 320 | memset(&sa, 0, sizeof(sa)); |
aeb9cc10 DB |
321 | sa.sa_handler = handler; |
322 | sigemptyset (&sa.sa_mask); | |
323 | if (handler != SIG_IGN) | |
324 | { | |
325 | sigaddset (&sa.sa_mask, SIGHUP); | |
326 | sigaddset (&sa.sa_mask, SIGPIPE); | |
327 | sigaddset (&sa.sa_mask, SIGINT); | |
328 | sigaddset (&sa.sa_mask, SIGTERM); | |
329 | sigaddset (&sa.sa_mask, SIGTTIN); | |
330 | sigaddset (&sa.sa_mask, SIGTTOU); | |
039f8231 | 331 | sigaddset (&sa.sa_mask, SIGXFSZ); |
5ed19be2 | 332 | sigaddset (&sa.sa_mask, SIGXCPU); |
aeb9cc10 DB |
333 | } |
334 | sa.sa_flags = SA_RESTART; | |
335 | ||
336 | sigaction (SIGHUP, &sa, NULL); | |
337 | sigaction (SIGPIPE, &sa, NULL); | |
338 | sigaction (SIGINT, &sa, NULL); | |
339 | sigaction (SIGTERM, &sa, NULL); | |
340 | sigaction (SIGTTIN, &sa, NULL); | |
341 | sigaction (SIGTTOU, &sa, NULL); | |
039f8231 | 342 | sigaction (SIGXFSZ, &sa, NULL); |
5ed19be2 | 343 | sigaction (SIGXCPU, &sa, NULL); |
aeb9cc10 DB |
344 | } |
345 | ||
c419fab7 DS |
346 | |
347 | // Get the list of MOK fingerprints on the server. If | |
348 | // 'only_one_needed' is true, just return the first MOK. | |
349 | static void | |
350 | get_server_mok_fingerprints(vector<string> &mok_fingerprints, bool verbose, | |
351 | bool only_one_needed) | |
352 | { | |
353 | DIR *dirp; | |
354 | struct dirent *direntp; | |
355 | vector<string> temp; | |
356 | ||
357 | // Clear the vector. | |
358 | mok_fingerprints.clear (); | |
359 | ||
360 | // The directory of machine owner keys (MOK) is optional, so if it | |
361 | // doesn't exist, we don't worry about it. | |
362 | dirp = opendir (mok_path.c_str ()); | |
363 | if (dirp == NULL) | |
364 | { | |
365 | // If the error isn't ENOENT (Directory does not exist), we've got | |
366 | // a non-fatal error. | |
367 | if (errno != ENOENT) | |
368 | server_error (_F("Could not open server MOK directory %s: %s", | |
369 | mok_path.c_str (), strerror (errno))); | |
370 | return; | |
371 | } | |
372 | ||
373 | // Create a regular expression object to verify MOK fingerprints | |
374 | // directory name. | |
375 | regex_t checkre; | |
376 | if ((regcomp (&checkre, "^[0-9a-f]{2}(:[0-9a-f]{2})+$", | |
377 | REG_EXTENDED | REG_NOSUB) != 0)) | |
378 | { | |
379 | // Not fatal, just ignore the MOK fingerprints. | |
380 | server_error (_F("Error in MOK fingerprint regcomp: %s", | |
381 | strerror (errno))); | |
382 | closedir (dirp); | |
383 | return; | |
384 | } | |
385 | ||
386 | // We've opened the directory, so read all the directory names from | |
387 | // it. | |
388 | while ((direntp = readdir (dirp)) != NULL) | |
389 | { | |
390 | // We're only interested in directories (of key files). | |
391 | if (direntp->d_type != DT_DIR) | |
112095a2 QN |
392 | { |
393 | if (direntp->d_type == DT_UNKNOWN) | |
394 | { | |
395 | // If the filesystem doesn't support d_type, we'll have to | |
396 | // call stat(). | |
397 | struct stat tmpstat; | |
32023754 FCE |
398 | int rc = stat((mok_path + "/" + direntp->d_name).c_str (), &tmpstat); |
399 | if (rc || !S_ISDIR(tmpstat.st_mode)) | |
112095a2 QN |
400 | continue; |
401 | } | |
402 | else | |
403 | continue; | |
404 | } | |
c419fab7 DS |
405 | |
406 | // We've got a directory. If the directory name isn't in the right | |
407 | // format for a MOK fingerprint, skip it. | |
408 | if ((regexec (&checkre, direntp->d_name, (size_t) 0, NULL, 0) != 0)) | |
409 | continue; | |
410 | ||
411 | // OK, we've got a directory name in the right format, so save it. | |
412 | temp.push_back (string (direntp->d_name)); | |
413 | } | |
414 | regfree (&checkre); | |
415 | closedir (dirp); | |
416 | ||
417 | // At this point, we've got a list of directories with names in the | |
418 | // proper format. Make sure each directory contains a x509 | |
419 | // certificate and private key file. | |
420 | vector<string>::const_iterator it; | |
421 | for (it = temp.begin (); it != temp.end (); it++) | |
422 | { | |
42be330a SC |
423 | string mok_path = server_cert_db_path() + "/moks"; |
424 | if (mok_dir_valid_p (*it, mok_path, true, server_error)) | |
c419fab7 DS |
425 | { |
426 | // Save the info. | |
427 | mok_fingerprints.push_back (*it); | |
428 | if (verbose) | |
429 | server_error (_F("Found MOK with fingerprint '%s'", it->c_str ())); | |
430 | if (only_one_needed) | |
431 | break; | |
432 | } | |
433 | } | |
434 | return; | |
435 | } | |
436 | ||
aeb9cc10 DB |
437 | #if HAVE_AVAHI |
438 | static AvahiEntryGroup *avahi_group = NULL; | |
56a0fe43 | 439 | static AvahiThreadedPoll *avahi_threaded_poll = NULL; |
aeb9cc10 | 440 | static char *avahi_service_name = NULL; |
3c453875 | 441 | static const char * const avahi_service_tag = "_stap._tcp"; |
56a0fe43 | 442 | static AvahiClient *avahi_client = 0; |
3c453875 | 443 | static int avahi_collisions = 0; |
86faa85b DS |
444 | static int inotify_fd = -1; |
445 | static AvahiWatch *avahi_inotify_watch = NULL; | |
aeb9cc10 DB |
446 | |
447 | static void create_services (AvahiClient *c); | |
448 | ||
3c453875 DB |
449 | static int |
450 | rename_service () | |
451 | { | |
452 | /* | |
453 | * Each service must have a unique name on the local network. | |
454 | * When there is a collision, we try to rename the service. | |
455 | * However, we need to limit the number of attempts, since the | |
456 | * service namespace could be maliciously flooded with service | |
457 | * names designed to maximize collisions. | |
458 | * Arbitrarily choose a limit of 65535, which is the number of | |
459 | * TCP ports. | |
460 | */ | |
461 | ++avahi_collisions; | |
462 | if (avahi_collisions >= 65535) { | |
463 | server_error (_F("Too many service name collisions for Avahi service %s", | |
464 | avahi_service_tag)); | |
465 | return -EBUSY; | |
466 | } | |
467 | ||
468 | /* | |
469 | * Use the avahi-supplied function to generate a new service name. | |
470 | */ | |
471 | char *n = avahi_alternative_service_name(avahi_service_name); | |
472 | ||
473 | server_error (_F("Avahi service name collision, renaming service '%s' to '%s'", | |
474 | avahi_service_name, n)); | |
475 | avahi_free(avahi_service_name); | |
476 | avahi_service_name = n; | |
477 | ||
478 | return 0; | |
479 | } | |
480 | ||
aeb9cc10 DB |
481 | static void |
482 | entry_group_callback ( | |
483 | AvahiEntryGroup *g, | |
484 | AvahiEntryGroupState state, | |
485 | AVAHI_GCC_UNUSED void *userdata | |
486 | ) { | |
487 | assert(g == avahi_group || avahi_group == NULL); | |
488 | avahi_group = g; | |
489 | ||
490 | // Called whenever the entry group state changes. | |
491 | switch (state) | |
492 | { | |
493 | case AVAHI_ENTRY_GROUP_ESTABLISHED: | |
494 | // The entry group has been established successfully. | |
24a060a0 | 495 | log (_F("Avahi service '%s' successfully established.", avahi_service_name)); |
aeb9cc10 DB |
496 | break; |
497 | ||
3c453875 DB |
498 | case AVAHI_ENTRY_GROUP_COLLISION: |
499 | // A service name collision with a remote service happened. | |
500 | // Unfortunately, we don't know which entry collided. | |
501 | // We need to rename them all and recreate the services. | |
502 | if (rename_service () == 0) | |
503 | create_services (avahi_entry_group_get_client (g)); | |
aeb9cc10 | 504 | break; |
aeb9cc10 DB |
505 | |
506 | case AVAHI_ENTRY_GROUP_FAILURE: | |
24a060a0 | 507 | // Some kind of failure happened. |
16560657 | 508 | server_error (_F("Avahi entry group failure: %s", |
24a060a0 | 509 | avahi_strerror (avahi_client_errno (avahi_entry_group_get_client (g))))); |
aeb9cc10 DB |
510 | break; |
511 | ||
512 | case AVAHI_ENTRY_GROUP_UNCOMMITED: | |
513 | case AVAHI_ENTRY_GROUP_REGISTERING: | |
514 | break; | |
515 | } | |
516 | } | |
517 | ||
518 | static void | |
86faa85b DS |
519 | create_services (AvahiClient *c) |
520 | { | |
aeb9cc10 DB |
521 | assert (c); |
522 | ||
3c453875 | 523 | // Create a new entry group, if necessary, or reset the existing one. |
aeb9cc10 | 524 | if (! avahi_group) |
aeb9cc10 | 525 | { |
3c453875 | 526 | if (! (avahi_group = avahi_entry_group_new (c, entry_group_callback, NULL))) |
3532c11d FL |
527 | { |
528 | server_error (_F("avahi_entry_group_new () failed: %s", | |
529 | avahi_strerror (avahi_client_errno (c)))); | |
530 | return; | |
531 | } | |
3c453875 DB |
532 | } |
533 | else | |
534 | avahi_entry_group_reset(avahi_group); | |
aeb9cc10 | 535 | |
3c453875 DB |
536 | // Contruct the information needed for our service. |
537 | log (_F("Adding Avahi service '%s'", avahi_service_name)); | |
538 | ||
4a48a4f9 | 539 | AvahiStringList *strlst = NULL; |
3532c11d FL |
540 | int ret; |
541 | for (auto it = kernel_build_tree.cbegin(); it != kernel_build_tree.cend(); ++it) | |
542 | { | |
543 | // Create the txt tags that will be registered with our service. | |
544 | string sysinfo = "sysinfo=" + it->first + " "+ arch; | |
545 | string certinfo = "certinfo=" + cert_serial_number; | |
546 | string version = string ("version=") + CURRENT_CS_PROTOCOL_VERSION;; | |
547 | string optinfo = "optinfo="; | |
548 | string separator; | |
549 | // These option strings already have a leading space. | |
550 | if (! R_option.empty ()) | |
551 | { | |
552 | optinfo += R_option.substr(1); | |
553 | separator = " "; | |
554 | } | |
555 | if (! B_options.empty ()) | |
556 | { | |
557 | optinfo += separator + B_options.substr(1); | |
558 | separator = " "; | |
559 | } | |
560 | if (! D_options.empty ()) | |
561 | { | |
562 | optinfo += separator + D_options.substr(1); | |
563 | separator = " "; | |
564 | } | |
565 | if (! I_options.empty ()) | |
566 | optinfo += separator + I_options.substr(1); | |
567 | ||
568 | // Create an avahi string list with the info we have so far. | |
569 | vector<string> mok_fingerprints; | |
570 | strlst = avahi_string_list_new(sysinfo.c_str (), | |
571 | optinfo.c_str (), | |
572 | version.c_str (), | |
573 | certinfo.c_str (), NULL); | |
574 | if (strlst == NULL) | |
575 | { | |
576 | server_error (_("Failed to allocate string list")); | |
577 | goto fail; | |
578 | } | |
b3367f63 | 579 | |
3532c11d FL |
580 | // Add server MOK info, if available. |
581 | get_server_mok_fingerprints (mok_fingerprints, true, false); | |
582 | if (! mok_fingerprints.empty()) | |
b3367f63 | 583 | { |
3532c11d FL |
584 | for (auto it = mok_fingerprints.cbegin(); it != mok_fingerprints.cend(); it++) |
585 | { | |
586 | string tmp = "mok_info=" + *it; | |
587 | strlst = avahi_string_list_add(strlst, tmp.c_str ()); | |
588 | if (strlst == NULL) | |
589 | { | |
590 | server_error (_("Failed to add a string to the list")); | |
591 | goto fail; | |
592 | } | |
593 | } | |
594 | } | |
b3367f63 | 595 | |
3532c11d FL |
596 | // We will now add our service to the entry group. |
597 | // Loop until no collisions. | |
598 | for (;;) { | |
599 | ret = avahi_entry_group_add_service_strlst (avahi_group, | |
600 | AVAHI_IF_UNSPEC, | |
601 | AVAHI_PROTO_UNSPEC, | |
602 | (AvahiPublishFlags)0, | |
603 | avahi_service_name, | |
604 | avahi_service_tag, | |
605 | NULL, NULL, port, strlst); | |
606 | if (ret == AVAHI_OK) | |
607 | break; // success! | |
608 | ||
609 | if (ret == AVAHI_ERR_COLLISION) | |
610 | { | |
611 | // A service name collision with a local service happened. | |
612 | // Pick a new name. | |
613 | if (rename_service () < 0) { | |
614 | // Too many collisions. Message already issued. | |
615 | goto fail; | |
616 | } | |
617 | continue; // try again. | |
618 | } | |
619 | ||
620 | server_error (_F("Failed to add %s service: %s", | |
621 | avahi_service_tag, avahi_strerror (ret))); | |
622 | goto fail; | |
3c453875 DB |
623 | } |
624 | ||
3532c11d | 625 | avahi_string_list_free(strlst); |
4a48a4f9 | 626 | strlst = NULL; |
3532c11d | 627 | } |
3c453875 DB |
628 | |
629 | // Tell the server to register the service. | |
630 | if ((ret = avahi_entry_group_commit (avahi_group)) < 0) | |
631 | { | |
632 | server_error (_F("Failed to commit avahi entry group: %s", avahi_strerror (ret))); | |
633 | goto fail; | |
aeb9cc10 | 634 | } |
aeb9cc10 DB |
635 | return; |
636 | ||
637 | fail: | |
56a0fe43 | 638 | avahi_entry_group_reset (avahi_group); |
b3367f63 | 639 | avahi_string_list_free(strlst); |
aeb9cc10 DB |
640 | } |
641 | ||
24a060a0 DB |
642 | static void avahi_cleanup_client () { |
643 | // This also frees the entry group, if any | |
644 | if (avahi_client) { | |
645 | avahi_client_free (avahi_client); | |
646 | avahi_client = 0; | |
647 | avahi_group = 0; | |
648 | } | |
649 | } | |
650 | ||
aeb9cc10 DB |
651 | static void |
652 | client_callback (AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) | |
653 | { | |
654 | assert(c); | |
655 | ||
656 | // Called whenever the client or server state changes. | |
657 | switch (state) | |
658 | { | |
659 | case AVAHI_CLIENT_S_RUNNING: | |
660 | // The server has startup successfully and registered its host | |
661 | // name on the network, so it's time to create our services. | |
662 | create_services (c); | |
663 | break; | |
664 | ||
665 | case AVAHI_CLIENT_FAILURE: | |
16560657 | 666 | server_error (_F("Avahi client failure: %s", avahi_strerror (avahi_client_errno (c)))); |
24a060a0 DB |
667 | if (avahi_client_errno (c) == AVAHI_ERR_DISCONNECTED) |
668 | { | |
669 | // The client has been disconnected; probably because the avahi daemon has been | |
670 | // restarted. We can free the client here and try to reconnect using a new one. | |
671 | // Passing AVAHI_CLIENT_NO_FAIL allows the new client to be | |
672 | // created, even if the avahi daemon is not running. Our service will be advertised | |
673 | // if/when the daemon is started. | |
674 | avahi_cleanup_client (); | |
675 | int error; | |
676 | avahi_client = avahi_client_new (avahi_threaded_poll_get (avahi_threaded_poll), | |
677 | (AvahiClientFlags)AVAHI_CLIENT_NO_FAIL, | |
678 | client_callback, NULL, & error); | |
679 | } | |
aeb9cc10 DB |
680 | break; |
681 | ||
682 | case AVAHI_CLIENT_S_COLLISION: | |
683 | // Let's drop our registered services. When the server is back | |
684 | // in AVAHI_SERVER_RUNNING state we will register them | |
685 | // again with the new host name. | |
686 | // Fall through ... | |
687 | case AVAHI_CLIENT_S_REGISTERING: | |
688 | // The server records are now being established. This | |
689 | // might be caused by a host name change. We need to wait | |
690 | // for our own records to register until the host name is | |
691 | // properly esatblished. | |
692 | if (avahi_group) | |
693 | avahi_entry_group_reset (avahi_group); | |
694 | break; | |
695 | ||
696 | case AVAHI_CLIENT_CONNECTING: | |
24a060a0 DB |
697 | // The avahi-daemon is not currently running. Our service will be advertised |
698 | // if/when the deamon is started. | |
699 | server_error (_F("The Avahi daemon is not running. Avahi service '%s' will be established when the deamon is started", avahi_service_name)); | |
aeb9cc10 DB |
700 | break; |
701 | } | |
702 | } | |
24a060a0 | 703 | |
56a0fe43 | 704 | static void |
dabd71bb | 705 | inotify_callback (AvahiWatch *, int fd, AvahiWatchEvent, void *) |
86faa85b | 706 | { |
2ee9d8cc DS |
707 | struct inotify_event in_events[10]; |
708 | ssize_t rc; | |
86faa85b DS |
709 | |
710 | // Drain the inotify file. Notice we don't really care what changed, | |
711 | // we just needed to know that something changed. | |
2ee9d8cc DS |
712 | do |
713 | { | |
714 | rc = read (fd, in_events, sizeof (in_events)); | |
715 | } while (rc > 0); | |
86faa85b DS |
716 | |
717 | // Re-create the services. | |
718 | if (avahi_client && (avahi_client_get_state (avahi_client) | |
719 | == AVAHI_CLIENT_S_RUNNING)) | |
720 | create_services (avahi_client); | |
721 | } | |
722 | ||
723 | static void | |
724 | avahi_cleanup () | |
725 | { | |
56a0fe43 DB |
726 | if (avahi_service_name) |
727 | log (_F("Removing Avahi service '%s'", avahi_service_name)); | |
728 | ||
729 | // Stop the avahi client, if it's running | |
730 | if (avahi_threaded_poll) | |
731 | avahi_threaded_poll_stop (avahi_threaded_poll); | |
732 | ||
733 | // Clean up the avahi objects. The order of freeing these is significant. | |
24a060a0 | 734 | avahi_cleanup_client (); |
86faa85b DS |
735 | if (avahi_inotify_watch) |
736 | { | |
737 | const AvahiPoll *poll = avahi_threaded_poll_get (avahi_threaded_poll); | |
738 | if (poll) | |
739 | poll->watch_free (avahi_inotify_watch); | |
740 | avahi_inotify_watch = NULL; | |
741 | } | |
742 | if (inotify_fd >= 0) | |
743 | { | |
744 | close (inotify_fd); | |
745 | inotify_fd = -1; | |
746 | } | |
56a0fe43 DB |
747 | if (avahi_threaded_poll) { |
748 | avahi_threaded_poll_free (avahi_threaded_poll); | |
749 | avahi_threaded_poll = 0; | |
750 | } | |
751 | if (avahi_service_name) { | |
752 | avahi_free (avahi_service_name); | |
753 | avahi_service_name = 0; | |
754 | } | |
755 | } | |
756 | ||
aeb9cc10 | 757 | // The entry point for the avahi client thread. |
56a0fe43 DB |
758 | static void |
759 | avahi_publish_service (CERTCertificate *cert) | |
aeb9cc10 | 760 | { |
3c453875 | 761 | // Get the certificate serial number. |
aeb9cc10 | 762 | cert_serial_number = get_cert_serial_number (cert); |
3c453875 DB |
763 | |
764 | // Construct the Avahi service name. | |
765 | char host[HOST_NAME_MAX + 1]; | |
766 | gethostname (host, sizeof(host)); | |
767 | host[sizeof(host) - 1] = '\0'; | |
81f5fb74 | 768 | string buf; |
3c453875 | 769 | buf = string ("Systemtap Compile Server on ") + host; |
1c82016e DB |
770 | |
771 | // Make sure the service name is valid | |
772 | const char *initial_service_name = buf.c_str (); | |
773 | if (! avahi_is_valid_service_name (initial_service_name)) { | |
774 | // The only restriction on service names is that the buffer must not exceed | |
775 | // AVAHI_LABEL_MAX in size, which means that the name cannot be longer than | |
776 | // AVAHI_LABEL_MAX-1 in length. | |
777 | assert (strlen (initial_service_name) >= AVAHI_LABEL_MAX); | |
778 | buf = buf.substr (0, AVAHI_LABEL_MAX - 1); | |
779 | initial_service_name = buf.c_str (); | |
780 | assert (avahi_is_valid_service_name (initial_service_name)); | |
781 | } | |
782 | avahi_service_name = avahi_strdup (initial_service_name); | |
aeb9cc10 | 783 | |
aeb9cc10 | 784 | // Allocate main loop object. |
56a0fe43 | 785 | if (! (avahi_threaded_poll = avahi_threaded_poll_new ())) |
aeb9cc10 | 786 | { |
16560657 | 787 | server_error (_("Failed to create avahi threaded poll object.")); |
56a0fe43 | 788 | return; |
aeb9cc10 DB |
789 | } |
790 | ||
24a060a0 DB |
791 | // Always allocate a new client. Passing AVAHI_CLIENT_NO_FAIL allows the client to be |
792 | // created, even if the avahi daemon is not running. Our service will be advertised | |
793 | // if/when the daemon is started. | |
aeb9cc10 | 794 | int error; |
56a0fe43 | 795 | avahi_client = avahi_client_new (avahi_threaded_poll_get (avahi_threaded_poll), |
24a060a0 | 796 | (AvahiClientFlags)AVAHI_CLIENT_NO_FAIL, |
56a0fe43 | 797 | client_callback, NULL, & error); |
49dbe419 | 798 | // Check whether creating the client object succeeded. |
56a0fe43 | 799 | if (! avahi_client) |
aeb9cc10 | 800 | { |
16560657 | 801 | server_error (_F("Failed to create avahi client: %s", avahi_strerror(error))); |
56a0fe43 | 802 | return; |
aeb9cc10 DB |
803 | } |
804 | ||
86faa85b | 805 | // Watch the server MOK directory for any changes. |
2ee9d8cc DS |
806 | #if defined(IN_CLOEXEC) && defined(IN_NONBLOCK) |
807 | inotify_fd = inotify_init1 (IN_CLOEXEC|IN_NONBLOCK); | |
86faa85b DS |
808 | #else |
809 | if ((inotify_fd = inotify_init ()) >= 0) | |
2ee9d8cc DS |
810 | { |
811 | fcntl(inotify_fd, F_SETFD, FD_CLOEXEC); | |
812 | fcntl(inotify_fd, F_SETFL, O_NONBLOCK); | |
813 | } | |
86faa85b DS |
814 | #endif |
815 | if (inotify_fd < 0) | |
816 | server_error (_F("Failed to initialize inotify: %s", strerror (errno))); | |
817 | else | |
818 | { | |
2ee9d8cc DS |
819 | // We want to watch for new or removed MOK directories |
820 | // underneath mok_path. But, to do that, mok_path must exist. | |
821 | if (create_dir (mok_path.c_str (), 0755) != 0) | |
822 | server_error (_F("Unable to find or create the MOK directory %s: %s", | |
823 | mok_path.c_str (), strerror (errno))); | |
824 | // Watch mok_path for changes. | |
825 | else if (inotify_add_watch (inotify_fd, mok_path.c_str (), | |
86faa85b DS |
826 | #ifdef IN_ONLYDIR |
827 | IN_ONLYDIR| | |
828 | #endif | |
829 | IN_CLOSE_WRITE|IN_DELETE|IN_DELETE_SELF|IN_MOVE) | |
830 | < 0) | |
831 | server_error (_F("Failed to add inotify watch: %s", strerror (errno))); | |
832 | else | |
833 | { | |
2ee9d8cc | 834 | // When mok_path changes, call inotify_callback(). |
86faa85b DS |
835 | const AvahiPoll *poll = avahi_threaded_poll_get (avahi_threaded_poll); |
836 | if (!poll | |
837 | || ! (avahi_inotify_watch = poll->watch_new (poll, inotify_fd, | |
838 | AVAHI_WATCH_IN, | |
839 | inotify_callback, | |
840 | NULL))) | |
841 | server_error (_("Failed to create inotify watcher")); | |
842 | } | |
843 | } | |
844 | ||
aeb9cc10 | 845 | // Run the main loop. |
56a0fe43 DB |
846 | avahi_threaded_poll_start (avahi_threaded_poll); |
847 | ||
848 | return; | |
aeb9cc10 DB |
849 | } |
850 | #endif // HAVE_AVAHI | |
851 | ||
852 | static void | |
853 | advertise_presence (CERTCertificate *cert __attribute ((unused))) | |
854 | { | |
855 | #if HAVE_AVAHI | |
56a0fe43 | 856 | avahi_publish_service (cert); |
aeb9cc10 | 857 | #else |
16560657 | 858 | server_error (_("Unable to advertise presence on the network. Avahi is not available")); |
aeb9cc10 DB |
859 | #endif |
860 | } | |
861 | ||
862 | static void | |
863 | unadvertise_presence () | |
864 | { | |
865 | #if HAVE_AVAHI | |
56a0fe43 | 866 | avahi_cleanup (); |
aeb9cc10 DB |
867 | #endif |
868 | } | |
869 | ||
b3367f63 | 870 | |
b3367f63 | 871 | |
b3367f63 | 872 | |
aeb9cc10 DB |
873 | static void |
874 | initialize (int argc, char **argv) { | |
26a39006 | 875 | pending_interrupts = 0; |
aeb9cc10 DB |
876 | setup_signals (& handle_interrupt); |
877 | ||
38452904 DB |
878 | // Seed the random number generator. Used to generate noise used during key generation. |
879 | srand (time (NULL)); | |
880 | ||
881 | // Initial values. | |
38452904 DB |
882 | use_db_password = false; |
883 | port = 0; | |
636e55b8 | 884 | max_threads = thread::hardware_concurrency(); // Default to number of processors |
8c074dc4 AJ |
885 | max_uncompressed_req_size = 50000; // 50 KB: default max uncompressed request size |
886 | max_compressed_req_size = 5000; // 5 KB: default max compressed request size | |
38452904 DB |
887 | keep_temp = false; |
888 | struct utsname utsname; | |
889 | uname (& utsname); | |
3532c11d | 890 | kernel_build_tree.insert({utsname.release, "/lib/modules/" + string(utsname.release) + "/build"}); |
38452904 DB |
891 | arch = normalize_machine (utsname.machine); |
892 | ||
893 | // Parse the arguments. This also starts the server log, if any, and should be done before | |
894 | // any messages are issued. | |
895 | parse_options (argc, argv); | |
896 | ||
aeb9cc10 | 897 | // PR11197: security prophylactics. |
878b2f3f | 898 | // Reject use as root, except via a special environment variable. |
aeb9cc10 DB |
899 | if (! getenv ("STAP_PR11197_OVERRIDE")) { |
900 | if (geteuid () == 0) | |
901 | fatal ("For security reasons, invocation of stap-serverd as root is not supported."); | |
902 | } | |
878b2f3f | 903 | |
38452904 DB |
904 | struct passwd *pw = getpwuid (geteuid ()); |
905 | if (! pw) | |
906 | fatal (_F("Unable to determine effective user name: %s", strerror (errno))); | |
907 | string username = pw->pw_name; | |
aeb9cc10 | 908 | pid_t pid = getpid (); |
38452904 | 909 | log (_F("===== compile server pid %d starting as %s =====", pid, username.c_str ())); |
aeb9cc10 DB |
910 | |
911 | // Where is the ssl certificate/key database? | |
912 | if (cert_db_path.empty ()) | |
913 | cert_db_path = server_cert_db_path (); | |
914 | ||
915 | // Make sure NSPR is initialized. Must be done before NSS is initialized | |
916 | PR_Init (PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); | |
917 | /* Set the cert database password callback. */ | |
918 | PK11_SetPasswordFunc (nssPasswordCallback); | |
b3367f63 | 919 | |
86faa85b DS |
920 | // Where are the optional machine owner keys (MOK) this server |
921 | // knows about? | |
95fe6e8d | 922 | mok_path = server_cert_db_path() + "/moks"; |
aeb9cc10 DB |
923 | } |
924 | ||
925 | static void | |
926 | cleanup () | |
927 | { | |
928 | unadvertise_presence (); | |
929 | end_log (); | |
930 | } | |
931 | ||
932 | /* Function: readDataFromSocket() | |
933 | * | |
934 | * Purpose: Read data from the socket into a temporary file. | |
935 | * | |
936 | */ | |
937 | static PRInt32 | |
938 | readDataFromSocket(PRFileDesc *sslSocket, const char *requestFileName) | |
939 | { | |
940 | PRFileDesc *local_file_fd = 0; | |
941 | PRInt32 numBytesExpected; | |
942 | PRInt32 numBytesRead; | |
943 | PRInt32 numBytesWritten; | |
4a044a5e | 944 | PRInt32 totalBytes = 0; |
aeb9cc10 DB |
945 | #define READ_BUFFER_SIZE 4096 |
946 | char buffer[READ_BUFFER_SIZE]; | |
947 | ||
5ab345f0 | 948 | // Read the number of bytes to be received. |
5ab345f0 DB |
949 | numBytesRead = PR_Read_Complete (sslSocket, & numBytesExpected, |
950 | (PRInt32)sizeof (numBytesExpected)); | |
aeb9cc10 DB |
951 | if (numBytesRead == 0) /* EOF */ |
952 | { | |
16560657 | 953 | server_error (_("Error reading size of request file")); |
aeb9cc10 DB |
954 | goto done; |
955 | } | |
956 | if (numBytesRead < 0) | |
957 | { | |
16560657 | 958 | server_error (_("Error in PR_Read")); |
aeb9cc10 DB |
959 | nssError (); |
960 | goto done; | |
961 | } | |
962 | ||
963 | /* Convert numBytesExpected from network byte order to host byte order. */ | |
964 | numBytesExpected = ntohl (numBytesExpected); | |
965 | ||
966 | /* If 0 bytes are expected, then we were contacted only to obtain our certificate. | |
967 | There is no client request. */ | |
968 | if (numBytesExpected == 0) | |
969 | return 0; | |
970 | ||
c4798c0f AJ |
971 | /* Impose a limit to prevent disk space consumption DoS */ |
972 | if (numBytesExpected > (PRInt32) max_compressed_req_size) | |
973 | { | |
974 | server_error (_("Error size of (compressed) request file is too large")); | |
975 | goto done; | |
976 | } | |
977 | ||
aeb9cc10 DB |
978 | /* Open the output file. */ |
979 | local_file_fd = PR_Open(requestFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, | |
980 | PR_IRUSR | PR_IWUSR); | |
981 | if (local_file_fd == NULL) | |
982 | { | |
16560657 | 983 | server_error (_F("Could not open output file %s", requestFileName)); |
aeb9cc10 DB |
984 | nssError (); |
985 | return -1; | |
986 | } | |
987 | ||
5ab345f0 | 988 | // Read until EOF or until the expected number of bytes has been read. |
aeb9cc10 DB |
989 | for (totalBytes = 0; totalBytes < numBytesExpected; totalBytes += numBytesRead) |
990 | { | |
5ab345f0 DB |
991 | // No need for PR_Read_Complete here, since we're already managing multiple |
992 | // reads to a fixed size buffer. | |
993 | numBytesRead = PR_Read (sslSocket, buffer, READ_BUFFER_SIZE); | |
aeb9cc10 DB |
994 | if (numBytesRead == 0) |
995 | break; /* EOF */ | |
996 | if (numBytesRead < 0) | |
997 | { | |
16560657 | 998 | server_error (_("Error in PR_Read")); |
aeb9cc10 DB |
999 | nssError (); |
1000 | goto done; | |
1001 | } | |
1002 | ||
1003 | /* Write to the request file. */ | |
1004 | numBytesWritten = PR_Write(local_file_fd, buffer, numBytesRead); | |
1005 | if (numBytesWritten < 0 || (numBytesWritten != numBytesRead)) | |
1006 | { | |
16560657 | 1007 | server_error (_F("Could not write to output file %s", requestFileName)); |
aeb9cc10 DB |
1008 | nssError (); |
1009 | goto done; | |
1010 | } | |
1011 | } | |
1012 | ||
1013 | if (totalBytes != numBytesExpected) | |
1014 | { | |
16560657 | 1015 | server_error (_F("Expected %d bytes, got %d while reading client request from socket", |
aeb9cc10 DB |
1016 | numBytesExpected, totalBytes)); |
1017 | goto done; | |
1018 | } | |
1019 | ||
1020 | done: | |
1021 | if (local_file_fd) | |
1022 | PR_Close (local_file_fd); | |
1023 | return totalBytes; | |
1024 | } | |
1025 | ||
1026 | /* Function: setupSSLSocket() | |
1027 | * | |
1028 | * Purpose: Configure a socket for SSL. | |
6cb00a60 | 1029 | * In case of failure, clean up the incoming tcpSocket and/or any partially setup sslSocket. |
aeb9cc10 DB |
1030 | * |
1031 | */ | |
1032 | static PRFileDesc * | |
1033 | setupSSLSocket (PRFileDesc *tcpSocket, CERTCertificate *cert, SECKEYPrivateKey *privKey) | |
1034 | { | |
1035 | PRFileDesc *sslSocket; | |
1036 | SSLKEAType certKEA; | |
1037 | SECStatus secStatus; | |
1038 | ||
1039 | /* Inport the socket into SSL. */ | |
1040 | sslSocket = SSL_ImportFD (NULL, tcpSocket); | |
1041 | if (sslSocket == NULL) | |
1042 | { | |
16560657 | 1043 | server_error (_("Could not import socket into SSL")); |
6cb00a60 | 1044 | goto error; |
aeb9cc10 DB |
1045 | } |
1046 | ||
1047 | /* Set the appropriate flags. */ | |
1048 | secStatus = SSL_OptionSet (sslSocket, SSL_SECURITY, PR_TRUE); | |
1049 | if (secStatus != SECSuccess) | |
1050 | { | |
16560657 | 1051 | server_error (_("Error setting SSL security for socket")); |
6cb00a60 | 1052 | goto error; |
aeb9cc10 DB |
1053 | } |
1054 | ||
1055 | secStatus = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_SERVER, PR_TRUE); | |
1056 | if (secStatus != SECSuccess) | |
1057 | { | |
16560657 | 1058 | server_error (_("Error setting handshake as server for socket")); |
6cb00a60 | 1059 | goto error; |
aeb9cc10 DB |
1060 | } |
1061 | ||
1062 | secStatus = SSL_OptionSet(sslSocket, SSL_REQUEST_CERTIFICATE, PR_FALSE); | |
1063 | if (secStatus != SECSuccess) | |
1064 | { | |
16560657 | 1065 | server_error (_("Error setting SSL client authentication mode for socket")); |
6cb00a60 | 1066 | goto error; |
aeb9cc10 DB |
1067 | } |
1068 | ||
1069 | secStatus = SSL_OptionSet(sslSocket, SSL_REQUIRE_CERTIFICATE, PR_FALSE); | |
1070 | if (secStatus != SECSuccess) | |
1071 | { | |
16560657 | 1072 | server_error (_("Error setting SSL client authentication mode for socket")); |
6cb00a60 | 1073 | goto error; |
aeb9cc10 DB |
1074 | } |
1075 | ||
1076 | /* Set the appropriate callback routines. */ | |
1077 | #if 0 /* use the default */ | |
1078 | secStatus = SSL_AuthCertificateHook (sslSocket, myAuthCertificate, CERT_GetDefaultCertDB()); | |
1079 | if (secStatus != SECSuccess) | |
1080 | { | |
16560657 | 1081 | server_error (_("Error in SSL_AuthCertificateHook")); |
6cb00a60 | 1082 | goto error; |
aeb9cc10 DB |
1083 | } |
1084 | #endif | |
1085 | #if 0 /* Use the default */ | |
1086 | secStatus = SSL_BadCertHook(sslSocket, (SSLBadCertHandler)myBadCertHandler, &certErr); | |
1087 | if (secStatus != SECSuccess) | |
1088 | { | |
16560657 | 1089 | server_error (_("Error in SSL_BadCertHook")); |
6cb00a60 | 1090 | goto error; |
aeb9cc10 DB |
1091 | } |
1092 | #endif | |
1093 | #if 0 /* no handshake callback */ | |
1094 | secStatus = SSL_HandshakeCallback(sslSocket, myHandshakeCallback, NULL); | |
1095 | if (secStatus != SECSuccess) | |
1096 | { | |
16560657 | 1097 | server_error (_("Error in SSL_HandshakeCallback")); |
6cb00a60 | 1098 | goto error; |
aeb9cc10 DB |
1099 | } |
1100 | #endif | |
1101 | ||
1102 | certKEA = NSS_FindCertKEAType (cert); | |
1103 | ||
1104 | secStatus = SSL_ConfigSecureServer (sslSocket, cert, privKey, certKEA); | |
1105 | if (secStatus != SECSuccess) | |
1106 | { | |
16560657 | 1107 | server_error (_("Error configuring SSL server")); |
6cb00a60 | 1108 | goto error; |
aeb9cc10 DB |
1109 | } |
1110 | ||
1111 | return sslSocket; | |
6cb00a60 FCE |
1112 | |
1113 | error: | |
1114 | nssError(); | |
1115 | ||
1116 | if (sslSocket) { | |
1117 | if (PR_Close (sslSocket) != PR_SUCCESS) { | |
1118 | server_error (_("Error closing ssl socket")); | |
1119 | nssError (); | |
1120 | } | |
1121 | } else { | |
1122 | if (PR_Close (tcpSocket) != PR_SUCCESS) { | |
1123 | server_error (_("Error closing tcp/ssl socket")); | |
1124 | nssError (); | |
1125 | } | |
1126 | } | |
1127 | ||
1128 | return NULL; | |
aeb9cc10 DB |
1129 | } |
1130 | ||
1131 | #if 0 /* No client authentication (for now) and not authenticating after each transaction. */ | |
1132 | /* Function: authenticateSocket() | |
1133 | * | |
1134 | * Purpose: Perform client authentication on the socket. | |
1135 | * | |
1136 | */ | |
1137 | static SECStatus | |
1138 | authenticateSocket (PRFileDesc *sslSocket, PRBool requireCert) | |
1139 | { | |
1140 | CERTCertificate *cert; | |
1141 | SECStatus secStatus; | |
1142 | ||
1143 | /* Returns NULL if client authentication is not enabled or if the | |
1144 | * client had no certificate. */ | |
1145 | cert = SSL_PeerCertificate(sslSocket); | |
1146 | if (cert) | |
1147 | { | |
1148 | /* Client had a certificate, so authentication is through. */ | |
1149 | CERT_DestroyCertificate(cert); | |
1150 | return SECSuccess; | |
1151 | } | |
1152 | ||
1153 | /* Request client to authenticate itself. */ | |
1154 | secStatus = SSL_OptionSet(sslSocket, SSL_REQUEST_CERTIFICATE, PR_TRUE); | |
1155 | if (secStatus != SECSuccess) | |
1156 | { | |
16560657 | 1157 | server_error (_("Error in SSL_OptionSet:SSL_REQUEST_CERTIFICATE")); |
aeb9cc10 DB |
1158 | nssError (); |
1159 | return SECFailure; | |
1160 | } | |
1161 | ||
1162 | /* If desired, require client to authenticate itself. Note | |
1163 | * SSL_REQUEST_CERTIFICATE must also be on, as above. */ | |
1164 | secStatus = SSL_OptionSet(sslSocket, SSL_REQUIRE_CERTIFICATE, requireCert); | |
1165 | if (secStatus != SECSuccess) | |
1166 | { | |
16560657 | 1167 | server_error (_("Error in SSL_OptionSet:SSL_REQUIRE_CERTIFICATE")); |
aeb9cc10 DB |
1168 | nssError (); |
1169 | return SECFailure; | |
1170 | } | |
1171 | ||
1172 | /* Having changed socket configuration parameters, redo handshake. */ | |
1173 | secStatus = SSL_ReHandshake(sslSocket, PR_TRUE); | |
1174 | if (secStatus != SECSuccess) | |
1175 | { | |
16560657 | 1176 | server_error (_("Error in SSL_ReHandshake")); |
aeb9cc10 DB |
1177 | nssError (); |
1178 | return SECFailure; | |
1179 | } | |
1180 | ||
1181 | /* Force the handshake to complete before moving on. */ | |
1182 | secStatus = SSL_ForceHandshake(sslSocket); | |
1183 | if (secStatus != SECSuccess) | |
1184 | { | |
16560657 | 1185 | server_error (_("Error in SSL_ForceHandshake")); |
aeb9cc10 DB |
1186 | nssError (); |
1187 | return SECFailure; | |
1188 | } | |
1189 | ||
1190 | return SECSuccess; | |
1191 | } | |
1192 | #endif /* No client authentication and not authenticating after each transaction. */ | |
1193 | ||
1194 | /* Function: writeDataToSocket | |
1195 | * | |
1196 | * Purpose: Write the server's response back to the socket. | |
1197 | * | |
1198 | */ | |
1199 | static SECStatus | |
1200 | writeDataToSocket(PRFileDesc *sslSocket, const char *responseFileName) | |
1201 | { | |
1202 | PRFileDesc *local_file_fd = PR_Open (responseFileName, PR_RDONLY, 0); | |
1203 | if (local_file_fd == NULL) | |
1204 | { | |
16560657 | 1205 | server_error (_F("Could not open input file %s", responseFileName)); |
aeb9cc10 DB |
1206 | nssError (); |
1207 | return SECFailure; | |
1208 | } | |
1209 | ||
1210 | /* Transmit the local file across the socket. | |
1211 | */ | |
1212 | int numBytes = PR_TransmitFile (sslSocket, local_file_fd, | |
1213 | NULL, 0, | |
1214 | PR_TRANSMITFILE_KEEP_OPEN, | |
1215 | PR_INTERVAL_NO_TIMEOUT); | |
1216 | ||
1217 | /* Error in transmission. */ | |
1218 | SECStatus secStatus = SECSuccess; | |
1219 | if (numBytes < 0) | |
1220 | { | |
16560657 | 1221 | server_error (_("Error writing response to socket")); |
aeb9cc10 DB |
1222 | nssError (); |
1223 | secStatus = SECFailure; | |
1224 | } | |
1225 | ||
1226 | PR_Close (local_file_fd); | |
1227 | return secStatus; | |
1228 | } | |
1229 | ||
cc7c72cd | 1230 | static void |
26a39006 | 1231 | get_stap_locale (const string &staplang, vector<string> &envVec, string stapstderr, cs_protocol_version *client_version) |
cc7c72cd | 1232 | { |
c89fe89c | 1233 | // If the client version is < 1.6, then no file containing environment |
cc7c72cd | 1234 | // variables defining the locale has been passed. |
26a39006 | 1235 | if (*client_version < "1.6") |
cc7c72cd DB |
1236 | return; |
1237 | ||
1238 | /* Go through each line of the file, verify it, then add it to the vector */ | |
1239 | ifstream langfile; | |
1240 | langfile.open(staplang.c_str()); | |
1241 | if (!langfile.is_open()) | |
1242 | { | |
1243 | // Not fatal. Proceed with the environment we have. | |
16560657 | 1244 | server_error(_F("Unable to open file %s for reading: %s", staplang.c_str(), |
cc7c72cd DB |
1245 | strerror (errno))); |
1246 | return; | |
1247 | } | |
1248 | ||
1249 | /* Unpackage internationalization variables and verify their contents */ | |
1250 | map<string, string> envMap; /* To temporarily store the entire array of strings */ | |
1251 | string line; | |
1252 | const set<string> &locVars = localization_variables(); | |
1253 | ||
1254 | /* Copy the global environ variable into the map */ | |
9f4cd64d CM |
1255 | if(environ != NULL) |
1256 | { | |
1257 | for (unsigned i=0; environ[i]; i++) | |
1258 | { | |
61019d8d | 1259 | string line = (string)environ[i]; |
9f4cd64d | 1260 | |
61019d8d CM |
1261 | /* Find the first '=' sign */ |
1262 | size_t pos = line.find("="); | |
1263 | ||
1264 | /* Make sure it found an '=' sign */ | |
1265 | if(pos != string::npos) | |
1266 | /* Everything before the '=' sign is the key, and everything after is the value. */ | |
1267 | envMap[line.substr(0, pos)] = line.substr(pos+1); | |
9f4cd64d CM |
1268 | } |
1269 | } | |
cc7c72cd DB |
1270 | |
1271 | /* Create regular expression objects to verify lines read from file. Should not allow | |
1272 | spaces, ctrl characters, etc */ | |
1273 | regex_t checkre; | |
4852f9d2 | 1274 | if ((regcomp(&checkre, "^[a-zA-Z0-9@_.=-]*$", REG_EXTENDED | REG_NOSUB) != 0)) |
cc7c72cd DB |
1275 | { |
1276 | // Not fatal. Proceed with the environment we have. | |
16560657 | 1277 | server_error(_F("Error in regcomp: %s", strerror (errno))); |
cc7c72cd DB |
1278 | return; |
1279 | } | |
1280 | ||
1281 | while (1) | |
1282 | { | |
1283 | getline(langfile, line); | |
1284 | if (!langfile.good()) | |
1285 | break; | |
1286 | ||
1287 | /* Extract key and value from the line. Note: value may contain "=". */ | |
1288 | string key; | |
1289 | string value; | |
1290 | size_t pos; | |
1291 | pos = line.find("="); | |
1d7ae21b FCE |
1292 | if (pos == string::npos) |
1293 | { | |
49398925 | 1294 | client_error(_F("Localization key=value line '%s' cannot be parsed", line.c_str()), stapstderr); |
1d7ae21b FCE |
1295 | continue; |
1296 | } | |
cc7c72cd DB |
1297 | key = line.substr(0, pos); |
1298 | pos++; | |
1299 | value = line.substr(pos); | |
1300 | ||
1301 | /* Make sure the key is found in the localization variables global set */ | |
1302 | if (locVars.find(key) == locVars.end()) | |
1303 | { | |
1304 | // Not fatal. Just ignore it. | |
49398925 | 1305 | client_error(_F("Localization key '%s' not found in global list", key.c_str()), stapstderr); |
16560657 | 1306 | continue; |
cc7c72cd DB |
1307 | } |
1308 | ||
1309 | /* Make sure the value does not contain illegal characters */ | |
1310 | if ((regexec(&checkre, value.c_str(), (size_t) 0, NULL, 0) != 0)) | |
1311 | { | |
1312 | // Not fatal. Just ignore it. | |
49398925 | 1313 | client_error(_F("Localization value '%s' contains illegal characters", value.c_str()), stapstderr); |
16560657 | 1314 | continue; |
cc7c72cd DB |
1315 | } |
1316 | ||
1317 | /* All is good, copy line into envMap, replacing if already there */ | |
1318 | envMap[key] = value; | |
1319 | } | |
1320 | ||
1321 | if (!langfile.eof()) | |
1322 | { | |
1323 | // Not fatal. Proceed with what we have. | |
16560657 | 1324 | server_error(_F("Error reading file %s: %s", staplang.c_str(), strerror (errno))); |
cc7c72cd DB |
1325 | } |
1326 | ||
1327 | regfree(&checkre); | |
1328 | ||
1329 | /* Copy map into vector */ | |
1330 | for (map<string, string>::iterator it = envMap.begin(); it != envMap.end(); it++) | |
1331 | envVec.push_back(it->first + "=" + it->second); | |
1332 | } | |
aeb9cc10 | 1333 | |
b3367f63 DS |
1334 | static void |
1335 | get_client_mok_fingerprints (const string &filename, | |
1336 | vector<string> &mok_fingerprints, | |
1337 | string stapstderr, | |
1338 | cs_protocol_version *client_version) | |
1339 | { | |
1340 | // If the client version is < 1.6, then no file containing MOK | |
1341 | // fingerprints could have been passed. | |
1342 | if (*client_version < "1.6") { | |
1343 | return; | |
1344 | } | |
1345 | ||
1346 | // Go through each line of the file and add it to the vector. | |
1347 | ifstream file; | |
1348 | file.open(filename.c_str()); | |
1349 | if (! file.is_open()) | |
1350 | // If the file isn't present, that's fine. It just means that the | |
1351 | // module doesn't need to be signed. | |
1352 | return; | |
1353 | ||
1354 | // Create a regular expression object to verify lines read from the | |
1355 | // file. | |
1356 | regex_t checkre; | |
1357 | if (regcomp(&checkre, "^([0-9a-f]{2}(:[0-9a-f]{2})+)$", REG_EXTENDED) | |
1358 | != 0) | |
1359 | { | |
1360 | // Not fatal, just ignore the MOK fingerprints. | |
1361 | server_error(_F("Error in MOK fingerprint regcomp: %s", | |
1362 | strerror (errno))); | |
1363 | return; | |
1364 | } | |
1365 | ||
1366 | // Unpack the MOK fingerprints. Notice we make sure the fingerprint | |
1367 | // is in the right format, but that's all we can do at this | |
1368 | // point. Later we'll check this client list against our server | |
1369 | // list. | |
1370 | string line; | |
1371 | regmatch_t matches[3]; | |
1372 | while (getline (file, line)) | |
1373 | { | |
cd1418c7 | 1374 | string fingerprint; |
b3367f63 | 1375 | |
3b02cd57 FCE |
1376 | if (line == "missing") // PR26665: needs signature, but has no useful mok key |
1377 | { | |
1378 | // save it, so handleRequest() sends back a new MOK pubkey | |
1379 | mok_fingerprints.push_back(line); | |
1380 | continue; | |
1381 | } | |
1382 | ||
b3367f63 DS |
1383 | if ((regexec(&checkre, line.c_str(), 3, matches, 0) != 0)) |
1384 | { | |
1385 | // Not fatal. Just ignore it. | |
3b02cd57 | 1386 | client_error(_F("MOK fingerprint value '%s' isn't in the correct format", |
b3367f63 DS |
1387 | line.c_str()), stapstderr); |
1388 | continue; | |
1389 | } | |
1390 | ||
1391 | // Save the fingerprint: | |
1392 | // matches[0] is the range of the entire match | |
1393 | // matches[1] is the entire fingerprint | |
1394 | // matches[2] is a portion of the fingerprint | |
1395 | if (matches[1].rm_so >= 0) | |
1396 | fingerprint = line.substr(matches[1].rm_so, | |
1397 | matches[1].rm_eo - matches[1].rm_so); | |
1398 | if (! fingerprint.empty()) | |
1399 | mok_fingerprints.push_back(fingerprint); | |
1400 | } | |
1401 | regfree(&checkre); | |
1402 | } | |
1403 | ||
b3367f63 | 1404 | |
820f2d22 DB |
1405 | // Filter paths prefixed with the server's home directory from the given file. |
1406 | // | |
1407 | static void | |
1408 | filter_response_file (const string &file_name, const string &responseDirName) | |
1409 | { | |
1192d243 DS |
1410 | // Filter the server's home directory name (unless it is "/") |
1411 | string dir = get_home_directory(); | |
1412 | if (dir != "/") | |
1413 | { | |
1414 | string swap = string ("s,") + get_home_directory () + ",<server>,g"; | |
1415 | vector<string> cmd { "sed", "-i", swap, file_name }; | |
1416 | (void) stap_system (0, cmd); | |
1417 | } | |
1418 | ||
1419 | // Filter the server's response directory name (unless it is "/") | |
1420 | if (responseDirName != "/") | |
1421 | { | |
1422 | string swap = string ("s,") + responseDirName + ",<server>,g"; | |
1423 | vector<string> cmd = { "sed", "-i", swap, file_name }; | |
1424 | (void) stap_system (0, cmd); | |
1425 | } | |
820f2d22 DB |
1426 | } |
1427 | ||
73f52eb4 DB |
1428 | static privilege_t |
1429 | getRequestedPrivilege (const vector<string> &stapargv) | |
1430 | { | |
1431 | // The purpose of this function is to find the --privilege or --unprivileged option specified | |
1432 | // by the user on the client side. We need to parse the command line completely, but we can | |
1433 | // exit when we find the first --privilege or --unprivileged option, since stap does not allow | |
1434 | // multiple privilege levels to specified on the same command line. | |
1435 | // | |
1436 | // Note that we need not do any options consistency checking since our spawned stap instance | |
1437 | // will do that. | |
1438 | // | |
1439 | // Create an argv/argc for use by getopt_long. | |
1440 | int argc = stapargv.size(); | |
1441 | char ** argv = new char *[argc + 1]; | |
1442 | for (unsigned i = 0; i < stapargv.size(); ++i) | |
1443 | argv[i] = (char *)stapargv[i].c_str(); | |
1444 | argv[argc] = NULL; | |
1445 | ||
1446 | privilege_t privilege = pr_highest; // Until specified otherwise. | |
1447 | optind = 1; | |
1448 | while (true) | |
1449 | { | |
1450 | // We need only allow getopt to parse the options until we find a | |
1451 | // --privilege or --unprivileged option. | |
1452 | int grc = getopt_long (argc, argv, STAP_SHORT_OPTIONS, stap_long_options, NULL); | |
1453 | if (grc < 0) | |
1454 | break; | |
1455 | switch (grc) | |
1456 | { | |
1457 | default: | |
372c6458 | 1458 | // We can ignore all options other than --privilege and --unprivileged. |
73f52eb4 | 1459 | break; |
372c6458 DB |
1460 | case LONG_OPT_PRIVILEGE: |
1461 | if (strcmp (optarg, "stapdev") == 0) | |
1462 | privilege = pr_stapdev; | |
1463 | else if (strcmp (optarg, "stapsys") == 0) | |
1464 | privilege = pr_stapsys; | |
1465 | else if (strcmp (optarg, "stapusr") == 0) | |
1466 | privilege = pr_stapusr; | |
1467 | else | |
1468 | { | |
1469 | server_error (_F("Invalid argument '%s' for --privilege", optarg)); | |
1470 | privilege = pr_highest; | |
73f52eb4 | 1471 | } |
372c6458 DB |
1472 | // We have discovered the client side --privilege option. We can exit now since |
1473 | // stap only tolerates one privilege setting option. | |
1474 | goto done; // break 2 switches and a loop | |
1475 | case LONG_OPT_UNPRIVILEGED: | |
1476 | privilege = pr_unprivileged; | |
1477 | // We have discovered the client side --unprivileged option. We can exit now since | |
1478 | // stap only tolerates one privilege setting option. | |
1479 | goto done; // break 2 switches and a loop | |
73f52eb4 DB |
1480 | } |
1481 | } | |
1482 | done: | |
1483 | delete[] argv; | |
1484 | return privilege; | |
1485 | } | |
1486 | ||
cd2af5d8 | 1487 | |
aeb9cc10 DB |
1488 | /* Run the translator on the data in the request directory, and produce output |
1489 | in the given output directory. */ | |
1490 | static void | |
49398925 | 1491 | handleRequest (const string &requestDirName, const string &responseDirName, string stapstderr) |
aeb9cc10 | 1492 | { |
aeb9cc10 | 1493 | vector<string> stapargv; |
26a39006 | 1494 | cs_protocol_version client_version = "1.0"; // Assumed until discovered otherwise |
aeb9cc10 DB |
1495 | int rc; |
1496 | wordexp_t words; | |
1497 | unsigned u; | |
1498 | unsigned i; | |
1499 | FILE* f; | |
a8e317b6 | 1500 | int retlen; |
aeb9cc10 | 1501 | |
cc7c72cd DB |
1502 | // Save the server version. Do this early, so the client knows what version of the server |
1503 | // it is dealing with, even if the request is not fully completed. | |
16560657 | 1504 | string stapversion = responseDirName + "/version"; |
cc7c72cd DB |
1505 | f = fopen (stapversion.c_str (), "w"); |
1506 | if (f) | |
1507 | { | |
c89fe89c | 1508 | fputs (CURRENT_CS_PROTOCOL_VERSION, f); |
cc7c72cd DB |
1509 | fclose(f); |
1510 | } | |
1511 | else | |
16560657 | 1512 | server_error (_F("Unable to open client version file %s", stapversion.c_str ())); |
cc7c72cd DB |
1513 | |
1514 | // Get the client version. The default version is already set. Use it if we fail here. | |
16560657 | 1515 | string filename = requestDirName + "/version"; |
cc7c72cd DB |
1516 | if (file_exists (filename)) |
1517 | read_from_file (filename, client_version); | |
c89fe89c | 1518 | log (_F("Client version is %s", client_version.v)); |
cc7c72cd DB |
1519 | |
1520 | // The name of the translator executable. | |
aeb9cc10 DB |
1521 | stapargv.push_back ((char *)(getenv ("SYSTEMTAP_STAP") ?: STAP_PREFIX "/bin/stap")); |
1522 | ||
3532c11d FL |
1523 | // Fetch the target kernel version from request and pass flag to stap |
1524 | string kernel_version; | |
1525 | filename = requestDirName + "/sysinfo"; | |
1526 | ifstream versionfile(filename.c_str()); | |
1527 | if (versionfile >> kernel_version >> kernel_version) // Skip sysinfo: label | |
1528 | { | |
1529 | if (kernel_build_tree.find(kernel_version) != kernel_build_tree.end()) | |
1530 | { | |
1531 | stapargv.push_back("-r" + kernel_version); | |
1532 | } | |
1533 | else | |
1534 | { | |
1535 | log(_F("Target kernel version %s is not supported by this server", | |
1536 | kernel_version.c_str())); | |
1537 | return; | |
1538 | } | |
1539 | } | |
1540 | else | |
1541 | { | |
1542 | server_error(_("Unable to read sysinfo")); | |
1543 | return; | |
1544 | } | |
1545 | ||
aeb9cc10 DB |
1546 | /* Transcribe stap_options. We use plain wordexp(3), since these |
1547 | options are coming from the local trusted user, so malicious | |
1548 | content is not a concern. */ | |
1549 | // TODO: Use tokenize here. | |
1550 | rc = wordexp (stap_options.c_str (), & words, WRDE_NOCMD|WRDE_UNDEF); | |
305fac7d CM |
1551 | if (rc) |
1552 | { | |
16560657 | 1553 | server_error (_("Cannot parse stap options")); |
aeb9cc10 DB |
1554 | return; |
1555 | } | |
1556 | ||
1557 | for (u=0; u<words.we_wordc; u++) | |
1558 | stapargv.push_back (words.we_wordv[u]); | |
1559 | ||
aeb9cc10 DB |
1560 | /* Process the saved command line arguments. Avoid quoting/unquoting errors by |
1561 | transcribing literally. */ | |
16560657 | 1562 | string new_staptmpdir = responseDirName + "/stap000000"; |
305fac7d CM |
1563 | rc = mkdir(new_staptmpdir.c_str(), 0700); |
1564 | if (rc) | |
16560657 | 1565 | server_error(_F("Could not create temporary directory %s", new_staptmpdir.c_str())); |
305fac7d CM |
1566 | |
1567 | stapargv.push_back("--tmpdir=" + new_staptmpdir); | |
1568 | ||
1569 | stapargv.push_back ("--client-options"); | |
40ae3a75 AJ |
1570 | |
1571 | string stap_opts = ""; | |
aeb9cc10 DB |
1572 | for (i=1 ; ; i++) |
1573 | { | |
1574 | char stapargfile[PATH_MAX]; | |
1575 | FILE* argfile; | |
1576 | struct stat st; | |
1577 | char *arg; | |
1578 | ||
a8e317b6 SC |
1579 | retlen = snprintf (stapargfile, PATH_MAX, "%s/argv%d", requestDirName.c_str (), i); |
1580 | if (retlen < 0 || retlen >= PATH_MAX) | |
1581 | { | |
1582 | server_error (_F("Error creating %s name", "path")); | |
1583 | return; | |
1584 | } | |
aeb9cc10 DB |
1585 | |
1586 | rc = stat(stapargfile, & st); | |
1587 | if (rc) break; | |
1588 | ||
1589 | arg = (char *)malloc (st.st_size+1); | |
1590 | if (!arg) | |
1591 | { | |
16560657 | 1592 | server_error (_("Out of memory")); |
aeb9cc10 DB |
1593 | return; |
1594 | } | |
1595 | ||
1596 | argfile = fopen(stapargfile, "r"); | |
1597 | if (! argfile) | |
1598 | { | |
129b743c | 1599 | free(arg); |
16560657 | 1600 | server_error (_F("Error opening %s: %s", stapargfile, strerror (errno))); |
aeb9cc10 DB |
1601 | return; |
1602 | } | |
1603 | ||
1604 | rc = fread(arg, 1, st.st_size, argfile); | |
1605 | if (rc != st.st_size) | |
1606 | { | |
129b743c PM |
1607 | free(arg); |
1608 | fclose(argfile); | |
16560657 | 1609 | server_error (_F("Error reading %s: %s", stapargfile, strerror (errno))); |
aeb9cc10 DB |
1610 | return; |
1611 | } | |
1612 | ||
1613 | arg[st.st_size] = '\0'; | |
1614 | stapargv.push_back (arg); | |
40ae3a75 AJ |
1615 | stap_opts.append(arg); |
1616 | stap_opts.append(" "); | |
aeb9cc10 DB |
1617 | free (arg); |
1618 | fclose (argfile); | |
1619 | } | |
40ae3a75 | 1620 | log(_F("Options passed from the client: %s", stap_opts.c_str())); |
aeb9cc10 | 1621 | |
16560657 | 1622 | string stapstdout = responseDirName + "/stdout"; |
aeb9cc10 | 1623 | |
304d85ce DB |
1624 | // NB: Before, when we did not fully parse the client's command line using getopt_long, |
1625 | // we used to insert a --privilege=XXX option here in case some other argument was mistaken | |
1626 | // for a --privilege or --unprivileged option by our spawned stap. Since we now parse | |
1627 | // the client's command line using getopt_long and share the getopt_long options | |
1628 | // string and table with stap, this is no longer necessary. stap will parse the | |
1629 | // command line identically to the way we have parsed it and will discover the same | |
1630 | // privilege-setting option. | |
aeb9cc10 | 1631 | |
cc7c72cd | 1632 | // Environment variables (possibly empty) to be passed to spawn_and_wait(). |
16560657 | 1633 | string staplang = requestDirName + "/locale"; |
cc7c72cd | 1634 | vector<string> envVec; |
26a39006 | 1635 | get_stap_locale (staplang, envVec, stapstderr, &client_version); |
e4e3d6b7 | 1636 | |
b3367f63 DS |
1637 | // Machine owner keys (MOK) fingerprints (possibly nonexistent), to |
1638 | // be used as a list of valid keys that the module must be signed with. | |
1639 | vector<string> client_mok_fingerprints; | |
b3367f63 DS |
1640 | get_client_mok_fingerprints(requestDirName + "/mok_fingerprints", |
1641 | client_mok_fingerprints, stapstderr, | |
1642 | &client_version); | |
b3367f63 | 1643 | |
e657df8a | 1644 | |
c419fab7 DS |
1645 | // If the client sent us MOK fingerprints, see if we have a matching |
1646 | // MOK on the server. | |
95fe6e8d | 1647 | string mok_fingerprint; |
42be330a | 1648 | string mok_path = server_cert_db_path() + "/moks"; |
c419fab7 | 1649 | if (! client_mok_fingerprints.empty()) |
e657df8a | 1650 | { |
c419fab7 | 1651 | // See if any of the client MOK fingerprints exist on the server. |
b3367f63 DS |
1652 | vector<string>::const_iterator it; |
1653 | for (it = client_mok_fingerprints.begin(); | |
1654 | it != client_mok_fingerprints.end(); it++) | |
1655 | { | |
42be330a | 1656 | if (mok_dir_valid_p (*it, mok_path, false, server_error)) |
b3367f63 | 1657 | { |
c419fab7 | 1658 | mok_fingerprint = *it; |
b3367f63 DS |
1659 | break; |
1660 | } | |
1661 | } | |
1662 | ||
1663 | // If the client requires signing, but we couldn't find a | |
1664 | // matching machine owner key installed on the server, we can't | |
e657df8a DS |
1665 | // build a signed module. But, the client may not have asked us |
1666 | // to create a module (for instance, the user could have done | |
1667 | // 'stap -L syscall.open'). So, keep going until we know we need | |
1668 | // to sign a module. | |
1669 | } | |
b3367f63 | 1670 | |
42be330a SC |
1671 | if (! mok_fingerprint.empty ()) |
1672 | stapargv.push_back("--sign-module=" + mok_path + "/" + mok_fingerprint); | |
1673 | ||
5ed19be2 | 1674 | /* All ready, let's run the translator! */ |
e7d52b3b JL |
1675 | int staprc; |
1676 | rc = spawn_and_wait(stapargv, &staprc, "/dev/null", stapstdout.c_str (), | |
1677 | stapstderr.c_str (), requestDirName.c_str (), envVec); | |
1678 | if (rc != PR_SUCCESS) | |
aeb9cc10 | 1679 | { |
e7d52b3b JL |
1680 | server_error(_("Failed spawning translator")); |
1681 | return; | |
aeb9cc10 DB |
1682 | } |
1683 | ||
b3367f63 DS |
1684 | // In unprivileged modes, if we have a module built, we need to sign |
1685 | // the sucker. We also might need to sign the module for secure | |
1686 | // boot purposes. | |
304d85ce | 1687 | privilege_t privilege = getRequestedPrivilege (stapargv); |
e657df8a DS |
1688 | if (staprc == 0 && (pr_contains (privilege, pr_stapusr) |
1689 | || pr_contains (privilege, pr_stapsys) | |
1690 | || ! client_mok_fingerprints.empty ())) | |
aeb9cc10 | 1691 | { |
305fac7d CM |
1692 | glob_t globber; |
1693 | char pattern[PATH_MAX]; | |
a8e317b6 SC |
1694 | int retlen; |
1695 | ||
1696 | retlen = snprintf (pattern, PATH_MAX, "%s/*.ko", new_staptmpdir.c_str()); | |
1697 | if (retlen < 0 || retlen >= PATH_MAX) | |
1698 | { | |
1699 | server_error (_F("Error creating %s name", "pattern")); | |
1700 | return; | |
1701 | } | |
1702 | ||
305fac7d CM |
1703 | rc = glob (pattern, GLOB_ERR, NULL, &globber); |
1704 | if (rc) | |
16560657 | 1705 | server_error (_F("Unable to find a module in %s", new_staptmpdir.c_str())); |
305fac7d | 1706 | else if (globber.gl_pathc != 1) |
03dd566c DS |
1707 | { |
1708 | server_error (_F("Too many modules (%zu) in %s", globber.gl_pathc, new_staptmpdir.c_str())); | |
1709 | globfree(&globber); | |
1710 | } | |
305fac7d CM |
1711 | else |
1712 | { | |
b3367f63 DS |
1713 | if (pr_contains (privilege, pr_stapusr) |
1714 | || pr_contains (privilege, pr_stapsys)) | |
1715 | sign_file (cert_db_path, server_cert_nickname(), | |
1716 | globber.gl_pathv[0], | |
1717 | string(globber.gl_pathv[0]) + ".sgn"); | |
42be330a | 1718 | if (mok_fingerprint.empty() && ! client_mok_fingerprints.empty ()) |
e657df8a | 1719 | { |
a32d236c DS |
1720 | // If we're here, the client sent us MOK fingerprints |
1721 | // (since client_mok_fingerprints isn't empty), but we | |
1722 | // don't have a matching MOK on the server (since | |
95fe6e8d DS |
1723 | // mok_fingerprint is empty). So, we can't sign the |
1724 | // module. | |
a32d236c DS |
1725 | client_error (_("No matching machine owner key (MOK) available on the server to sign the\n module."), stapstderr); |
1726 | ||
3b02cd57 FCE |
1727 | // Well akksshually ... we could sign the new module with |
1728 | // this key preemptively ... and return the MOK public | |
1729 | // key. But that wouldn't help the client machine, | |
1730 | // because it won't be able to run the signed .ko until | |
1731 | // a MOK enroll + UEFI reboot anyway. | |
1732 | ||
a32d236c DS |
1733 | // Since we can't sign the module, send the client one |
1734 | // of our MOKs. If we don't have any, create one. | |
c419fab7 DS |
1735 | vector<string> mok_fingerprints; |
1736 | get_server_mok_fingerprints(mok_fingerprints, false, true); | |
1737 | if (mok_fingerprints.empty ()) | |
a32d236c | 1738 | { |
cd2af5d8 | 1739 | // Generate a new MOK. |
2f228e21 | 1740 | generate_mok(mok_fingerprint, server_error); |
a32d236c DS |
1741 | } |
1742 | else | |
1743 | { | |
1744 | // At this point we have at least one MOK on the | |
1745 | // server. Send the public key down to the | |
c419fab7 DS |
1746 | // client. |
1747 | mok_fingerprint = *mok_fingerprints.begin (); | |
a32d236c DS |
1748 | } |
1749 | ||
95fe6e8d | 1750 | if (! mok_fingerprint.empty ()) |
a32d236c DS |
1751 | { |
1752 | // Copy the public cert file to the response directory. | |
95fe6e8d DS |
1753 | string mok_directory = mok_path + "/" + mok_fingerprint; |
1754 | string src = mok_directory + MOK_PUBLIC_CERT_FILE; | |
a32d236c DS |
1755 | string dst = responseDirName + MOK_PUBLIC_CERT_FILE; |
1756 | if (copy_file (src, dst, true)) | |
3b02cd57 | 1757 | 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\t# sudo mokutil --import signing_key.x509", stapstderr); |
a32d236c DS |
1758 | else |
1759 | client_error ("The server has no machine owner key (MOK) in common with this\nsystem. The server failed to return a certificate.", stapstderr); | |
1760 | } | |
1761 | else | |
1762 | { | |
1763 | client_error ("The server has no machine owner keys (MOK) in common with this\nsystem. The server could not generate a new MOK.", stapstderr); | |
1764 | } | |
e657df8a | 1765 | |
e657df8a DS |
1766 | // If we couldn't sign the module, let's change the |
1767 | // staprc to 1, so that the client won't try to run the | |
1768 | // resulting module, which wouldn't work. | |
1769 | staprc = 1; | |
1770 | } | |
03dd566c | 1771 | globfree(&globber); |
305fac7d | 1772 | } |
aeb9cc10 DB |
1773 | } |
1774 | ||
e657df8a DS |
1775 | // Save the RC (which might have gotten changed above). |
1776 | ofstream ofs((responseDirName + "/rc").c_str()); | |
1777 | ofs << staprc; | |
1778 | ofs.close(); | |
1779 | ||
7d26ee02 JS |
1780 | /* If uprobes.ko is required, it will have been built or cache-copied into |
1781 | * the temp directory. We need to pack it into the response where the client | |
71a522b5 | 1782 | * can find it, and sign, if necessary, for unprivileged users. |
7d26ee02 JS |
1783 | */ |
1784 | string uprobes_ko = new_staptmpdir + "/uprobes/uprobes.ko"; | |
1785 | if (get_file_size(uprobes_ko) > 0) | |
1786 | { | |
71a522b5 | 1787 | /* uprobes.ko is required. |
7d26ee02 | 1788 | * |
71a522b5 DB |
1789 | * It's already underneath the stap tmpdir, but older stap clients |
1790 | * don't know to look for it there, so, for these clients, we end up packing uprobes twice | |
1791 | * into the zip. We could move instead of symlink. | |
7d26ee02 | 1792 | */ |
71a522b5 DB |
1793 | string uprobes_response; |
1794 | if (client_version < "1.6") | |
1795 | { | |
1796 | uprobes_response = (string)responseDirName + "/uprobes.ko"; | |
1797 | rc = symlink(uprobes_ko.c_str(), uprobes_response.c_str()); | |
1798 | if (rc != 0) | |
1799 | server_error (_F("Could not link to %s from %s", | |
1800 | uprobes_ko.c_str(), uprobes_response.c_str())); | |
1801 | } | |
1802 | else | |
1803 | uprobes_response = uprobes_ko; | |
7d26ee02 JS |
1804 | |
1805 | /* In unprivileged mode, we need a signature on uprobes as well. */ | |
f66bb29a | 1806 | if (! pr_contains (privilege, pr_stapdev)) |
305fac7d | 1807 | { |
7d26ee02 JS |
1808 | sign_file (cert_db_path, server_cert_nickname(), |
1809 | uprobes_response, uprobes_response + ".sgn"); | |
aeb9cc10 | 1810 | } |
e657df8a DS |
1811 | |
1812 | // Notice we're not giving an error message here if the client | |
1813 | // requires signed modules. The error will have been generated | |
1814 | // above on the systemtap module itself. | |
95fe6e8d | 1815 | if (! mok_fingerprint.empty ()) |
2f228e21 | 1816 | { |
42be330a SC |
1817 | if (int rc = mok_sign_file (mok_fingerprint, |
1818 | mok_path, | |
1819 | kernel_build_tree[kernel_version], | |
1820 | uprobes_response)) | |
2f228e21 SC |
1821 | client_error (_F("Running sign-file failed, rc = %d", rc), stapstderr); |
1822 | else | |
1823 | cerr << _F("Module signed with MOK, fingerprint \"%s\"", // | |
1824 | mok_fingerprint.c_str()) << endl; | |
1825 | } | |
aeb9cc10 DB |
1826 | } |
1827 | ||
1828 | /* Free up all the arg string copies. Note that the first few were alloc'd | |
1829 | by wordexp(), which wordfree() frees; others were hand-set to literal strings. */ | |
1830 | wordfree (& words); | |
1831 | ||
820f2d22 DB |
1832 | // Filter paths prefixed with the server's home directory from the stdout and stderr |
1833 | // files in the response. | |
1834 | filter_response_file (stapstdout, responseDirName); | |
1835 | filter_response_file (stapstderr, responseDirName); | |
1836 | ||
aeb9cc10 DB |
1837 | /* Sorry about the inconvenience. C string/file processing is such a pleasure. */ |
1838 | } | |
1839 | ||
1840 | ||
1841 | /* A front end for stap_spawn that handles stdin, stdout, stderr, switches to a working | |
1842 | directory and returns overall success or failure. */ | |
1843 | static PRStatus | |
e7d52b3b | 1844 | spawn_and_wait (const vector<string> &argv, int *spawnrc, |
5ed19be2 | 1845 | const char* fd0, const char* fd1, const char* fd2, |
878b2f3f | 1846 | const char *pwd, const vector<string>& envVec) |
aeb9cc10 DB |
1847 | { |
1848 | pid_t pid; | |
1849 | int rc; | |
1850 | posix_spawn_file_actions_t actions; | |
1851 | int dotfd = -1; | |
1852 | ||
16560657 | 1853 | #define CHECKRC(msg) do { if (rc) { server_error (_(msg)); return PR_FAILURE; } } while (0) |
aeb9cc10 DB |
1854 | |
1855 | rc = posix_spawn_file_actions_init (& actions); | |
1856 | CHECKRC ("Error in spawn file actions ctor"); | |
1857 | if (fd0) { | |
1858 | rc = posix_spawn_file_actions_addopen(& actions, 0, fd0, O_RDONLY, 0600); | |
1859 | CHECKRC ("Error in spawn file actions fd0"); | |
1860 | } | |
1861 | if (fd1) { | |
1862 | rc = posix_spawn_file_actions_addopen(& actions, 1, fd1, O_WRONLY|O_CREAT, 0600); | |
1863 | CHECKRC ("Error in spawn file actions fd1"); | |
1864 | } | |
1865 | if (fd2) { | |
16560657 DB |
1866 | // Use append mode for stderr because it gets written to in other places in the server. |
1867 | rc = posix_spawn_file_actions_addopen(& actions, 2, fd2, O_WRONLY|O_APPEND|O_CREAT, 0600); | |
aeb9cc10 DB |
1868 | CHECKRC ("Error in spawn file actions fd2"); |
1869 | } | |
1870 | ||
1871 | /* change temporarily to a directory if requested */ | |
1872 | if (pwd) | |
1873 | { | |
1874 | dotfd = open (".", O_RDONLY); | |
1875 | if (dotfd < 0) | |
129b743c | 1876 | { |
16560657 | 1877 | server_error (_("Error in spawn getcwd")); |
aeb9cc10 DB |
1878 | return PR_FAILURE; |
1879 | } | |
129b743c | 1880 | |
aeb9cc10 | 1881 | rc = chdir (pwd); |
129b743c PM |
1882 | if (rc) |
1883 | { | |
1884 | close(dotfd); | |
1885 | server_error(_("Error in spawn chdir")); | |
1886 | return PR_FAILURE; | |
1887 | } | |
1888 | } | |
1889 | ||
878b2f3f CM |
1890 | pid = stap_spawn (0, argv, & actions, envVec); |
1891 | /* NB: don't react to pid==-1 right away; need to chdir back first. */ | |
aeb9cc10 DB |
1892 | |
1893 | if (pwd && dotfd >= 0) | |
1894 | { | |
1895 | int subrc; | |
1896 | subrc = fchdir (dotfd); | |
1897 | subrc |= close (dotfd); | |
1898 | if (subrc) | |
16560657 | 1899 | server_error (_("Error in spawn unchdir")); |
aeb9cc10 DB |
1900 | } |
1901 | ||
1902 | if (pid == -1) | |
1903 | { | |
16560657 | 1904 | server_error (_F("Error in spawn: %s", strerror (errno))); |
aeb9cc10 DB |
1905 | return PR_FAILURE; |
1906 | } | |
1907 | ||
e7d52b3b | 1908 | *spawnrc = stap_waitpid (0, pid); |
13efed2a | 1909 | if (*spawnrc == -1) // something wrong with waitpid() call itself |
aeb9cc10 | 1910 | { |
16560657 | 1911 | server_error (_("Error in waitpid")); |
aeb9cc10 DB |
1912 | return PR_FAILURE; |
1913 | } | |
1914 | ||
1915 | rc = posix_spawn_file_actions_destroy (&actions); | |
1916 | CHECKRC ("Error in spawn file actions dtor"); | |
1917 | ||
1918 | return PR_SUCCESS; | |
1919 | #undef CHECKRC | |
1920 | } | |
1921 | ||
c4798c0f | 1922 | /* Given the path to the compressed request file, return 0 if the size of the |
8c074dc4 | 1923 | * uncompressed request is within the determined limit. */ |
7ac8a2e2 | 1924 | int |
c4798c0f | 1925 | check_uncompressed_request_size (const char * zip_file) |
7ac8a2e2 | 1926 | { |
a3ff9800 | 1927 | ostringstream result; |
a3ff9800 | 1928 | |
8c074dc4 | 1929 | // Generate the command to heck the uncompressed size |
4c847338 | 1930 | vector<string> args { "unzip", "-Zt", zip_file }; |
a3ff9800 AJ |
1931 | int rc = stap_system_read (0, args, result); |
1932 | if (rc != 0) | |
1933 | { | |
42be330a | 1934 | server_error (_F("Unable to check the zipfile size. Error code: %d .", rc)); |
a3ff9800 AJ |
1935 | return rc; |
1936 | } | |
a3ff9800 AJ |
1937 | |
1938 | // Parse the result from the unzip call, looking for the third token | |
1939 | vector<string> toks; | |
1940 | tokenize(result.str(), toks, " "); | |
1941 | if (toks.size() < 3) | |
1942 | { | |
1943 | // Something went wrong and the format is probably not what we expect. | |
1944 | server_error("Unable to check the uncompressed zipfile size. Output came in an unexpected format."); | |
1945 | return -1; | |
1946 | } | |
1947 | ||
1948 | long uncomp_size = atol(toks[2].c_str()); | |
ed4873c2 | 1949 | if (uncomp_size < 1 || (unsigned)uncomp_size > max_uncompressed_req_size) |
a3ff9800 AJ |
1950 | { |
1951 | server_error(_F("Uncompressed request size of %ld bytes is not within the expected range of 1 to %zu bytes.", | |
ed4873c2 | 1952 | uncomp_size,max_uncompressed_req_size)); |
a3ff9800 AJ |
1953 | return -1; |
1954 | } | |
1955 | ||
1956 | return 0; // If it got to this point, everthing went well. | |
7ac8a2e2 AJ |
1957 | } |
1958 | ||
49398925 | 1959 | /* Function: void *handle_connection() |
aeb9cc10 DB |
1960 | * |
1961 | * Purpose: Handle a connection to a socket. Copy in request zip | |
1962 | * file, process it, copy out response. Temporary directories are | |
1963 | * created & destroyed here. | |
1964 | */ | |
49398925 CM |
1965 | |
1966 | void * | |
1967 | handle_connection (void *arg) | |
aeb9cc10 DB |
1968 | { |
1969 | PRFileDesc * sslSocket = NULL; | |
1970 | SECStatus secStatus = SECFailure; | |
0ec2c5bf | 1971 | PRStatus prStatus; |
aeb9cc10 DB |
1972 | int rc; |
1973 | char *rc1; | |
305fac7d | 1974 | char tmpdir[PATH_MAX]; |
aeb9cc10 DB |
1975 | char requestFileName[PATH_MAX]; |
1976 | char requestDirName[PATH_MAX]; | |
1977 | char responseDirName[PATH_MAX]; | |
1978 | char responseFileName[PATH_MAX]; | |
49398925 CM |
1979 | string stapstderr; /* Cannot be global since we need a unique |
1980 | copy for each connection.*/ | |
aeb9cc10 DB |
1981 | vector<string> argv; |
1982 | PRInt32 bytesRead; | |
a8e317b6 | 1983 | int retlen; |
aeb9cc10 | 1984 | |
49398925 CM |
1985 | /* Detatch to avoid a memory leak */ |
1986 | if(max_threads > 0) | |
1987 | pthread_detach(pthread_self()); | |
1988 | ||
1989 | /* Unpack the arg */ | |
1990 | thread_arg *t_arg = (thread_arg *) arg; | |
1991 | PRFileDesc *tcpSocket = t_arg->tcpSocket; | |
1992 | CERTCertificate *cert = t_arg->cert; | |
1993 | SECKEYPrivateKey *privKey = t_arg->privKey; | |
1994 | PRNetAddr addr = t_arg->addr; | |
1995 | ||
aeb9cc10 DB |
1996 | tmpdir[0]='\0'; /* prevent cleanup-time /bin/rm of uninitialized directory */ |
1997 | ||
ba49e036 | 1998 | #if 0 // already done on the listenSocket |
aeb9cc10 | 1999 | /* Make sure the socket is blocking. */ |
ba49e036 | 2000 | PRSocketOptionData socketOption; |
aeb9cc10 DB |
2001 | socketOption.option = PR_SockOpt_Nonblocking; |
2002 | socketOption.value.non_blocking = PR_FALSE; | |
2003 | PR_SetSocketOption (tcpSocket, &socketOption); | |
ba49e036 | 2004 | #endif |
aeb9cc10 DB |
2005 | secStatus = SECFailure; |
2006 | sslSocket = setupSSLSocket (tcpSocket, cert, privKey); | |
2007 | if (sslSocket == NULL) | |
2008 | { | |
2009 | // Message already issued. | |
2010 | goto cleanup; | |
2011 | } | |
2012 | ||
2013 | secStatus = SSL_ResetHandshake(sslSocket, /* asServer */ PR_TRUE); | |
2014 | if (secStatus != SECSuccess) | |
2015 | { | |
16560657 | 2016 | server_error (_("Error resetting SSL handshake")); |
aeb9cc10 DB |
2017 | nssError (); |
2018 | goto cleanup; | |
2019 | } | |
2020 | ||
2021 | #if 0 // The client authenticates the server, so the client initiates the handshake | |
2022 | /* Force the handshake to complete before moving on. */ | |
2023 | secStatus = SSL_ForceHandshake(sslSocket); | |
2024 | if (secStatus != SECSuccess) | |
2025 | { | |
16560657 | 2026 | server_error (_("Error forcing SSL handshake")); |
aeb9cc10 DB |
2027 | nssError (); |
2028 | goto cleanup; | |
2029 | } | |
2030 | #endif | |
2031 | ||
2032 | secStatus = SECFailure; | |
a8e317b6 SC |
2033 | retlen = snprintf(tmpdir, PATH_MAX, "%s/stap-server.XXXXXX", getenv("TMPDIR") ?: "/tmp"); |
2034 | if (retlen < 0 || retlen >= PATH_MAX) | |
2035 | { | |
2036 | server_error (_F("Error creating %s name", "temporary directory")); | |
2037 | tmpdir[0]=0; /* prevent /bin/rm */ | |
2038 | goto cleanup; | |
2039 | } | |
aeb9cc10 DB |
2040 | rc1 = mkdtemp(tmpdir); |
2041 | if (! rc1) | |
2042 | { | |
16560657 | 2043 | server_error (_F("Could not create temporary directory %s: %s", tmpdir, strerror(errno))); |
aeb9cc10 DB |
2044 | tmpdir[0]=0; /* prevent /bin/rm */ |
2045 | goto cleanup; | |
2046 | } | |
2047 | ||
2048 | /* Create a temporary files names and directories. */ | |
a8e317b6 SC |
2049 | retlen = snprintf (requestFileName, PATH_MAX, "%s/request.zip", tmpdir); |
2050 | if (retlen < 0 || retlen >= PATH_MAX) | |
2051 | { | |
2052 | server_error (_F("Error creating %s name", "request.zip path")); | |
2053 | goto cleanup; | |
2054 | } | |
2055 | ||
2056 | retlen = snprintf (requestDirName, PATH_MAX, "%s/request", tmpdir); | |
2057 | if (retlen < 0 || retlen >= PATH_MAX) | |
2058 | { | |
2059 | server_error (_F("Error creating %s name", "request directory path")); | |
2060 | goto cleanup; | |
2061 | } | |
aeb9cc10 | 2062 | |
aeb9cc10 DB |
2063 | rc = mkdir(requestDirName, 0700); |
2064 | if (rc) | |
2065 | { | |
16560657 | 2066 | server_error (_F("Could not create temporary directory %s: %s", requestDirName, strerror (errno))); |
aeb9cc10 DB |
2067 | goto cleanup; |
2068 | } | |
2069 | ||
a8e317b6 SC |
2070 | retlen = snprintf (responseDirName, PATH_MAX, "%s/response", tmpdir); |
2071 | if (retlen < 0 || retlen >= PATH_MAX) | |
2072 | { | |
2073 | server_error (_F("Error creating %s name", "response directory path")); | |
2074 | goto cleanup; | |
2075 | } | |
2076 | ||
aeb9cc10 DB |
2077 | rc = mkdir(responseDirName, 0700); |
2078 | if (rc) | |
2079 | { | |
16560657 | 2080 | server_error (_F("Could not create temporary directory %s: %s", responseDirName, strerror (errno))); |
aeb9cc10 DB |
2081 | goto cleanup; |
2082 | } | |
16560657 DB |
2083 | // Set this early, since it gets used for errors to be returned to the client. |
2084 | stapstderr = string(responseDirName) + "/stderr"; | |
aeb9cc10 | 2085 | |
a8e317b6 SC |
2086 | retlen = snprintf (responseFileName, PATH_MAX, "%s/response.zip", tmpdir); |
2087 | if (retlen < 0 || retlen >= PATH_MAX) | |
2088 | { | |
2089 | server_error (_F("Error creating %s name", "response.zip path")); | |
2090 | goto cleanup; | |
2091 | } | |
aeb9cc10 DB |
2092 | |
2093 | /* Read data from the socket. | |
2094 | * If the user is requesting/requiring authentication, authenticate | |
2095 | * the socket. */ | |
2096 | bytesRead = readDataFromSocket(sslSocket, requestFileName); | |
2097 | if (bytesRead < 0) // Error | |
2098 | goto cleanup; | |
2099 | if (bytesRead == 0) // No request -- not an error | |
2100 | { | |
2101 | secStatus = SECSuccess; | |
2102 | goto cleanup; | |
2103 | } | |
2104 | ||
2105 | #if 0 /* Don't authenticate after each transaction */ | |
2106 | if (REQUEST_CERT_ALL) | |
2107 | { | |
2108 | secStatus = authenticateSocket(sslSocket); | |
2109 | if (secStatus != SECSuccess) | |
2110 | goto cleanup; | |
2111 | } | |
2112 | #endif | |
2113 | ||
7ac8a2e2 AJ |
2114 | /* Just before we do any kind of processing, we want to check that the request there will |
2115 | * be enough memory to unzip the file. */ | |
c4798c0f | 2116 | if (check_uncompressed_request_size(requestFileName)) |
7ac8a2e2 AJ |
2117 | { |
2118 | goto cleanup; | |
2119 | } | |
2120 | ||
aeb9cc10 DB |
2121 | /* Unzip the request. */ |
2122 | secStatus = SECFailure; | |
4c847338 | 2123 | argv = { "unzip", "-q", "-d", requestDirName, requestFileName }; |
aeb9cc10 | 2124 | rc = stap_system (0, argv); |
820f2d22 | 2125 | if (rc != 0) |
aeb9cc10 | 2126 | { |
16560657 | 2127 | server_error (_("Unable to extract client request")); |
aeb9cc10 DB |
2128 | goto cleanup; |
2129 | } | |
2130 | ||
2131 | /* Handle the request zip file. An error therein should still result | |
2132 | in a response zip file (containing stderr etc.) so we don't have to | |
2133 | have a result code here. */ | |
49398925 | 2134 | handleRequest(requestDirName, responseDirName, stapstderr); |
aeb9cc10 DB |
2135 | |
2136 | /* Zip the response. */ | |
e7d52b3b | 2137 | int ziprc; |
4c847338 | 2138 | argv = { "zip", "-q", "-r", responseFileName, "." }; |
e7d52b3b JL |
2139 | rc = spawn_and_wait (argv, &ziprc, NULL, NULL, NULL, responseDirName); |
2140 | if (rc != PR_SUCCESS || ziprc != 0) | |
aeb9cc10 | 2141 | { |
16560657 | 2142 | server_error (_("Unable to compress server response")); |
aeb9cc10 DB |
2143 | goto cleanup; |
2144 | } | |
49398925 | 2145 | |
aeb9cc10 DB |
2146 | secStatus = writeDataToSocket (sslSocket, responseFileName); |
2147 | ||
2148 | cleanup: | |
2149 | if (sslSocket) | |
2150 | if (PR_Close (sslSocket) != PR_SUCCESS) | |
2151 | { | |
16560657 | 2152 | server_error (_("Error closing ssl socket")); |
aeb9cc10 DB |
2153 | nssError (); |
2154 | } | |
71a522b5 | 2155 | |
aeb9cc10 DB |
2156 | if (tmpdir[0]) |
2157 | { | |
71a522b5 DB |
2158 | // Remove the whole tmpdir and all that lies beneath, unless -k was specified. |
2159 | if (keep_temp) | |
2160 | log (_F("Keeping temporary directory %s", tmpdir)); | |
2161 | else | |
2162 | { | |
4c847338 | 2163 | argv = { "rm", "-r", tmpdir }; |
71a522b5 | 2164 | rc = stap_system (0, argv); |
820f2d22 | 2165 | if (rc != 0) |
71a522b5 DB |
2166 | server_error (_("Error in tmpdir cleanup")); |
2167 | } | |
aeb9cc10 DB |
2168 | } |
2169 | ||
0ec2c5bf DB |
2170 | if (secStatus != SECSuccess) |
2171 | server_error (_("Error processing client request")); | |
2172 | ||
2173 | // Log the end of the request. | |
2174 | char buf[1024]; | |
2175 | prStatus = PR_NetAddrToString (& addr, buf, sizeof (buf)); | |
2176 | if (prStatus == PR_SUCCESS) | |
2177 | { | |
2178 | if (addr.raw.family == PR_AF_INET) | |
2179 | log (_F("Request from %s:%d complete", buf, addr.inet.port)); | |
2180 | else if (addr.raw.family == PR_AF_INET6) | |
2181 | log (_F("Request from [%s]:%d complete", buf, addr.ipv6.port)); | |
2182 | } | |
2183 | ||
30321a38 FCE |
2184 | if (privKey) |
2185 | SECKEY_DestroyPrivateKey (privKey); /* Destroy our copy of the private key. */ | |
2186 | ||
0ec2c5bf DB |
2187 | /* Increment semephore to indicate this thread is finished. */ |
2188 | free(t_arg); | |
2189 | if (max_threads > 0) | |
2190 | { | |
2191 | sem_post(&sem_client); | |
2192 | pthread_exit(0); | |
2193 | } | |
2194 | else | |
2195 | return 0; | |
aeb9cc10 DB |
2196 | } |
2197 | ||
2198 | /* Function: int accept_connection() | |
2199 | * | |
2200 | * Purpose: Accept a connection to the socket. | |
2201 | * | |
2202 | */ | |
2203 | static SECStatus | |
2204 | accept_connections (PRFileDesc *listenSocket, CERTCertificate *cert) | |
2205 | { | |
2206 | PRNetAddr addr; | |
2207 | PRFileDesc *tcpSocket; | |
0ec2c5bf | 2208 | PRStatus prStatus; |
aeb9cc10 DB |
2209 | SECStatus secStatus; |
2210 | CERTCertDBHandle *dbHandle; | |
49398925 | 2211 | pthread_t tid; |
26a39006 | 2212 | thread_arg *t_arg; |
49398925 | 2213 | |
aeb9cc10 DB |
2214 | |
2215 | dbHandle = CERT_GetDefaultCertDB (); | |
2216 | ||
2217 | // cert_db_path gets passed to nssPasswordCallback. | |
2218 | SECKEYPrivateKey *privKey = PK11_FindKeyByAnyCert (cert, (void*)cert_db_path.c_str ()); | |
2219 | if (privKey == NULL) | |
2220 | { | |
16560657 | 2221 | server_error (_("Unable to obtain certificate private key")); |
aeb9cc10 DB |
2222 | nssError (); |
2223 | return SECFailure; | |
2224 | } | |
2225 | ||
26a39006 | 2226 | while (pending_interrupts == 0) |
aeb9cc10 DB |
2227 | { |
2228 | /* Accept a connection to the socket. */ | |
26a39006 | 2229 | tcpSocket = PR_Accept (listenSocket, &addr, PR_INTERVAL_MIN); |
aeb9cc10 | 2230 | if (tcpSocket == NULL) |
26a39006 CM |
2231 | { |
2232 | if(PR_GetError() == PR_IO_TIMEOUT_ERROR) | |
2233 | continue; | |
2234 | else | |
2235 | { | |
2236 | server_error (_("Error accepting client connection")); | |
2237 | break; | |
2238 | } | |
2239 | } | |
aeb9cc10 DB |
2240 | |
2241 | /* Log the accepted connection. */ | |
0ec2c5bf DB |
2242 | char buf[1024]; |
2243 | prStatus = PR_NetAddrToString (&addr, buf, sizeof (buf)); | |
2244 | if (prStatus == PR_SUCCESS) | |
2245 | { | |
2246 | if (addr.raw.family == PR_AF_INET) | |
2247 | log (_F("Accepted connection from %s:%d", buf, addr.inet.port)); | |
2248 | else if (addr.raw.family == PR_AF_INET6) | |
2249 | log (_F("Accepted connection from [%s]:%d", buf, addr.ipv6.port)); | |
2250 | } | |
aeb9cc10 DB |
2251 | |
2252 | /* XXX: alarm() or somesuch to set a timeout. */ | |
aeb9cc10 DB |
2253 | |
2254 | /* Accepted the connection, now handle it. */ | |
aeb9cc10 | 2255 | |
49398925 CM |
2256 | /* Wait for a thread to finish if there are none available */ |
2257 | if(max_threads >0) | |
2258 | { | |
26a39006 CM |
2259 | int idle_threads; |
2260 | sem_getvalue(&sem_client, &idle_threads); | |
2261 | if(idle_threads <= 0) | |
49398925 | 2262 | log(_("Server is overloaded. Processing times may be longer than normal.")); |
26a39006 | 2263 | else if (idle_threads == max_threads) |
49398925 CM |
2264 | log(_("Processing 1 request...")); |
2265 | else | |
26a39006 | 2266 | log(_F("Processing %d concurrent requests...", ((int)max_threads - idle_threads) + 1)); |
49398925 CM |
2267 | |
2268 | sem_wait(&sem_client); | |
2269 | } | |
2270 | ||
2271 | /* Create the argument structure to pass to pthread_create | |
2272 | * (or directly to handle_connection if max_threads == 0 */ | |
26a39006 CM |
2273 | t_arg = (thread_arg *)malloc(sizeof(*t_arg)); |
2274 | if (t_arg == 0) | |
2275 | fatal(_("No memory available for new thread arg!")); | |
2276 | t_arg->tcpSocket = tcpSocket; | |
2277 | t_arg->cert = cert; | |
30321a38 | 2278 | t_arg->privKey = SECKEY_CopyPrivateKey(privKey); /* pass by value */ |
26a39006 | 2279 | t_arg->addr = addr; |
49398925 CM |
2280 | |
2281 | /* Handle the conncection */ | |
848f7473 | 2282 | int thread_rc = -1; |
49398925 CM |
2283 | if (max_threads > 0) |
2284 | /* Create the worker thread and handle the connection. */ | |
848f7473 FCE |
2285 | thread_rc = pthread_create(&tid, NULL, handle_connection, t_arg); |
2286 | ||
2287 | if (thread_rc != 0) | |
26a39006 CM |
2288 | /* Since max_threads == 0, don't spawn a new thread, |
2289 | * just handle in the current thread. */ | |
2290 | handle_connection(t_arg); | |
aeb9cc10 DB |
2291 | |
2292 | // If our certificate is no longer valid (e.g. has expired), then exit. | |
2293 | secStatus = CERT_VerifyCertNow (dbHandle, cert, PR_TRUE/*checkSig*/, | |
2294 | certUsageSSLServer, NULL/*wincx*/); | |
2295 | if (secStatus != SECSuccess) | |
2296 | { | |
7e001983 FCE |
2297 | // Not a serious error. Exit the loop so a new cert can be generated. |
2298 | nssError (); | |
aeb9cc10 DB |
2299 | break; |
2300 | } | |
2301 | } | |
2302 | ||
30321a38 | 2303 | SECKEY_DestroyPrivateKey (privKey); /* Safe to destroy, even if thread still running. */ |
aeb9cc10 DB |
2304 | return SECSuccess; |
2305 | } | |
2306 | ||
2307 | /* Function: void server_main() | |
2308 | * | |
2309 | * Purpose: This is the server's main function. It configures a socket | |
2310 | * and listens to it. | |
2311 | * | |
2312 | */ | |
2313 | static SECStatus | |
ba49e036 | 2314 | server_main (PRFileDesc *listenSocket) |
aeb9cc10 | 2315 | { |
26a39006 CM |
2316 | int idle_threads; |
2317 | int timeout = 0; | |
fddf715d SC |
2318 | NSSInitContext *context; |
2319 | SECStatus secStatus; | |
26a39006 | 2320 | |
ba49e036 | 2321 | // Initialize NSS. |
fddf715d SC |
2322 | context = nssInitContext (cert_db_path.c_str ()); |
2323 | if (context == NULL) | |
ba49e036 DB |
2324 | { |
2325 | // Message already issued. | |
fddf715d | 2326 | return SECFailure; |
ba49e036 DB |
2327 | } |
2328 | ||
2329 | // Preinitialized here due to jumps to the label 'done'. | |
2330 | CERTCertificate *cert = NULL; | |
2331 | bool serverCacheConfigured = false; | |
2332 | ||
241e35f0 | 2333 | // Enable all cipher suites. |
ba49e036 DB |
2334 | // NB: The NSS docs say that SSL_ClearSessionCache is required for the new settings to take |
2335 | // effect, however, calling it puts NSS in a state where it will not shut down cleanly. | |
2336 | // We need to be able to shut down NSS cleanly if we are to generate a new certificate when | |
2337 | // ours expires. It should be noted however, thet SSL_ClearSessionCache only clears the | |
2338 | // client cache, and we are a server. | |
f1b05046 FCE |
2339 | /* Some NSS versions don't do this correctly in NSS_SetDomesticPolicy. */ |
2340 | do { | |
2341 | const PRUint16 *cipher; | |
f2ff34ed | 2342 | for (cipher = SSL_GetImplementedCiphers(); *cipher != 0; ++cipher) |
f1b05046 FCE |
2343 | SSL_CipherPolicySet(*cipher, SSL_ALLOWED); |
2344 | } while (0); | |
ba49e036 | 2345 | // SSL_ClearSessionCache (); |
ba49e036 DB |
2346 | |
2347 | // Configure the SSL session cache for a single process server with the default settings. | |
2348 | secStatus = SSL_ConfigServerSessionIDCache (0, 0, 0, NULL); | |
2349 | if (secStatus != SECSuccess) | |
2350 | { | |
16560657 | 2351 | server_error (_("Unable to configure SSL server session ID cache")); |
ba49e036 DB |
2352 | nssError (); |
2353 | goto done; | |
2354 | } | |
2355 | serverCacheConfigured = true; | |
2356 | ||
2357 | /* Get own certificate. */ | |
2358 | cert = PK11_FindCertFromNickname (server_cert_nickname (), NULL); | |
2359 | if (cert == NULL) | |
2360 | { | |
16560657 | 2361 | server_error (_F("Unable to find our certificate in the database at %s", |
ba49e036 DB |
2362 | cert_db_path.c_str ())); |
2363 | nssError (); | |
2364 | goto done; | |
2365 | } | |
2366 | ||
2367 | // Tell the world that we're listening. | |
2368 | advertise_presence (cert); | |
2369 | ||
2370 | /* Handle connections to the socket. */ | |
2371 | secStatus = accept_connections (listenSocket, cert); | |
2372 | ||
2373 | // Tell the world we're no longer listening. | |
2374 | unadvertise_presence (); | |
2375 | ||
26a39006 CM |
2376 | sem_getvalue(&sem_client, &idle_threads); |
2377 | ||
2378 | /* Wait for requests to finish or the timeout to be reached. | |
2379 | * If we got here from an interrupt, exit immediately if | |
2380 | * the timeout is reached. Otherwise, wait indefinitiely | |
2381 | * until the threads exit (or an interrupt is recieved).*/ | |
2382 | if(idle_threads < max_threads) | |
2383 | log(_F("Waiting for %d outstanding requests to complete...", (int)max_threads - idle_threads)); | |
2384 | while(idle_threads < max_threads) | |
2385 | { | |
2386 | if(pending_interrupts && timeout++ > CONCURRENCY_TIMEOUT_S) | |
2387 | { | |
2388 | log(_("Timeout reached, exiting (forced)")); | |
2754e7bb | 2389 | kill_stap_spawn (SIGTERM); |
26a39006 CM |
2390 | cleanup (); |
2391 | _exit(0); | |
2392 | } | |
2393 | sleep(1); | |
2394 | sem_getvalue(&sem_client, &idle_threads); | |
2395 | } | |
2396 | ||
ba49e036 DB |
2397 | done: |
2398 | // Clean up | |
2399 | if (cert) | |
2400 | CERT_DestroyCertificate (cert); | |
2401 | ||
2402 | // Shutdown NSS | |
2403 | if (serverCacheConfigured && SSL_ShutdownServerSessionIDCache () != SECSuccess) | |
2404 | { | |
16560657 | 2405 | server_error (_("Unable to shut down server session ID cache")); |
ba49e036 DB |
2406 | nssError (); |
2407 | } | |
fddf715d | 2408 | nssCleanup (cert_db_path.c_str (), context); |
ba49e036 DB |
2409 | |
2410 | return secStatus; | |
2411 | } | |
2412 | ||
2413 | static void | |
2414 | listen () | |
2415 | { | |
2416 | // Create a new socket. | |
0ec2c5bf | 2417 | PRFileDesc *listenSocket = PR_OpenTCPSocket (PR_AF_INET6); // Accepts IPv4 too |
aeb9cc10 DB |
2418 | if (listenSocket == NULL) |
2419 | { | |
16560657 | 2420 | server_error (_("Error creating socket")); |
aeb9cc10 | 2421 | nssError (); |
ba49e036 | 2422 | return; |
aeb9cc10 DB |
2423 | } |
2424 | ||
ba49e036 DB |
2425 | // Set socket to be blocking - on some platforms the default is nonblocking. |
2426 | PRSocketOptionData socketOption; | |
aeb9cc10 DB |
2427 | socketOption.option = PR_SockOpt_Nonblocking; |
2428 | socketOption.value.non_blocking = PR_FALSE; | |
ba49e036 DB |
2429 | PRStatus prStatus = PR_SetSocketOption (listenSocket, & socketOption); |
2430 | if (prStatus != PR_SUCCESS) | |
2431 | { | |
16560657 | 2432 | server_error (_("Error setting socket properties")); |
ba49e036 DB |
2433 | nssError (); |
2434 | goto done; | |
2435 | } | |
aeb9cc10 | 2436 | |
ba49e036 DB |
2437 | // Allow the socket address to be reused, in case we want the same port across a |
2438 | // 'service stap-server restart' | |
2439 | socketOption.option = PR_SockOpt_Reuseaddr; | |
2440 | socketOption.value.reuse_addr = PR_TRUE; | |
2441 | prStatus = PR_SetSocketOption (listenSocket, & socketOption); | |
aeb9cc10 DB |
2442 | if (prStatus != PR_SUCCESS) |
2443 | { | |
16560657 | 2444 | server_error (_("Error setting socket properties")); |
aeb9cc10 DB |
2445 | nssError (); |
2446 | goto done; | |
2447 | } | |
2448 | ||
ba49e036 DB |
2449 | // Configure the network connection. |
2450 | PRNetAddr addr; | |
0ec2c5bf DB |
2451 | memset (& addr, 0, sizeof(addr)); |
2452 | prStatus = PR_InitializeNetAddr (PR_IpAddrAny, port, & addr); | |
2453 | addr.ipv6.family = PR_AF_INET6; | |
2454 | #if 0 | |
2455 | // addr.inet.ip = PR_htonl(PR_INADDR_ANY); | |
2456 | PR_StringToNetAddr ("::", & addr); | |
2457 | // PR_StringToNetAddr ("fe80::5eff:35ff:fe07:55ca", & addr); | |
2458 | // PR_StringToNetAddr ("::1", & addr); | |
2459 | addr.ipv6.port = PR_htons (port); | |
2460 | #endif | |
aeb9cc10 | 2461 | |
0ec2c5bf DB |
2462 | // Bind the socket to an address. Retry if the selected port is busy, unless the port was |
2463 | // specified directly. | |
aeb9cc10 DB |
2464 | for (;;) |
2465 | { | |
aeb9cc10 DB |
2466 | /* Bind the address to the listener socket. */ |
2467 | prStatus = PR_Bind (listenSocket, & addr); | |
2468 | if (prStatus == PR_SUCCESS) | |
2469 | break; | |
2470 | ||
0ec2c5bf | 2471 | // If the selected port is busy. Try another, but only if a specific port was not specified. |
aeb9cc10 DB |
2472 | PRErrorCode errorNumber = PR_GetError (); |
2473 | switch (errorNumber) | |
2474 | { | |
2475 | case PR_ADDRESS_NOT_AVAILABLE_ERROR: | |
0ec2c5bf DB |
2476 | if (port == 0) |
2477 | { | |
2478 | server_error (_F("Network port %hu is unavailable. Trying another port", port)); | |
2479 | continue; | |
2480 | } | |
2481 | break; | |
aeb9cc10 | 2482 | case PR_ADDRESS_IN_USE_ERROR: |
0ec2c5bf DB |
2483 | if (port == 0) |
2484 | { | |
2485 | server_error (_F("Network port %hu is busy. Trying another port", port)); | |
2486 | continue; | |
2487 | } | |
2488 | break; | |
aeb9cc10 | 2489 | default: |
0ec2c5bf | 2490 | break; |
aeb9cc10 | 2491 | } |
0ec2c5bf DB |
2492 | server_error (_("Error setting socket address")); |
2493 | nssError (); | |
2494 | goto done; | |
aeb9cc10 DB |
2495 | } |
2496 | ||
2497 | // Query the socket for the port that was assigned. | |
2498 | prStatus = PR_GetSockName (listenSocket, &addr); | |
2499 | if (prStatus != PR_SUCCESS) | |
2500 | { | |
16560657 | 2501 | server_error (_("Unable to obtain socket address")); |
aeb9cc10 DB |
2502 | nssError (); |
2503 | goto done; | |
2504 | } | |
0ec2c5bf DB |
2505 | char buf[1024]; |
2506 | prStatus = PR_NetAddrToString (&addr, buf, sizeof (buf)); | |
2507 | port = PR_ntohs (addr.ipv6.port); | |
2508 | log (_F("Using network address [%s]:%hu", buf, port)); | |
49398925 CM |
2509 | |
2510 | if (max_threads > 0) | |
2511 | log (_F("Using a maximum of %ld threads", max_threads)); | |
2512 | else | |
2513 | log (_("Concurrency disabled")); | |
aeb9cc10 | 2514 | |
ba49e036 DB |
2515 | // Listen for connection on the socket. The second argument is the maximum size of the queue |
2516 | // for pending connections. | |
aeb9cc10 DB |
2517 | prStatus = PR_Listen (listenSocket, 5); |
2518 | if (prStatus != PR_SUCCESS) | |
2519 | { | |
16560657 | 2520 | server_error (_("Error listening on socket")); |
aeb9cc10 DB |
2521 | nssError (); |
2522 | goto done; | |
2523 | } | |
2524 | ||
26a39006 CM |
2525 | /* Initialize semephore with the maximum number of threads |
2526 | * defined by --max-threads. If it is not defined, the | |
2527 | * default is the number of processors */ | |
2528 | sem_init(&sem_client, 0, max_threads); | |
2529 | ||
ba49e036 DB |
2530 | // Loop forever. We check our certificate (and regenerate, if necessary) and then start the |
2531 | // server. The server will go down when our certificate is no longer valid (e.g. expired). We | |
2532 | // then generate a new one and start the server again. | |
26a39006 | 2533 | while(!pending_interrupts) |
aeb9cc10 DB |
2534 | { |
2535 | // Ensure that our certificate is valid. Generate a new one if not. | |
2536 | if (check_cert (cert_db_path, server_cert_nickname (), use_db_password) != 0) | |
2537 | { | |
2538 | // Message already issued | |
30321a38 FCE |
2539 | server_error (_("Cannot check/create certificate, giving up.")); |
2540 | goto done; | |
aeb9cc10 DB |
2541 | } |
2542 | ||
2543 | // Ensure that our certificate is trusted by our local client. | |
2544 | // Construct the client database path relative to the server database path. | |
2545 | SECStatus secStatus = add_client_cert (server_cert_file (), | |
7209427d | 2546 | local_client_cert_db_path (), db_nssinit); |
aeb9cc10 DB |
2547 | if (secStatus != SECSuccess) |
2548 | { | |
c981acd9 DB |
2549 | // Not fatal. Other clients may trust the server and trust can be added |
2550 | // for the local client in other ways. | |
16560657 | 2551 | server_error (_("Unable to authorize certificate for the local client")); |
aeb9cc10 DB |
2552 | } |
2553 | ||
ba49e036 DB |
2554 | // Launch the server. |
2555 | secStatus = server_main (listenSocket); | |
aeb9cc10 | 2556 | } // loop forever |
ba49e036 DB |
2557 | |
2558 | done: | |
26a39006 | 2559 | sem_destroy(&sem_client); /*Not really necessary, as we are shutting down...but for correctness */ |
ba49e036 DB |
2560 | if (PR_Close (listenSocket) != PR_SUCCESS) |
2561 | { | |
16560657 | 2562 | server_error (_("Error closing listen socket")); |
ba49e036 DB |
2563 | nssError (); |
2564 | } | |
aeb9cc10 DB |
2565 | } |
2566 | ||
2567 | int | |
2568 | main (int argc, char **argv) { | |
2569 | initialize (argc, argv); | |
2570 | listen (); | |
2571 | cleanup (); | |
2572 | return 0; | |
2573 | } | |
7d26ee02 JS |
2574 | |
2575 | /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */ |