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