Make safe_strncat() available to modules.
[userinfo.git] / src / ui.c
blob1500bc1beb7570e510cfa9973b0ed6630fa2e7be
1 /*
2 Copyright (C) 2001-2011 Ben Kibbey <bjk@luxsci.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <pwd.h>
30 #include <time.h>
32 #ifdef HAVE_GETOPT_H
33 #include <getopt.h>
34 #endif
36 #ifndef HAVE_ERR_H
37 #include "err.c"
38 #endif
40 #include "safe_strncat.c"
41 #include "ui.h"
43 static void *Realloc(void *p, size_t size)
45 void *p2;
47 if ((p2 = realloc(p, size)) == NULL)
48 err(EXIT_FAILURE, "%s", "realloc()");
50 return p2;
53 /* This may be used in modules to keep a consistant time format with other
54 * modules. */
55 char *stamp(time_t epoch, const char *format)
57 static char buf[TIMEBUFSIZE];
58 struct tm *t;
60 t = localtime(&epoch);
61 strftime(buf, sizeof(buf), format, t);
62 return buf;
65 /*
66 * This may be used in modules to add a string to the buffer (ui_module_exec()).
68 void add_string(char ***buf, const char *str)
70 char **s;
71 int i = 0;
73 if (*buf) {
74 for (s = *buf; *s; s++)
75 i++;
78 s = *buf;
79 s = Realloc(s, (i + 2) * sizeof(char *));
80 s[i++] = strdup(str);
81 s[i] = NULL;
82 *buf = s;
85 /* This is for the field separators (-F and -m). */
86 static int escapes(const char *str)
88 int c = 0;
90 if (str[0] != '\\')
91 return str[0];
93 switch (*++str) {
94 case 't':
95 c = '\t';
96 break;
97 case 'n':
98 c = '\n';
99 break;
100 case '\\':
101 c = '\\';
102 break;
103 case 'v':
104 c = '\v';
105 break;
106 case 'b':
107 c = '\b';
108 break;
109 case 'f':
110 c = '\f';
111 break;
112 case 'r':
113 c = '\r';
114 break;
115 case '\'':
116 c = '\'';
117 break;
118 default:
119 c = 0;
120 break;
123 return c;
126 /* Help text. Module help text is displayed after this. */
127 static void usage_header()
129 printf("Usage: %s [-vhVL] [-c <filename>] [-t fmt] [-m c] [-F c] [-d]\n"
130 "\t[[-xX] -O <module1> [options] [-- [-xX] -O <module2> [...]]]\n"
131 "\t[- | username | -f filename] [...]\n\n", __progname);
134 /* Help text. Module help text is displayed before this. */
135 static void usage()
137 printf(" -d\tLoad the default modules (passwd.so, mail.so, and login.so).\n");
138 printf(" -c\tRead a configuration file. Can be used more than once.\n");
139 printf(" -O\tLoad a module. Can be used more than once.\n");
140 printf(" -x\tChain module1's output to module2's input.\n");
141 printf(" -X\tDon't output module1's info, only chain it.\n");
142 printf(" -F c\tSeparate output with the specified character "
143 "('%c').\n", delimchar);
144 printf(" -m c\tSeparate multi-string values with the specified "
145 "character ('%c').\n", multichar);
146 printf(" -t tf\tstrftime(3) time format ('%s').\n", DEFAULT_TIMEFORMAT);
147 printf(" -f\tUsers are the owners of the specified files.\n");
148 printf(" -L\tFollow symbolic links.\n");
149 printf(" -v\tVerbose output when possible (twice for all modules).\n");
150 printf(" -h\tThis help text.\n");
151 printf(" -V\tVersion information.\n\n");
152 printf("Output key: %s=unknown/error, %s=none, %s=yes/on, "
153 "%s=no/off\n", UNKNOWN, NONE, ON, OFF);
157 * Add a module to the array of loaded modules. The index argument is the
158 * current item number being added stored in an integer. The module is also
159 * initialized here with ui_module_init().
161 static int open_module(char *filename, int *idx)
163 void *m;
164 module_init *init;
165 char *p, s[PATH_MAX];
166 int i;
167 int chainable = 0;
169 strncpy(s, filename, sizeof(s));
171 if ((p = strrchr(s, '/')) != NULL)
172 p++;
173 else {
174 strncpy(s, filename, sizeof(s));
175 p = s;
178 for (i = 0; i < module_index; i++) {
179 if (strcmp(p, modules[i].name) == 0) {
180 if (TEST_FLAG(modules[i].flags, MODULE_DUP))
181 break;
183 SET_FLAG(modules[i].flags, MODULE_DUP);
184 warnx("%s: a module by this name is already loaded", p);
188 if ((m = dlopen(filename, RTLD_NOW)) == NULL) {
189 warnx("%s", dlerror());
190 chaining = 0;
191 chain_output = 1;
192 return 1;
195 modules = Realloc(modules, (module_index + 2) * sizeof(struct module));
196 modules[module_index].m = m;
198 strncpy(modules[module_index].name, p, sizeof(modules[module_index].name));
199 *idx = module_index++;
201 if ((init = dlsym(modules[*idx].m, "ui_module_init")) == NULL)
202 warnx("%s", dlerror());
203 else
204 (*init) (&chainable);
206 if (chainable)
207 SET_FLAG(modules[*idx].flags, MODULE_CHAINABLE);
209 if (*idx - 1 >= 0 && TEST_FLAG(modules[*idx - 1].flags, MODULE_CHAINED) &&
210 !TEST_FLAG(modules[*idx].flags, MODULE_CHAINABLE)) {
211 warnx("%s: this module is not chainable", modules[*idx].name);
212 return 1;
215 /* Module chaining. See junction() for more info. */
216 if (chaining)
217 SET_FLAG(modules[*idx].flags, MODULE_CHAINED);
219 if (chain_output)
220 SET_FLAG(modules[*idx].flags, MODULE_OUTPUT);
222 if (verbose)
223 SET_FLAG(modules[*idx].flags, MODULE_VERBOSE);
225 chaining = 0;
226 chain_output = 1;
227 verbose = (verbose < 2) ? 0 : 2;
228 return 0;
231 /* This just free's up the array of modules. The modules should clean up after
232 * themselves via the ui_module_exit() function. */
233 static void cleanup_modules()
235 int i;
237 for (i = 0; i < module_index; i++) {
238 module_exit *e;
240 if ((e = dlsym(modules[i].m, "ui_module_exit")) == NULL)
241 warnx("%s", dlerror());
242 else
243 (*e)();
245 dlclose(modules[i].m);
248 free(modules);
251 static void output(char **s, const int sep, int which)
253 char **p = s;
255 if (p) {
256 for (; *p; p++) {
257 printf("%s", *p);
259 if (*(p+1))
260 printf("%c", sep);
264 printf("%c", which == OUTPUT_DONE ? '\n' : sep);
267 /* Pass the argument to each loaded module. */
268 static int junction(const char *arg)
270 int i;
271 struct passwd *pw;
272 struct stat st;
273 int ret = EXIT_SUCCESS;
274 char **s = NULL;
276 if (usefile) {
277 if ((STAT(arg, &st)) == -1) {
278 warn("%s", arg);
279 return EXIT_FAILURE;
282 errno = 0;
284 if ((pw = getpwuid(st.st_uid)) == NULL) {
285 #ifdef __NetBSD__
286 warnx("%s: no such user", arg);
287 #else
288 if (errno == 0 || errno == ENOENT || errno == EPERM
289 || errno == EBADF || errno == ESRCH)
290 warnx("%s: no such uid %u", arg, st.st_uid);
291 else
292 warn("%s", "getpwuid()");
293 #endif
295 return EXIT_FAILURE;
298 else {
299 errno = 0;
301 if ((pw = getpwnam(arg)) == NULL) {
302 #ifdef __NetBSD__
303 warnx("%s: no such user", arg);
304 #else
305 if (errno == 0 || errno == ENOENT || errno == EPERM
306 || errno == EBADF || errno == ESRCH)
307 warnx("%s: no such user", arg);
308 else
309 warn("%s", "getpwnam()");
310 #endif
312 return EXIT_FAILURE;
316 for (i = 0; i < module_index; i++) {
317 module_exec *m_exec;
319 if ((m_exec = dlsym(modules[i].m, "ui_module_exec")) == NULL) {
320 warnx("%s", dlerror());
321 continue;
324 ret |= (*m_exec) (&s, pw, multichar,
325 TEST_FLAG(modules[i].flags, MODULE_VERBOSE), tf);
327 if (!TEST_FLAG(modules[i].flags, MODULE_CHAINED) ||
328 (TEST_FLAG(modules[i].flags, MODULE_CHAINED) &&
329 TEST_FLAG(modules[i].flags, MODULE_OUTPUT))) {
330 output(s, delimchar,
331 ((i + 1) < module_index) ? OUTPUT_APPEND : OUTPUT_DONE);
333 if (!TEST_FLAG(modules[i].flags, MODULE_CHAINED)) {
334 char **p;
336 for (p = s; *p; p++) {
337 free(*p);
340 free(s);
341 s = NULL;
346 return ret;
349 /* Copy options for each module into it's own argc and argv variables stopping
350 * at -- (getopt(3)). */
351 static int init_module_options(int the_argc, char **the_argv, struct module mod)
353 char tmp[255];
354 module_options *m;
355 module_options_init *o;
356 int old_optind = optind;
357 int argc = 0;
358 char **argv = NULL;
359 int opt;
360 int ret = EXIT_SUCCESS;
361 char *optstring = NULL;
362 char *defaults = NULL;
363 int have_an_argument = 0;
365 if ((o = dlsym(mod.m, "ui_module_options_init")) == NULL) {
366 warnx("%s", dlerror());
367 return EXIT_FAILURE;
370 if ((optstring = (*o) (&defaults))) {
371 argv = Realloc(argv, (argc + 2) * sizeof(char *));
372 argv[argc++] = strdup(__progname);
373 argv[argc] = NULL;
375 /* Probably a default module. */
376 if (the_argv == NULL)
377 goto blah;
379 while ((opt = getopt(the_argc, the_argv, optstring)) != -1) {
380 switch (opt) {
381 case '?':
382 warnx("%s: invalid option -- %c\n", mod.name,
383 optopt);
384 return EXIT_FAILURE;
385 default:
386 break;
389 argv = Realloc(argv, (argc + 2) * sizeof(char *));
390 snprintf(tmp, sizeof(tmp), "-%c%s", opt, (optarg) ? optarg : "");
391 argv[argc++] = strdup(tmp);
392 argv[argc] = NULL;
393 have_an_argument = 1;
396 else
397 goto skip_option_stuff;
399 blah:
401 * No options were specified for this module. Set the modules default
402 * options (ui_module_options_init()) if any.
404 if (!have_an_argument && defaults) {
405 argv = Realloc(argv, (argc + 2) * sizeof(char *));
406 snprintf(tmp, sizeof(tmp), "-%s", defaults);
407 argv[argc++] = strdup(tmp);
408 argv[argc] = NULL;
411 old_optind = optind;
412 opterr = optind = optopt = 1;
414 if ((m = dlsym(mod.m, "ui_module_options")) == NULL) {
415 warnx("%s", dlerror());
416 return EXIT_FAILURE;
419 ret |= (*m) (argc, argv);
420 optind = old_optind;
422 skip_option_stuff:
423 return ret;
427 * parseargs.c
429 * This will parse a line used as an argument list for the exec() line of
430 * functions returning a dynamically allocated array of character pointers so
431 * you should free() it afterwards. Both ' and " quoting is supported (with
432 * escapes) for multi-word arguments.
434 * This is my second attempt at it. Works alot better than the first. :)
436 * 2002/10/05
437 * Ben Kibbey <bjk@luxsci.net>
439 * 2004/11/07
440 * Modified to handle argv[0] and argc. (Ben Kibbey <bjk@luxsci.net>)
442 static char **parseargv(char *str, const char *progname, int *me_argc)
444 char **pptr, *s;
445 char arg[255];
446 int idx = 0;
447 int quote = 0;
448 int lastchar = 0;
449 int i;
450 int my_argc = 0;
452 if (!str)
453 return NULL;
455 if (!(pptr = malloc(sizeof(char *))))
456 return NULL;
458 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
459 pptr[idx++] = strdup(progname);
460 my_argc++;
462 for (i = 0, s = str; *s; lastchar = *s++) {
463 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
464 quote = (quote) ? 0 : 1;
465 continue;
468 if (*s == ' ' && !quote) {
469 arg[i] = 0;
470 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
471 pptr[idx++] = strdup(arg);
472 my_argc++;
473 arg[0] = i = 0;
474 continue;
477 if ((i + 1) == sizeof(arg))
478 continue;
480 arg[i++] = *s;
483 arg[i] = 0;
485 if (arg[0]) {
486 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
487 pptr[idx++] = strdup(arg);
488 my_argc++;
491 pptr[idx] = NULL;
492 *me_argc = my_argc;
493 return pptr;
496 static char *get_home_directory()
498 struct passwd *pw;
499 static char dir[PATH_MAX];
501 errno = 0;
503 if ((pw = getpwuid(getuid())) == NULL) {
504 if (errno)
505 warn("getpwuid()");
506 else
507 warnx("getpwuid(): no such uid");
509 return NULL;
512 strncpy(dir, pw->pw_dir, sizeof(dir));
513 return dir;
516 /* Read in a configuration file adding modules to the module array and
517 * checking any module options. */
518 static int parse_rc_file(const char *filename)
520 char line[LINE_MAX], *p;
521 FILE *fp;
522 int idx;
523 int old_optind = optind;
525 if ((fp = fopen(filename, "r")) == NULL) {
526 warn("%s", filename);
527 return 1;
530 while ((p = fgets(line, sizeof(line), fp)) != NULL) {
531 char name[PATH_MAX], options[LINE_MAX], tmp[PATH_MAX], *s;
532 int my_argc;
533 char **my_argv;
534 int lastchar = '\0';
536 while (*p && isspace((unsigned char) *p))
537 p++;
539 if (*p == '#')
540 continue;
542 s = name;
544 if (*p == '>' || *p == '-') {
545 chaining = 1;
547 if (*p == '-')
548 chain_output = 0;
550 p++;
553 while (*p && *p != ' ' && *p != '\t') {
554 if (*p == '\n') {
555 p++;
556 break;
559 *s++ = *p++;
562 *s = '\0';
564 if (!name[0])
565 continue;
567 s = options;
569 while (*p && isspace((unsigned char) *p))
570 p++;
572 lastchar = *p;
574 while (*p) {
575 if (*p == '\n' || (*p == '#' && lastchar != '\\'))
576 break;
578 if (*p == '#' && lastchar == '\\') {
579 lastchar = *--s = *p++;
580 s++;
581 continue;
584 lastchar = *s++ = *p++;
587 *s = '\0';
588 p = name;
590 if (*p == '~') {
591 s = get_home_directory();
592 strncpy(tmp, s, sizeof(tmp));
593 p++;
594 safe_strncat(tmp, p, sizeof(tmp));
595 strncpy(name, tmp, sizeof(name));
598 if (open_module(name, &idx))
599 continue;
601 if ((my_argv = parseargv(options, __progname, &my_argc)) == NULL)
602 continue;
604 optind = 0;
606 if (init_module_options(my_argc, my_argv, modules[idx])) {
607 fclose(fp);
608 return 2;
611 optind = old_optind;
614 fclose(fp);
615 return 0;
618 int main(int argc, char *argv[])
620 int i = 0;
621 int ret = EXIT_SUCCESS;
622 int opt;
623 char line[LINE_MAX], *s = NULL;
624 int want_help = 0;
626 #ifndef HAVE___PROGNAME
627 __progname = argv[0];
628 #endif
629 delimchar = DEFAULT_DELIMINATING_CHAR;
630 multichar = DEFAULT_MULTI_CHAR;
631 strncpy(tf, DEFAULT_TIMEFORMAT, sizeof(tf));
632 chain_output = 1;
634 while ((opt = getopt(argc, argv, "+x:X:dm:c:hO:F:t:vVLf")) != -1) {
636 * See getopt(3).
638 opterr = 0;
640 switch (opt) {
641 case 'd':
642 i = module_index;
644 if (open_module("passwd.so", &i) == 0) {
645 if (init_module_options(1, NULL, modules[i]))
646 want_help = 1;
648 else {
649 ret = EXIT_FAILURE;
650 goto cleanup;
653 if (open_module("mail.so", &i) == 0) {
654 if (init_module_options(1, NULL, modules[i]))
655 want_help = 1;
657 else {
658 ret = EXIT_FAILURE;
659 goto cleanup;
662 if (open_module("login.so", &i) == 0) {
663 if (init_module_options(1, NULL, modules[i]))
664 want_help = 1;
666 else {
667 ret = EXIT_FAILURE;
668 goto cleanup;
671 break;
672 case 'm':
673 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
674 (optarg[0] == '\\' && strlen(optarg) != 2)) {
675 want_help = 1;
676 break;
679 if ((multichar = escapes(optarg)) == 0)
680 want_help = 1;
682 break;
683 case 'c':
684 if ((ret = parse_rc_file(optarg)) != 0) {
685 if (ret == 2)
686 want_help = 1;
687 else
688 exit(EXIT_FAILURE);
690 break;
691 case 'F':
692 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
693 (optarg[0] == '\\' && strlen(optarg) != 2)) {
694 want_help = 1;
695 break;
698 if ((delimchar = escapes(optarg)) == 0)
699 want_help = 1;
701 break;
702 case 't':
703 strncpy(tf, optarg, sizeof(tf));
704 break;
705 case 'V':
706 printf("%s\n%s\n", PACKAGE_STRING, COPYRIGHT);
707 exit(EXIT_SUCCESS);
708 break;
709 case 'L':
710 followsymlinks = 1;
711 break;
712 case 'f':
713 usefile = 1;
714 break;
715 case 'v':
716 verbose++;
717 break;
718 case 'X':
719 chain_output = 0;
720 case 'x':
721 chaining = 1;
722 case 'O':
723 if (open_module(optarg, &i)) {
724 ret = EXIT_FAILURE;
725 goto cleanup;
728 if (init_module_options(argc, argv, modules[i]))
729 want_help = 1;
732 * For modules which have no options at all (to keep getopt
733 * from interpreting the rest as arguments.
735 if (optind < argc) {
736 if (strcmp(argv[optind], "--") == 0)
737 optind++;
740 break;
741 case 'h':
742 default:
743 want_help = 1;
744 break;
748 /* The last module cannot be chained (syntax). */
749 if (!module_index || TEST_FLAG(modules[module_index - 1].flags, MODULE_CHAINED))
750 want_help = 1;
752 /* Cycle through the modules and output their help text. */
753 if (want_help) {
754 usage_header();
756 for (i = 0; i < module_index; i++) {
757 module_help *m_help;
759 if (TEST_FLAG(modules[i].flags, MODULE_DUP))
760 continue;
762 if ((m_help = dlsym(modules[i].m, "ui_module_help")) == NULL) {
763 warnx("%s", dlerror());
764 continue;
767 fprintf(stderr, "%s\n", modules[i].name);
768 (*m_help) ();
771 usage();
772 cleanup_modules();
773 exit(EXIT_FAILURE);
776 if (argc == optind || strcmp(argv[optind], "-") == 0) {
777 while ((s = fgets(line, sizeof(line), stdin)) != NULL) {
778 if (s[strlen(s) - 1] == '\n')
779 s[strlen(s) - 1] = '\0';
781 ret |= junction(s);
784 else {
785 for (; optind < argc; optind++)
786 ret |= junction(argv[optind]);
789 cleanup:
790 cleanup_modules();
791 exit(ret);