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