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