1 /*****************************************************************************
2 * Written by Chris Dunlap <cdunlap@llnl.gov>.
3 * Copyright (C) 2007-2020 Lawrence Livermore National Security, LLC.
4 * Copyright (C) 2002-2007 The Regents of the University of California.
5 * UCRL-CODE-155910.
6 *
7 * This file is part of the MUNGE Uid 'N' Gid Emporium (MUNGE).
8 * For details, see <https://dun.github.io/munge/>.
9 *
10 * MUNGE is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free
12 * Software Foundation, either version 3 of the License, or (at your option)
13 * any later version. Additionally for the MUNGE library (libmunge), you
14 * can redistribute it and/or modify it under the terms of the GNU Lesser
15 * General Public License as published by the Free Software Foundation,
16 * either version 3 of the License, or (at your option) any later version.
17 *
18 * MUNGE is distributed in the hope that it will be useful, but WITHOUT
19 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 * and GNU Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * and GNU Lesser General Public License along with MUNGE. If not, see
25 * <http://www.gnu.org/licenses/>.
26 *****************************************************************************/
27
28
29 #if HAVE_CONFIG_H
30 # include "config.h"
31 #endif /* HAVE_CONFIG_H */
32
33 #include <assert.h>
34 #include <errno.h>
35 #include <limits.h>
36 #include <pthread.h>
37 #include <signal.h>
38 #include <stdarg.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/time.h>
43 #include <sys/types.h>
44 #include <unistd.h>
45 #include <munge.h>
46 #include "common.h"
47 #include "license.h"
48 #include "log.h"
49 #include "query.h"
50 #include "version.h"
51 #include "xsignal.h"
52
53
54 /*****************************************************************************
55 * Constants
56 *****************************************************************************/
57
58 #define DEF_DO_DECODE 0
59 #define DEF_NUM_THREADS 1
60 #define DEF_PAYLOAD_LENGTH 0
61 #define DEF_WARNING_TIME 5
62 #define MIN_DURATION 0.5
63
64
65 /*****************************************************************************
66 * Command-Line Options
67 *****************************************************************************/
68
69 const char * const short_opts = ":hLVqc:Cm:Mz:Zedl:u:g:t:S:D:N:T:W:";
70
71 #include <getopt.h>
72 struct option long_opts[] = {
73 { "help", no_argument, NULL, 'h' },
74 { "license", no_argument, NULL, 'L' },
75 { "version", no_argument, NULL, 'V' },
76 { "quiet", no_argument, NULL, 'q' },
77 { "cipher", required_argument, NULL, 'c' },
78 { "list-ciphers", no_argument, NULL, 'C' },
79 { "mac", required_argument, NULL, 'm' },
80 { "list-macs", no_argument, NULL, 'M' },
81 { "zip", required_argument, NULL, 'z' },
82 { "list-zips", no_argument, NULL, 'Z' },
83 { "encode", no_argument, NULL, 'e' },
84 { "decode", no_argument, NULL, 'd' },
85 { "length", required_argument, NULL, 'l' },
86 { "restrict-uid", required_argument, NULL, 'u' },
87 { "restrict-gid", required_argument, NULL, 'g' },
88 { "ttl", required_argument, NULL, 't' },
89 { "socket", required_argument, NULL, 'S' },
90 { "duration", required_argument, NULL, 'D' },
91 { "num-creds", required_argument, NULL, 'N' },
92 { "num-threads", required_argument, NULL, 'T' },
93 { "warn-time", required_argument, NULL, 'W' },
94 { NULL, 0, NULL, 0 }
95 };
96
97
98 /*****************************************************************************
99 * Data Types
100 *****************************************************************************/
101
102 /* LOCKING PROTOCOL:
103 * The mutex must be locked when accessing the following fields:
104 * num_creds_done, num_encode_errs, num_decode_errs.
105 * The remaining fields are either not shared between threads or
106 * are constant while processing credentials.
107 */
108 struct conf {
109 munge_ctx_t ctx; /* munge context */
110 int do_decode; /* true to decode/validate all creds */
111 char *payload; /* payload to be encoded into cred */
112 int num_payload; /* number of bytes for cred payload */
113 int max_threads; /* max number of threads available */
114 int num_threads; /* number of threads to spawn */
115 int num_running; /* number of threads now running */
116 int num_seconds; /* number of seconds to run */
117 unsigned long num_creds; /* number of credentials to process */
118 int warn_time; /* number of seconds to allow for op */
119 struct timeval t_main_start; /* time when cred processing started */
120 struct timeval t_main_stop; /* time when cred processing stopped */
121 pthread_t *tids; /* ptr to array of thread IDs */
122 pthread_mutex_t mutex; /* mutex for accessing shared data */
123 pthread_cond_t cond_done; /* cond for when last thread is done */
124
125 struct { /* thread-modified data; mutex req'd */
126 unsigned long num_creds_done; /* number of credentials processed */
127 unsigned long num_encode_errs; /* number of errors encoding creds */
128 unsigned long num_decode_errs; /* number of errors decoding creds */
129 } shared;
130 };
131 typedef struct conf * conf_t;
132
133 struct thread_data {
134 conf_t conf; /* reference to global configuration */
135 munge_ctx_t ectx; /* local munge context for encodes */
136 munge_ctx_t dctx; /* local munge context for decodes */
137 };
138 typedef struct thread_data * tdata_t;
139
140 typedef void * (*thread_f) (void *);
141 typedef void (*thread_cleanup_f) (void *);
142
143
144 /*****************************************************************************
145 * Global Variables
146 *****************************************************************************/
147
148 int g_got_quiet = 0;
149
150
151 /*****************************************************************************
152 * Prototypes
153 *****************************************************************************/
154
155 conf_t create_conf (void);
156 void destroy_conf (conf_t conf);
157 tdata_t create_tdata (conf_t conf);
158 void destroy_tdata (tdata_t tdata);
159 void parse_cmdline (conf_t conf, int argc, char **argv);
160 void display_help (char *prog);
161 void display_strings (const char *header, munge_enum_t type);
162 int get_si_multiple (char c);
163 int get_time_multiple (char c);
164 void start_threads (conf_t conf);
165 void process_creds (conf_t conf);
166 void stop_threads (conf_t conf);
167 void * remunge (conf_t conf);
168 void remunge_cleanup (tdata_t tdata);
169 void output_msg (const char *format, ...);
170
171
172 /*****************************************************************************
173 * Macros
174 *****************************************************************************/
175
176 #define GET_TIMEVAL(TV) \
177 do { \
178 if (gettimeofday ((&TV), NULL) == -1) { \
179 log_errno (EMUNGE_SNAFU, LOG_ERR, \
180 "Failed to query current time"); \
181 } \
182 } while (0)
183
184 #define DIFF_TIMEVAL(TV1, TV0) \
185 ( ((TV1).tv_sec - (TV0).tv_sec ) + \
186 (((TV1).tv_usec - (TV0).tv_usec) / 1e6) )
187
188
189 /*****************************************************************************
190 * Functions
191 *****************************************************************************/
192
193 int
main(int argc,char * argv[])194 main (int argc, char *argv[])
195 {
196 conf_t conf;
197
198 xsignal_ignore (SIGHUP);
199 xsignal_ignore (SIGPIPE);
200
201 /* Close stdin since it is not used.
202 */
203 if (close (STDIN_FILENO) < 0) {
204 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to close standard input");
205 }
206 /* Set stdout to be line buffered.
207 */
208 if (setvbuf (stdout, NULL, _IOLBF, 0) < 0) {
209 log_err (EMUNGE_SNAFU, LOG_ERR,
210 "Failed to line-buffer standard output");
211 }
212 log_open_file (stderr, argv[0], LOG_INFO, LOG_OPT_PRIORITY);
213 conf = create_conf ();
214 parse_cmdline (conf, argc, argv);
215
216 start_threads (conf);
217 process_creds (conf);
218 stop_threads (conf);
219
220 destroy_conf (conf);
221 log_close_file ();
222 exit (EMUNGE_SUCCESS);
223 }
224
225
226 conf_t
create_conf(void)227 create_conf (void)
228 {
229 /* Creates and returns the default configuration.
230 * Returns a valid ptr or dies trying.
231 */
232 conf_t conf;
233 int n;
234
235 if (!(conf = malloc (sizeof (*conf)))) {
236 log_errno (EMUNGE_NO_MEMORY, LOG_ERR, "Failed to allocate conf");
237 }
238 if (!(conf->ctx = munge_ctx_create ())) {
239 log_errno (EMUNGE_NO_MEMORY, LOG_ERR, "Failed to create conf ctx");
240 }
241 if ((errno = pthread_mutex_init (&conf->mutex, NULL)) != 0) {
242 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to init mutex");
243 }
244 if ((errno = pthread_cond_init (&conf->cond_done, NULL)) != 0) {
245 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to init condition");
246 }
247 conf->do_decode = DEF_DO_DECODE;
248 conf->payload = NULL;
249 conf->num_payload = DEF_PAYLOAD_LENGTH;;
250 conf->num_threads = DEF_NUM_THREADS;
251 conf->num_running = 0;
252 conf->num_seconds = 0;
253 conf->num_creds = 0;
254 conf->shared.num_creds_done = 0;
255 conf->shared.num_encode_errs = 0;
256 conf->shared.num_decode_errs = 0;
257 conf->warn_time = DEF_WARNING_TIME;
258 conf->tids = NULL;
259 /*
260 * Compute the maximum number of threads available for the process.
261 * Each thread requires an open file descriptor to communicate with
262 * the local munge daemon. Reserve 2 fds for stdout and stderr.
263 * And reserve 2 fds in case LinuxThreads is being used.
264 */
265 errno = 0;
266 if (((n = sysconf (_SC_OPEN_MAX)) == -1) && (errno != 0)) {
267 log_errno (EMUNGE_SNAFU, LOG_ERR,
268 "Failed to determine maximum number of open files");
269 }
270 if ((conf->max_threads = n - 2 - 2) < 1) {
271 log_err (EMUNGE_SNAFU, LOG_ERR,
272 "Failed to compute maximum number of threads");
273 }
274 return (conf);
275 }
276
277
278 void
destroy_conf(conf_t conf)279 destroy_conf (conf_t conf)
280 {
281 /* Destroys the configuration [conf].
282 */
283 assert (conf != NULL);
284
285 if (conf->payload) {
286 assert (conf->num_payload > 0);
287 free (conf->payload);
288 }
289 if ((errno = pthread_cond_destroy (&conf->cond_done)) != 0) {
290 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to destroy condition");
291 }
292 if ((errno = pthread_mutex_destroy (&conf->mutex)) != 0) {
293 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to destroy mutex");
294 }
295 munge_ctx_destroy (conf->ctx);
296 free (conf->tids);
297 free (conf);
298 return;
299 }
300
301
302 tdata_t
create_tdata(conf_t conf)303 create_tdata (conf_t conf)
304 {
305 /* Create thread-specific data referencing back to the global config [conf].
306 * This struct is required since remunge_cleanup() needs access to both
307 * the global conf mutex and the local munge context.
308 * Returns a valid ptr or dies trying.
309 */
310 tdata_t tdata;
311
312 assert (conf != NULL);
313
314 if (!(tdata = malloc (sizeof (*tdata)))) {
315 log_errno (EMUNGE_NO_MEMORY, LOG_ERR,
316 "Failed to allocate thread data");
317 }
318 tdata->conf = conf;
319 /*
320 * The munge ctx in the global conf is copied since each thread needs
321 * access to its own local ctx for thread-safety.
322 * A separate ctx is used for both encoding and decoding since a
323 * decode error could place the ctx in an invalid state for encoding.
324 * The decode ctx is copied from the global conf instead of creating
325 * a new one from scratch in order to preserve the location of the
326 * munge socket (which may have been set in the conf).
327 */
328 if (!(tdata->ectx = munge_ctx_copy (conf->ctx))) {
329 log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to copy munge encode context");
330 }
331 if ((conf->do_decode) && !(tdata->dctx = munge_ctx_copy (conf->ctx))) {
332 log_err (EMUNGE_SNAFU, LOG_ERR, "Failed to copy munge decode context");
333 }
334 return (tdata);
335 }
336
337
338 void
destroy_tdata(tdata_t tdata)339 destroy_tdata (tdata_t tdata)
340 {
341 /* Destroy the thread-specific data [tdata].
342 */
343 assert (tdata != NULL);
344
345 if (tdata->conf->do_decode) {
346 munge_ctx_destroy (tdata->dctx);
347 }
348 munge_ctx_destroy (tdata->ectx);
349 free (tdata);
350 return;
351 }
352
353
354 void
parse_cmdline(conf_t conf,int argc,char ** argv)355 parse_cmdline (conf_t conf, int argc, char **argv)
356 {
357 /* Parses the command-line, altering the configuration [conf] as specified.
358 */
359 char *prog;
360 int c;
361 char *p;
362 int i;
363 long int l;
364 unsigned long u;
365 int multiplier;
366 munge_err_t e;
367
368 opterr = 0; /* suppress default getopt err msgs */
369
370 prog = (prog = strrchr (argv[0], '/')) ? prog + 1 : argv[0];
371
372 for (;;) {
373
374 c = getopt_long (argc, argv, short_opts, long_opts, NULL);
375
376 if (c == -1) { /* reached end of option list */
377 break;
378 }
379 switch (c) {
380 case 'h':
381 display_help (prog);
382 exit (EMUNGE_SUCCESS);
383 break;
384 case 'L':
385 display_license ();
386 exit (EMUNGE_SUCCESS);
387 break;
388 case 'V':
389 display_version ();
390 exit (EMUNGE_SUCCESS);
391 break;
392 case 'q':
393 g_got_quiet = 1;
394 break;
395 case 'c':
396 i = munge_enum_str_to_int (MUNGE_ENUM_CIPHER, optarg);
397 if ((i < 0) || !munge_enum_is_valid (MUNGE_ENUM_CIPHER, i)) {
398 log_err (EMUNGE_SNAFU, LOG_ERR,
399 "Invalid cipher type \"%s\"", optarg);
400 }
401 e = munge_ctx_set (conf->ctx, MUNGE_OPT_CIPHER_TYPE, i);
402 if (e != EMUNGE_SUCCESS) {
403 log_err (EMUNGE_SNAFU, LOG_ERR,
404 "Failed to set cipher type: %s",
405 munge_ctx_strerror (conf->ctx));
406 }
407 break;
408 case 'C':
409 display_strings ("Cipher types", MUNGE_ENUM_CIPHER);
410 exit (EMUNGE_SUCCESS);
411 break;
412 case 'm':
413 i = munge_enum_str_to_int (MUNGE_ENUM_MAC, optarg);
414 if ((i < 0) || !munge_enum_is_valid (MUNGE_ENUM_MAC, i)) {
415 log_err (EMUNGE_SNAFU, LOG_ERR,
416 "Invalid MAC type \"%s\"", optarg);
417 }
418 e = munge_ctx_set (conf->ctx, MUNGE_OPT_MAC_TYPE, i);
419 if (e != EMUNGE_SUCCESS) {
420 log_err (EMUNGE_SNAFU, LOG_ERR,
421 "Failed to set MAC type: %s",
422 munge_ctx_strerror (conf->ctx));
423 }
424 break;
425 case 'M':
426 display_strings ("MAC types", MUNGE_ENUM_MAC);
427 exit (EMUNGE_SUCCESS);
428 break;
429 case 'z':
430 i = munge_enum_str_to_int (MUNGE_ENUM_ZIP, optarg);
431 if ((i < 0) || !munge_enum_is_valid (MUNGE_ENUM_ZIP, i)) {
432 log_err (EMUNGE_SNAFU, LOG_ERR,
433 "Invalid compression type \"%s\"", optarg);
434 }
435 e = munge_ctx_set (conf->ctx, MUNGE_OPT_ZIP_TYPE, i);
436 if (e != EMUNGE_SUCCESS) {
437 log_err (EMUNGE_SNAFU, LOG_ERR,
438 "Failed to set compression type: %s",
439 munge_ctx_strerror (conf->ctx));
440 }
441 break;
442 case 'Z':
443 display_strings ("Compression types", MUNGE_ENUM_ZIP);
444 exit (EMUNGE_SUCCESS);
445 break;
446 case 'e':
447 conf->do_decode = 0;
448 break;
449 case 'd':
450 conf->do_decode = 1;
451 break;
452 case 'l':
453 errno = 0;
454 l = strtol (optarg, &p, 10);
455 if ((optarg == p) || ((*p != '\0') && (*(p+1) != '\0'))
456 || (l < 0)) {
457 log_err (EMUNGE_SNAFU, LOG_ERR,
458 "Invalid number of bytes '%s'", optarg);
459 }
460 if (((errno == ERANGE) && (l == LONG_MAX)) || (l > INT_MAX)) {
461 log_err (EMUNGE_SNAFU, LOG_ERR,
462 "Exceeded maximum number of %d bytes", INT_MAX);
463 }
464 if (!(multiplier = get_si_multiple (*p))) {
465 log_err (EMUNGE_SNAFU, LOG_ERR,
466 "Invalid number specifier '%c'", *p);
467 }
468 if (l > (INT_MAX / multiplier)) {
469 log_err (EMUNGE_SNAFU, LOG_ERR,
470 "Exceeded maximum number of %d bytes", INT_MAX);
471 }
472 conf->num_payload = (int) (l * multiplier);
473 break;
474 case 'u':
475 if (query_uid (optarg, (uid_t *) &i) < 0) {
476 log_err (EMUNGE_SNAFU, LOG_ERR,
477 "Unrecognized user \"%s\"", optarg);
478 }
479 e = munge_ctx_set (conf->ctx, MUNGE_OPT_UID_RESTRICTION, i);
480 if (e != EMUNGE_SUCCESS) {
481 log_err (EMUNGE_SNAFU, LOG_ERR,
482 "Failed to set UID restriction: %s",
483 munge_ctx_strerror (conf->ctx));
484 }
485 break;
486 case 'g':
487 if (query_gid (optarg, (gid_t *) &i) < 0) {
488 log_err (EMUNGE_SNAFU, LOG_ERR,
489 "Unrecognized group \"%s\"", optarg);
490 }
491 e = munge_ctx_set (conf->ctx, MUNGE_OPT_GID_RESTRICTION, i);
492 if (e != EMUNGE_SUCCESS) {
493 log_err (EMUNGE_SNAFU, LOG_ERR,
494 "Failed to set GID restriction: %s",
495 munge_ctx_strerror (conf->ctx));
496 }
497 break;
498 case 't':
499 errno = 0;
500 l = strtol (optarg, &p, 10);
501 if ((optarg == p) || (*p != '\0') || (l < -1)) {
502 log_err (EMUNGE_SNAFU, LOG_ERR,
503 "Invalid time-to-live '%s'", optarg);
504 }
505 if ((errno == ERANGE) && (l == LONG_MAX)) {
506 log_err (EMUNGE_SNAFU, LOG_ERR,
507 "Overflowed maximum time-to-live of %ld seconds",
508 LONG_MAX);
509 }
510 if (l > UINT_MAX) {
511 log_err (EMUNGE_SNAFU, LOG_ERR,
512 "Exceeded maximum time-to-live of %u seconds",
513 UINT_MAX);
514 }
515 if (l == -1) {
516 l = MUNGE_TTL_MAXIMUM;
517 }
518 e = munge_ctx_set (conf->ctx, MUNGE_OPT_TTL, (int) l);
519 if (e != EMUNGE_SUCCESS) {
520 log_err (EMUNGE_SNAFU, LOG_ERR,
521 "Failed to set time-to-live: %s",
522 munge_ctx_strerror (conf->ctx));
523 }
524 break;
525 case 'S':
526 e = munge_ctx_set (conf->ctx, MUNGE_OPT_SOCKET, optarg);
527 if (e != EMUNGE_SUCCESS) {
528 log_err (EMUNGE_SNAFU, LOG_ERR,
529 "Failed to set munge socket name: %s",
530 munge_ctx_strerror (conf->ctx));
531 }
532 break;
533 case 'D':
534 errno = 0;
535 l = strtol (optarg, &p, 10);
536 if ((optarg == p) || ((*p != '\0') && (*(p+1) != '\0'))
537 || (l <= 0)) {
538 log_err (EMUNGE_SNAFU, LOG_ERR,
539 "Invalid duration '%s'", optarg);
540 }
541 if (((errno == ERANGE) && (l == LONG_MAX)) || (l > INT_MAX)) {
542 log_err (EMUNGE_SNAFU, LOG_ERR,
543 "Exceeded maximum duration of %d seconds", INT_MAX);
544 }
545 if (!(multiplier = get_time_multiple (*p))) {
546 log_err (EMUNGE_SNAFU, LOG_ERR,
547 "Invalid duration specifier '%c'", *p);
548 }
549 if (l > (INT_MAX / multiplier)) {
550 log_err (EMUNGE_SNAFU, LOG_ERR,
551 "Exceeded maximum duration of %d seconds", INT_MAX);
552 }
553 conf->num_seconds = (int) (l * multiplier);
554 break;
555 case 'N':
556 errno = 0;
557 u = strtoul (optarg, &p, 10);
558 if ((optarg == p) || ((*p != '\0') && (*(p+1) != '\0'))
559 || (u == 0)) {
560 log_err (EMUNGE_SNAFU, LOG_ERR,
561 "Invalid number of credentials '%s'", optarg);
562 }
563 if ((errno == ERANGE) && (u == ULONG_MAX)) {
564 log_err (EMUNGE_SNAFU, LOG_ERR,
565 "Exceeded maximum number of %lu credentials",
566 ULONG_MAX);
567 }
568 if (!(multiplier = get_si_multiple (*p))) {
569 log_err (EMUNGE_SNAFU, LOG_ERR,
570 "Invalid number specifier '%c'", *p);
571 }
572 if (u > (ULONG_MAX / multiplier)) {
573 log_err (EMUNGE_SNAFU, LOG_ERR,
574 "Exceeded maximum number of %lu credentials",
575 ULONG_MAX);
576 }
577 conf->num_creds = u * multiplier;
578 break;
579 case 'T':
580 errno = 0;
581 l = strtol (optarg, &p, 10);
582 if ((optarg == p) || (*p != '\0') || (l <= 0)) {
583 log_err (EMUNGE_SNAFU, LOG_ERR,
584 "Invalid number of threads '%s'", optarg);
585 }
586 if (((errno == ERANGE) && (l == LONG_MAX)) || (l > INT_MAX)
587 || (l > conf->max_threads)) {
588 log_err (EMUNGE_SNAFU, LOG_ERR,
589 "Exceeded maximum number of %d thread%s",
590 conf->max_threads,
591 (conf->max_threads == 1) ? "" : "s");
592 }
593 conf->num_threads = (int) l;
594 break;
595 case 'W':
596 errno = 0;
597 l = strtol (optarg, &p, 10);
598 if ((optarg == p) || (*p != '\0') || (l <= 0)) {
599 log_err (EMUNGE_SNAFU, LOG_ERR,
600 "Invalid number of seconds '%s'", optarg);
601 }
602 if (((errno == ERANGE) && (l == LONG_MAX)) || (l > INT_MAX)) {
603 log_err (EMUNGE_SNAFU, LOG_ERR,
604 "Exceeded maximum number of %d seconds", INT_MAX);
605 }
606 conf->warn_time = (int) l;
607 break;
608 case '?':
609 if (optopt > 0) {
610 log_err (EMUNGE_SNAFU, LOG_ERR,
611 "Invalid option \"-%c\"", optopt);
612 }
613 else if (optind > 1) {
614 log_err (EMUNGE_SNAFU, LOG_ERR,
615 "Invalid option \"%s\"", argv[optind - 1]);
616 }
617 else {
618 log_err (EMUNGE_SNAFU, LOG_ERR,
619 "Failed to process command-line");
620 }
621 break;
622 case ':':
623 if ((optind > 1)
624 && (strncmp (argv[optind - 1], "--", 2) == 0)) {
625 log_err (EMUNGE_SNAFU, LOG_ERR,
626 "Missing argument for option \"%s\"",
627 argv[optind - 1]);
628 }
629 else if (optopt > 0) {
630 log_err (EMUNGE_SNAFU, LOG_ERR,
631 "Missing argument for option \"-%c\"", optopt);
632 }
633 else {
634 log_err (EMUNGE_SNAFU, LOG_ERR,
635 "Failed to process command-line");
636 }
637 break;
638 default:
639 if ((optind > 1)
640 && (strncmp (argv[optind - 1], "--", 2) == 0)) {
641 log_err (EMUNGE_SNAFU, LOG_ERR,
642 "Unimplemented option \"%s\"", argv[optind - 1]);
643 }
644 else {
645 log_err (EMUNGE_SNAFU, LOG_ERR,
646 "Unimplemented option \"-%c\"", c);
647 }
648 break;
649 }
650 }
651 if (argv[optind]) {
652 log_err (EMUNGE_SNAFU, LOG_ERR,
653 "Unrecognized parameter \"%s\"", argv[optind]);
654 }
655 /* Create arbitrary payload of the specified length.
656 */
657 if (conf->num_payload > 0) {
658 if (!(conf->payload = malloc (conf->num_payload + 1))) {
659 log_err (EMUNGE_NO_MEMORY, LOG_ERR,
660 "Failed to allocate credential payload of %d byte%s",
661 conf->num_payload, (conf->num_payload == 1 ? "" : "s"));
662 }
663 for (i = 0, c = 'A'; i < conf->num_payload; i++) {
664 if ((conf->payload[i] = c++) == 'Z') {
665 c = 'A';
666 }
667 }
668 conf->payload[conf->num_payload] = '\0';
669 }
670 return;
671 }
672
673
674 void
display_help(char * prog)675 display_help (char *prog)
676 {
677 /* Displays a help message describing the command-line options.
678 */
679 const int w = -25; /* pad for width of option string */
680
681 assert (prog != NULL);
682
683 printf ("Usage: %s [OPTIONS]\n", prog);
684 printf ("\n");
685
686 printf (" %*s %s\n", w, "-h, --help",
687 "Display this help");
688
689 printf (" %*s %s\n", w, "-L, --license",
690 "Display license information");
691
692 printf (" %*s %s\n", w, "-V, --version",
693 "Display version information");
694
695 printf (" %*s %s\n", w, "-q, --quiet",
696 "Display only the creds/sec numeric result");
697
698 printf ("\n");
699
700 printf (" %*s %s\n", w, "-c, --cipher=STRING",
701 "Specify cipher type");
702
703 printf (" %*s %s\n", w, "-C, --list-ciphers",
704 "Display a list of supported ciphers");
705
706 printf (" %*s %s\n", w, "-m, --mac=STRING",
707 "Specify MAC type");
708
709 printf (" %*s %s\n", w, "-M, --list-macs",
710 "Display a list of supported MACs");
711
712 printf (" %*s %s\n", w, "-z, --zip=STRING",
713 "Specify compression type");
714
715 printf (" %*s %s\n", w, "-Z, --list-zips",
716 "Display a list of supported compressions");
717
718 printf ("\n");
719
720 printf (" %*s %s\n", w, "-e, --encode",
721 "Encode (but do not decode) each credential");
722
723 printf (" %*s %s\n", w, "-d, --decode",
724 "Encode and decode each credential");
725
726 printf (" %*s %s\n", w, "-l, --length=INTEGER",
727 "Specify payload length (in bytes)");
728
729 printf (" %*s %s\n", w, "-u, --restrict-uid=UID",
730 "Restrict credential decoding by UID");
731
732 printf (" %*s %s\n", w, "-g, --restrict-gid=GID",
733 "Restrict credential decoding by GID");
734
735 printf (" %*s %s\n", w, "-t, --ttl=INTEGER",
736 "Specify time-to-live (in seconds; 0=dfl -1=max)");
737
738 printf (" %*s %s\n", w, "-S, --socket=STRING",
739 "Specify local domain socket for munged");
740
741 printf ("\n");
742
743 printf (" %*s %s\n", w, "-D, --duration=INTEGER",
744 "Specify test duration (in seconds; -1=max)");
745
746 printf (" %*s %s\n", w, "-N, --num-creds=INTEGER",
747 "Specify number of credentials to generate");
748
749 printf (" %*s %s\n", w, "-T, --num-threads=INTEGER",
750 "Specify number of threads to spawn");
751
752 printf (" %*s %s\n", w, "-W, --warn-time=INTEGER",
753 "Specify max seconds for munge op before warning");
754
755 printf ("\n");
756 return;
757 }
758
759
760 void
display_strings(const char * header,munge_enum_t type)761 display_strings (const char *header, munge_enum_t type)
762 {
763 int i;
764 const char *p;
765
766 if (header) {
767 printf ("%s:\n\n", header);
768 }
769 for (i = 0; (p = munge_enum_int_to_str (type, i)); i++) {
770 if (munge_enum_is_valid (type, i)) {
771 printf (" %s (%d)\n", p, i);
772 }
773 }
774 printf ("\n");
775 return;
776 }
777
778
779 int
get_si_multiple(char c)780 get_si_multiple (char c)
781 {
782 /* Converts the SI-suffix [c] into an equivalent multiplier.
783 * Returns the multiple, or 0 if invalid.
784 */
785 int multiple;
786
787 switch (c) {
788 case '\0': /* bytes */
789 multiple = 1;
790 break;
791 case 'k': /* kilobytes */
792 multiple = 1e3;
793 break;
794 case 'K': /* kibibytes */
795 multiple = 1 << 10;
796 break;
797 case 'm': /* megabytes */
798 multiple = 1e6;
799 break;
800 case 'M': /* mebibytes */
801 multiple = 1 << 20;
802 break;
803 case 'g': /* gigabytes */
804 multiple = 1e9;
805 break;
806 case 'G': /* gibibytes */
807 multiple = 1 << 30;
808 break;
809 default:
810 multiple = 0;
811 break;
812 }
813 return (multiple);
814 }
815
816
817 int
get_time_multiple(char c)818 get_time_multiple (char c)
819 {
820 /* Converts the time suffix [c] into a multiplier for computing
821 * the number of seconds.
822 * Returns the multiple, or 0 if invalid.
823 */
824 int multiple;
825
826 switch (c) {
827 case '\0':
828 case 's':
829 case 'S':
830 multiple = 1;
831 break;
832 case 'm':
833 case 'M':
834 multiple = 60;
835 break;
836 case 'h':
837 case 'H':
838 multiple = 60 * 60;
839 break;
840 case 'd':
841 case 'D':
842 multiple = 60 * 60 * 24;
843 break;
844 default:
845 multiple = 0;
846 break;
847 }
848 return (multiple);
849 }
850
851
852 void
start_threads(conf_t conf)853 start_threads (conf_t conf)
854 {
855 /* Start the number of threads specified by [conf] for processing credentials.
856 */
857 pthread_attr_t tattr;
858 size_t stacksize = 256 * 1024;
859 int i;
860
861 if (!(conf->tids = malloc (sizeof (*conf->tids) * conf->num_threads))) {
862 log_err (EMUNGE_NO_MEMORY, LOG_ERR, "Failed to allocate tid array");
863 }
864 if ((errno = pthread_attr_init (&tattr)) != 0) {
865 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to init thread attribute");
866 }
867 #ifdef _POSIX_THREAD_ATTR_STACKSIZE
868 if ((errno = pthread_attr_setstacksize (&tattr, stacksize)) != 0) {
869 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to set thread stacksize");
870 }
871 #endif /* _POSIX_THREAD_ATTR_STACKSIZE */
872 /*
873 * Lock mutex to prevent threads from starting until all are created.
874 * After the timer has been started, the mutex will be unlocked via
875 * pthread_cond_timedwait().
876 */
877 if ((errno = pthread_mutex_lock (&conf->mutex)) != 0) {
878 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock mutex");
879 }
880 /* The purpose of the num_running count is for signaling the main thread
881 * when the last worker thread has exited in order to interrupt the
882 * pthread_cond_timedwait(). The reason num_running is set to
883 * num_threads here instead of incrementing it at the start of each
884 * thread is to prevent this condition from being signaled prematurely.
885 * This could happen if all credentials are processed by just a few
886 * threads before all threads have been scheduled to run; consequently,
887 * num_running would bounce to 0 before all threads have finished while
888 * the remaining threads would have no credentials left to process.
889 */
890 assert (conf->num_threads > 0);
891 conf->num_running = conf->num_threads;
892
893 output_msg ("Spawning %d thread%s for %s",
894 conf->num_threads, ((conf->num_threads == 1) ? "" : "s"),
895 (conf->do_decode ? "encoding/decoding" : "encoding"));
896
897 for (i = 0; i < conf->num_threads; i++) {
898 if ((errno = pthread_create
899 (&conf->tids[i], &tattr, (thread_f) remunge, conf)) != 0) {
900 log_errno (EMUNGE_SNAFU, LOG_ERR,
901 "Failed to create thread #%d", i+1);
902 }
903 }
904 if ((errno = pthread_attr_destroy (&tattr)) != 0) {
905 log_errno (EMUNGE_SNAFU, LOG_ERR,
906 "Failed to destroy thread attribute");
907 }
908 return;
909 }
910
911
912 void
process_creds(conf_t conf)913 process_creds (conf_t conf)
914 {
915 /* Process credentials according to the configuration [conf].
916 * Processing continues for the specified duration or until the
917 * credential count is reached, whichever comes first.
918 */
919 int n_secs;
920 unsigned long n_creds;
921 struct timespec to;
922
923 /* Start the main timer before the timeout is computed below.
924 */
925 GET_TIMEVAL (conf->t_main_start);
926 /*
927 * The default is to process credentials for 1 second.
928 */
929 if (!conf->num_creds && !conf->num_seconds) {
930 conf->num_seconds = 1;
931 }
932 /* Save configuration values before they are further modified.
933 */
934 n_secs = conf->num_seconds;
935 n_creds = conf->num_creds;
936 /*
937 * If a duration is not specified (either explicitly or implicitly),
938 * set the timeout to the maximum value so pthread_cond_timedwait()
939 * can still be used.
940 */
941 if (conf->num_seconds) {
942 to.tv_sec = conf->t_main_start.tv_sec + conf->num_seconds;
943 if (to.tv_sec < conf->t_main_start.tv_sec) {
944 to.tv_sec = (sizeof (to.tv_sec) == 4) ? INT_MAX : LONG_MAX;
945 }
946 to.tv_nsec = conf->t_main_start.tv_usec * 1e3;
947 }
948 else {
949 to.tv_sec = (sizeof (to.tv_sec) == 4) ? INT_MAX : LONG_MAX;
950 to.tv_nsec = 0;
951 }
952 /* Recompute the number of seconds in case the specified duration
953 * exceeded the maximum timeout.
954 */
955 conf->num_seconds = to.tv_sec - conf->t_main_start.tv_sec;
956 /*
957 * If a credential count was not specified, set the limit at the maximum.
958 */
959 if (!conf->num_creds) {
960 conf->num_creds = ULONG_MAX;
961 }
962 /* Output processing start message.
963 */
964 if (n_creds && !n_secs) {
965 output_msg ("Processing %lu credential%s",
966 conf->num_creds, ((conf->num_creds == 1) ? "" : "s"));
967 }
968 else if (n_secs && !n_creds) {
969 output_msg ("Processing credentials for %d second%s",
970 conf->num_seconds, ((conf->num_seconds == 1) ? "" : "s"));
971 }
972 else {
973 output_msg ("Processing %lu credential%s for up to %d second%s",
974 conf->num_creds, ((conf->num_creds == 1) ? "" : "s"),
975 conf->num_seconds, ((conf->num_seconds == 1) ? "" : "s"));
976 }
977 /* Start processing credentials.
978 */
979 while (conf->num_running > 0) {
980
981 errno = pthread_cond_timedwait (&conf->cond_done, &conf->mutex, &to);
982
983 if (!errno || (errno == ETIMEDOUT)) {
984 break;
985 }
986 else if (errno == EINTR) {
987 continue;
988 }
989 else {
990 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to wait on condition");
991 }
992 }
993 return;
994 }
995
996
997 void
stop_threads(conf_t conf)998 stop_threads (conf_t conf)
999 {
1000 /* Stop the threads from processing further credentials. Output the results.
1001 */
1002 int i;
1003 unsigned long n;
1004 double delta;
1005 double rate;
1006
1007 /* The mutex must be unlocked here in order to let the threads clean up
1008 * (via remunge_cleanup()) once they are canceled/finished.
1009 */
1010 if ((errno = pthread_mutex_unlock (&conf->mutex)) != 0) {
1011 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock mutex");
1012 }
1013 for (i = 0; i < conf->num_threads; i++) {
1014 errno = pthread_cancel (conf->tids[i]);
1015 if ((errno != 0) && (errno != ESRCH)) {
1016 log_errno (EMUNGE_SNAFU, LOG_ERR,
1017 "Failed to cancel thread #%d", i+1);
1018 }
1019 }
1020 for (i = 0; i < conf->num_threads; i++) {
1021 if ((errno = pthread_join (conf->tids[i], NULL)) != 0) {
1022 log_errno (EMUNGE_SNAFU, LOG_ERR,
1023 "Failed to join thread #%d", i+1);
1024 }
1025 conf->tids[i] = 0;
1026 }
1027 /* Stop the main timer now that all credential processing has stopped.
1028 */
1029 GET_TIMEVAL (conf->t_main_stop);
1030 delta = DIFF_TIMEVAL (conf->t_main_stop, conf->t_main_start);
1031 /*
1032 * Output processing stop message and results.
1033 */
1034 if (conf->shared.num_encode_errs && conf->shared.num_decode_errs) {
1035 output_msg ("Generated %lu encoding error%s and %lu decoding error%s",
1036 conf->shared.num_encode_errs,
1037 ((conf->shared.num_encode_errs == 1) ? "" : "s"),
1038 conf->shared.num_decode_errs,
1039 ((conf->shared.num_decode_errs == 1) ? "" : "s"));
1040 }
1041 else if (conf->shared.num_encode_errs) {
1042 output_msg ("Generated %lu encoding error%s",
1043 conf->shared.num_encode_errs,
1044 ((conf->shared.num_encode_errs == 1) ? "" : "s"));
1045 }
1046 else if (conf->shared.num_decode_errs) {
1047 output_msg ("Generated %lu decoding error%s",
1048 conf->shared.num_decode_errs,
1049 ((conf->shared.num_decode_errs == 1) ? "" : "s"));
1050 }
1051 /* Subtract the errors from the number of credentials processed.
1052 */
1053 n = conf->shared.num_creds_done
1054 - conf->shared.num_encode_errs
1055 - conf->shared.num_decode_errs;
1056 rate = n / delta;
1057 output_msg ("Processed %lu credential%s in %0.3fs (%0.0f creds/sec)",
1058 n, ((n == 1) ? "" : "s"), delta, rate);
1059 if (g_got_quiet) {
1060 printf ("%0.0f\n", rate);
1061 }
1062 /* Check for minimum duration time interval.
1063 */
1064 if (delta < MIN_DURATION) {
1065 printf ("\nWARNING: Results based on such a short time interval "
1066 "are of low accuracy\n\n");
1067 }
1068 return;
1069 }
1070
1071
1072 void *
remunge(conf_t conf)1073 remunge (conf_t conf)
1074 {
1075 /* Worker thread responsible for encoding/decoding/validating credentials.
1076 */
1077 tdata_t tdata;
1078 int cancel_state;
1079 unsigned long n;
1080 unsigned long got_encode_err;
1081 unsigned long got_decode_err;
1082 struct timeval t_start;
1083 struct timeval t_stop;
1084 double delta;
1085 munge_err_t e;
1086 char *cred;
1087 void *data;
1088 int dlen;
1089 uid_t uid;
1090 gid_t gid;
1091
1092 tdata = create_tdata (conf);
1093
1094 pthread_cleanup_push ((thread_cleanup_f) remunge_cleanup, tdata);
1095
1096 if ((errno = pthread_mutex_lock (&conf->mutex)) != 0) {
1097 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock mutex");
1098 }
1099 while (conf->num_creds - conf->shared.num_creds_done > 0) {
1100
1101 pthread_testcancel ();
1102
1103 if ((errno = pthread_setcancelstate
1104 (PTHREAD_CANCEL_DISABLE, &cancel_state)) != 0) {
1105 log_errno (EMUNGE_SNAFU, LOG_ERR,
1106 "Failed to disable thread cancellation");
1107 }
1108 n = ++conf->shared.num_creds_done;
1109
1110 if ((errno = pthread_mutex_unlock (&conf->mutex)) != 0) {
1111 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock mutex");
1112 }
1113 got_encode_err = 0;
1114 got_decode_err = 0;
1115 data = NULL;
1116
1117 GET_TIMEVAL (t_start);
1118 e = munge_encode(&cred, tdata->ectx, conf->payload, conf->num_payload);
1119 GET_TIMEVAL (t_stop);
1120
1121 delta = DIFF_TIMEVAL (t_stop, t_start);
1122 if (delta > conf->warn_time) {
1123 output_msg ("Credential #%lu encoding took %0.3f seconds",
1124 n, delta);
1125 }
1126 if (e != EMUNGE_SUCCESS) {
1127 output_msg ("Credential #%lu encoding failed: %s (err=%d)",
1128 n, munge_ctx_strerror (tdata->ectx), e);
1129 ++got_encode_err;
1130 }
1131 else if (conf->do_decode) {
1132
1133 GET_TIMEVAL (t_start);
1134 e = munge_decode (cred, tdata->dctx, &data, &dlen, &uid, &gid);
1135 GET_TIMEVAL (t_stop);
1136
1137 delta = DIFF_TIMEVAL (t_stop, t_start);
1138 if (delta > conf->warn_time) {
1139 output_msg ("Credential #%lu decoding took %0.3f seconds",
1140 n, delta);
1141 }
1142 if (e != EMUNGE_SUCCESS) {
1143 output_msg ("Credential #%lu decoding failed: %s (err=%d)",
1144 n, munge_ctx_strerror (tdata->dctx), e);
1145 ++got_decode_err;
1146 }
1147
1148 /* FIXME:
1149 * The following block does some validating of the decoded credential.
1150 * It should have a cmdline option to enable this validation check.
1151 * The decode ctx should also be checked against the encode ctx.
1152 * This becomes slightly more difficult in that it must also take
1153 * into account the default field settings.
1154 *
1155 * This block should be moved into a separate function (or more).
1156 * The [cred], [data], [dlen], [uid], and [gid] vars could be placed
1157 * into the tdata struct to facilitate parameter passing.
1158 */
1159 #if 0
1160 else if (conf->do_validate) {
1161 if (getuid () != uid) {
1162 output_msg (
1163 "Credential #%lu UID %d does not match process UID %d",
1164 n, uid, getuid ());
1165 }
1166 if (getgid () != gid) {
1167 output_msg (
1168 "Credential #%lu GID %d does not match process GID %d",
1169 n, gid, getgid ());
1170 }
1171 if (conf->num_payload != dlen) {
1172 output_msg (
1173 "Credential #%lu payload length mismatch (%d/%d)",
1174 n, conf->num_payload, dlen);
1175 }
1176 else if (data && memcmp (conf->payload, data, dlen) != 0) {
1177 output_msg ("Credential #%lu payload mismatch", n);
1178 }
1179 }
1180 #endif /* 0 */
1181
1182 /* The 'data' parm can still be set on certain munge errors.
1183 */
1184 if (data != NULL) {
1185 free (data);
1186 }
1187 }
1188 if (cred != NULL) {
1189 free (cred);
1190 }
1191 if ((errno = pthread_setcancelstate
1192 (cancel_state, &cancel_state)) != 0) {
1193 log_errno (EMUNGE_SNAFU, LOG_ERR,
1194 "Failed to enable thread cancellation");
1195 }
1196 if ((errno = pthread_mutex_lock (&conf->mutex)) != 0) {
1197 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock mutex");
1198 }
1199 conf->shared.num_encode_errs += got_encode_err;
1200 conf->shared.num_decode_errs += got_decode_err;
1201 }
1202 pthread_cleanup_pop (1);
1203 return (NULL);
1204 }
1205
1206
1207 void
remunge_cleanup(tdata_t tdata)1208 remunge_cleanup (tdata_t tdata)
1209 {
1210 /* Signal the main thread when the last worker thread is exiting.
1211 * Clean up resources held by the thread.
1212 */
1213 if (--tdata->conf->num_running == 0) {
1214 if ((errno = pthread_cond_signal (&tdata->conf->cond_done)) != 0) {
1215 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to signal condition");
1216 }
1217 }
1218 if ((errno = pthread_mutex_unlock (&tdata->conf->mutex)) != 0) {
1219 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock mutex");
1220 }
1221 destroy_tdata (tdata);
1222 return;
1223 }
1224
1225
1226 void
output_msg(const char * format,...)1227 output_msg (const char *format, ...)
1228 {
1229 /* Outputs the current time followed by the [format] string
1230 * to stdout in a thread-safe manner.
1231 */
1232 time_t t;
1233 struct tm tm;
1234 struct tm *tm_ptr;
1235 char buf[256];
1236 char *p = buf;
1237 int len = sizeof (buf);
1238 int n;
1239 va_list vargs;
1240
1241 if (g_got_quiet) {
1242 return;
1243 }
1244 if (!format) {
1245 return;
1246 }
1247 if (time (&t) == ((time_t) -1)) {
1248 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to query current time");
1249 }
1250 #if HAVE_LOCALTIME_R
1251 tm_ptr = localtime_r (&t, &tm);
1252 #else /* !HAVE_LOCALTIME_R */
1253 tm_ptr = localtime (&t);
1254 #endif /* !HAVE_LOCALTIME_R */
1255
1256 if (tm_ptr != NULL) {
1257 n = strftime (p, len, "%Y-%m-%d %H:%M:%S ", tm_ptr);
1258 if ((n <= 0) || (n >= len)) {
1259 log_err (EMUNGE_SNAFU, LOG_ERR,
1260 "Exceeded buffer while writing timestamp");
1261 }
1262 p += n;
1263 len -= n;
1264 }
1265 va_start (vargs, format);
1266 n = vsnprintf (p, len, format, vargs);
1267 va_end (vargs);
1268
1269 if ((n < 0) || (n >= len)) {
1270 buf[sizeof (buf) - 2] = '+';
1271 buf[sizeof (buf) - 1] = '\0'; /* technically redundant */
1272 }
1273 printf ("%s\n", buf);
1274 return;
1275 }
1276