Make a note about multiple loading of the same module.
[userinfo.git] / src / ui.c
blob89b32193e4fdcee34cb080ff105dad2eca88c001
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, struct module_s **ptr)
163 void *m;
164 module_init *init;
165 char *p, s[PATH_MAX];
166 int chainable = 0;
167 struct module_s *mod, **mods, *last;
168 int dup = 0;
170 strncpy(s, filename, sizeof(s));
172 if ((p = strrchr(s, '/')) != NULL)
173 p++;
174 else {
175 strncpy(s, filename, sizeof(s));
176 p = s;
179 for (mods = modules; mods && *mods; mods++) {
180 if (strcmp(p, (*mods)->name) == 0) {
181 dup = 1;
182 break;
186 if ((m = dlopen(filename, RTLD_NOW)) == NULL) {
187 warnx("%s", dlerror());
188 chaining = 0;
189 chain_output = 1;
190 return 1;
193 last = modules ? modules[module_total-1] : NULL;
194 modules = Realloc(modules, (module_total+2) * sizeof(struct module_s *));
195 mod = calloc(1, sizeof(struct module_s));
196 modules[module_total++] = mod;
197 modules[module_total] = NULL;
198 mod->m = m;
199 strncpy(mod->name, p, sizeof(mod->name));
201 if ((init = dlsym(mod->m, "ui_module_init")) == NULL)
202 warnx("%s", dlerror());
203 else
204 (*init) (&chainable);
206 if (chainable)
207 SET_FLAG(mod->flags, MODULE_CHAINABLE);
209 if (last && TEST_FLAG(last->flags, MODULE_CHAINED) &&
210 !TEST_FLAG(mod->flags, MODULE_CHAINABLE)) {
211 warnx("%s: this module is not chainable", mod->name);
212 module_total--;
213 modules[module_total] = NULL;
214 free(mod);
215 return 1;
218 /* Module chaining. See junction() for more info. */
219 if (chaining)
220 SET_FLAG(mod->flags, MODULE_CHAINED);
222 if (chain_output)
223 SET_FLAG(mod->flags, MODULE_OUTPUT);
225 if (verbose)
226 SET_FLAG(mod->flags, MODULE_VERBOSE);
228 if (dup) {
229 SET_FLAG(mod->flags, MODULE_DUP);
230 warnx("%s: a module by this name is already loaded", p);
233 chaining = 0;
234 chain_output = 1;
235 verbose = (verbose < 2) ? 0 : 2;
236 *ptr = mod;
237 return 0;
240 /* This just free's up the array of modules. The modules should clean up after
241 * themselves via the ui_module_exit() function. */
242 static void cleanup_modules()
244 struct module_s **mod;
246 if (!modules)
247 return;
249 for (mod = modules; *mod; mod++) {
250 module_exit *e;
251 char **p;
253 if ((e = dlsym((*mod)->m, "ui_module_exit")) == NULL)
254 warnx("%s", dlerror());
255 else
256 (*e)();
258 dlclose((*mod)->m);
260 if ((*mod)->argv) {
261 for (p = (*mod)->argv; *p; p++)
262 free(*p);
264 free((*mod)->argv);
267 free(*mod);
270 free(modules);
273 static void output(char **s, const int sep, int which)
275 char **p = s;
277 if (p) {
278 for (; *p; p++) {
279 printf("%s", *p);
281 if (*(p+1))
282 printf("%c", sep);
286 printf("%c", which == OUTPUT_DONE ? '\n' : sep);
289 /* Pass the argument to each loaded module. */
290 static int junction(const char *arg)
292 struct passwd *pw;
293 struct stat st;
294 int ret = EXIT_SUCCESS;
295 char **s = NULL;
296 struct module_s **mod;
298 if (usefile) {
299 if ((STAT(arg, &st)) == -1) {
300 warn("%s", arg);
301 return EXIT_FAILURE;
304 errno = 0;
306 if ((pw = getpwuid(st.st_uid)) == NULL) {
307 #ifdef __NetBSD__
308 warnx("%s: no such user", arg);
309 #else
310 if (errno == 0 || errno == ENOENT || errno == EPERM
311 || errno == EBADF || errno == ESRCH)
312 warnx("%s: no such uid %u", arg, st.st_uid);
313 else
314 warn("%s", "getpwuid()");
315 #endif
317 return EXIT_FAILURE;
320 else {
321 errno = 0;
323 if ((pw = getpwnam(arg)) == NULL) {
324 #ifdef __NetBSD__
325 warnx("%s: no such user", arg);
326 #else
327 if (errno == 0 || errno == ENOENT || errno == EPERM
328 || errno == EBADF || errno == ESRCH)
329 warnx("%s: no such user", arg);
330 else
331 warn("%s", "getpwnam()");
332 #endif
334 return EXIT_FAILURE;
338 for (mod = modules; *mod; mod++) {
339 module_exec *m_exec;
341 if ((m_exec = dlsym((*mod)->m, "ui_module_exec")) == NULL) {
342 warnx("%s", dlerror());
343 continue;
346 ret |= (*m_exec) (&s, pw, multichar,
347 TEST_FLAG((*mod)->flags, MODULE_VERBOSE), tf);
349 if (!TEST_FLAG((*mod)->flags, MODULE_CHAINED) ||
350 (TEST_FLAG((*mod)->flags, MODULE_CHAINED) &&
351 TEST_FLAG((*mod)->flags, MODULE_OUTPUT))) {
352 output(s, delimchar,
353 (*(mod+1)) ? OUTPUT_APPEND : OUTPUT_DONE);
355 if (!TEST_FLAG((*mod)->flags, MODULE_CHAINED)) {
356 char **p;
358 for (p = s; *p; p++) {
359 free(*p);
362 free(s);
363 s = NULL;
368 return ret;
371 /* Copy options for each module into it's own argc and argv variables stopping
372 * at -- (getopt(3)). */
373 static int init_module_options(int the_argc, char **the_argv,
374 struct module_s *mod)
376 char tmp[255];
377 module_options *m;
378 module_options_init *o;
379 int old_optind = optind;
380 int opt;
381 int ret = EXIT_SUCCESS;
382 char *optstring = NULL;
383 char *defaults = NULL;
384 int have_an_argument = 0;
386 if ((o = dlsym(mod->m, "ui_module_options_init")) == NULL) {
387 warnx("%s", dlerror());
388 return EXIT_FAILURE;
391 if ((optstring = (*o) (&defaults))) {
392 mod->argv = Realloc(mod->argv, (mod->argc + 2) * sizeof(char *));
393 mod->argv[mod->argc++] = strdup(__progname);
394 mod->argv[mod->argc] = NULL;
396 /* Probably a default module. */
397 if (the_argv == NULL)
398 goto blah;
400 while ((opt = getopt(the_argc, the_argv, optstring)) != -1) {
401 switch (opt) {
402 case '?':
403 warnx("%s: invalid option -- %c\n", mod->name,
404 optopt);
405 return EXIT_FAILURE;
406 default:
407 break;
410 mod->argv = Realloc(mod->argv, (mod->argc + 2) * sizeof(char *));
411 snprintf(tmp, sizeof(tmp), "-%c%s", opt, (optarg) ? optarg : "");
412 mod->argv[mod->argc++] = strdup(tmp);
413 mod->argv[mod->argc] = NULL;
414 have_an_argument = 1;
417 else
418 goto skip_option_stuff;
420 blah:
422 * No options were specified for this module. Set the modules default
423 * options (ui_module_options_init()) if any.
425 if (!have_an_argument && defaults) {
426 mod->argv = Realloc(mod->argv, (mod->argc + 2) * sizeof(char *));
427 snprintf(tmp, sizeof(tmp), "-%s", defaults);
428 mod->argv[mod->argc++] = strdup(tmp);
429 mod->argv[mod->argc] = NULL;
432 old_optind = optind;
433 opterr = optind = optopt = 1;
435 if ((m = dlsym(mod->m, "ui_module_options")) == NULL) {
436 warnx("%s", dlerror());
437 return EXIT_FAILURE;
440 ret |= (*m) (mod->argc, mod->argv);
441 optind = old_optind;
443 skip_option_stuff:
444 return ret;
448 * parseargs.c
450 * This will parse a line used as an argument list for the exec() line of
451 * functions returning a dynamically allocated array of character pointers so
452 * you should free() it afterwards. Both ' and " quoting is supported (with
453 * escapes) for multi-word arguments.
455 * This is my second attempt at it. Works alot better than the first. :)
457 * 2002/10/05
458 * Ben Kibbey <bjk@luxsci.net>
460 * 2004/11/07
461 * Modified to handle argv[0] and argc. (Ben Kibbey <bjk@luxsci.net>)
463 static char **parseargv(char *str, const char *progname, int *me_argc)
465 char **pptr, *s;
466 char arg[LINE_MAX];
467 int idx = 0;
468 int quote = 0;
469 int lastchar = 0;
470 int i;
471 int my_argc = 0;
473 if (!str)
474 return NULL;
476 if (!(pptr = malloc(sizeof(char *))))
477 return NULL;
479 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
480 pptr[idx++] = strdup(progname);
481 my_argc++;
483 for (i = 0, s = str; *s; lastchar = *s++) {
484 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
485 quote = (quote) ? 0 : 1;
486 continue;
489 if (*s == ' ' && !quote) {
490 arg[i] = 0;
491 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
492 pptr[idx++] = strdup(arg);
493 my_argc++;
494 arg[0] = i = 0;
495 continue;
498 if ((i + 1) == sizeof(arg))
499 continue;
501 arg[i++] = *s;
504 arg[i] = 0;
506 if (arg[0]) {
507 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
508 pptr[idx++] = strdup(arg);
509 my_argc++;
512 pptr[idx] = NULL;
513 *me_argc = my_argc;
514 return pptr;
517 static char *get_home_directory()
519 struct passwd *pw;
520 static char dir[PATH_MAX];
522 errno = 0;
524 if ((pw = getpwuid(getuid())) == NULL) {
525 if (errno)
526 warn("getpwuid()");
527 else
528 warnx("getpwuid(): no such uid");
530 return NULL;
533 strncpy(dir, pw->pw_dir, sizeof(dir));
534 return dir;
537 /* Read in a configuration file adding modules to the module array and
538 * checking any module options. */
539 static int parse_rc_file(const char *filename)
541 char line[LINE_MAX], *p;
542 FILE *fp;
543 int old_optind = optind;
544 struct module_s *mod;
546 if ((fp = fopen(filename, "r")) == NULL) {
547 warn("%s", filename);
548 return 1;
551 while ((p = fgets(line, sizeof(line), fp)) != NULL) {
552 char name[PATH_MAX], options[LINE_MAX], tmp[PATH_MAX], *s;
553 int my_argc;
554 char **my_argv, **ap;
555 int lastchar = '\0';
556 int n;
558 while (*p && isspace((unsigned char) *p))
559 p++;
561 if (*p == '#')
562 continue;
564 s = name;
566 if (*p == '>' || *p == '-') {
567 chaining = 1;
569 if (*p == '-')
570 chain_output = 0;
572 p++;
575 while (*p && *p != ' ' && *p != '\t') {
576 if (*p == '\n') {
577 p++;
578 break;
581 *s++ = *p++;
584 *s = '\0';
586 if (!name[0])
587 continue;
589 s = options;
591 while (*p && isspace((unsigned char) *p))
592 p++;
594 lastchar = *p;
596 while (*p) {
597 if (*p == '\n' || (*p == '#' && lastchar != '\\'))
598 break;
600 if (*p == '#' && lastchar == '\\') {
601 lastchar = *--s = *p++;
602 s++;
603 continue;
606 lastchar = *s++ = *p++;
609 *s = '\0';
610 p = name;
612 if (*p == '~') {
613 s = get_home_directory();
614 strncpy(tmp, s, sizeof(tmp));
615 p++;
616 safe_strncat(tmp, p, sizeof(tmp));
617 strncpy(name, tmp, sizeof(name));
620 if (open_module(name, &mod))
621 continue;
623 if ((my_argv = parseargv(options, __progname, &my_argc)) == NULL)
624 continue;
626 optind = 0;
627 n = init_module_options(my_argc, my_argv, mod);
629 for (ap = my_argv; *ap; ap++)
630 free(*ap);
632 free(my_argv);
634 if (n) {
635 fclose(fp);
636 return 2;
639 optind = old_optind;
642 fclose(fp);
643 return 0;
646 int main(int argc, char *argv[])
648 int ret = EXIT_SUCCESS;
649 int opt;
650 char line[LINE_MAX], *s = NULL;
651 int want_help = 0;
653 #ifndef HAVE___PROGNAME
654 __progname = argv[0];
655 #endif
656 delimchar = DEFAULT_DELIMINATING_CHAR;
657 multichar = DEFAULT_MULTI_CHAR;
658 strncpy(tf, DEFAULT_TIMEFORMAT, sizeof(tf));
659 chain_output = 1;
661 while ((opt = getopt(argc, argv, "+x:X:dm:c:hO:F:t:vVLf")) != -1) {
662 struct module_s *mod;
665 * See getopt(3).
667 opterr = 0;
669 switch (opt) {
670 case 'd':
671 if (open_module("passwd.so", &mod) == 0) {
672 if (init_module_options(1, NULL, mod))
673 want_help = 1;
675 else {
676 ret = EXIT_FAILURE;
677 goto cleanup;
680 if (open_module("mail.so", &mod) == 0) {
681 if (init_module_options(1, NULL, mod))
682 want_help = 1;
684 else {
685 ret = EXIT_FAILURE;
686 goto cleanup;
689 if (open_module("login.so", &mod) == 0) {
690 if (init_module_options(1, NULL, mod))
691 want_help = 1;
693 else {
694 ret = EXIT_FAILURE;
695 goto cleanup;
698 break;
699 case 'm':
700 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
701 (optarg[0] == '\\' && strlen(optarg) != 2)) {
702 want_help = 1;
703 break;
706 if ((multichar = escapes(optarg)) == 0)
707 want_help = 1;
709 break;
710 case 'c':
711 if ((ret = parse_rc_file(optarg)) != 0) {
712 if (ret == 2)
713 want_help = 1;
714 else
715 exit(EXIT_FAILURE);
717 break;
718 case 'F':
719 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
720 (optarg[0] == '\\' && strlen(optarg) != 2)) {
721 want_help = 1;
722 break;
725 if ((delimchar = escapes(optarg)) == 0)
726 want_help = 1;
728 break;
729 case 't':
730 strncpy(tf, optarg, sizeof(tf));
731 break;
732 case 'V':
733 printf("%s\n%s\n", PACKAGE_STRING, COPYRIGHT);
734 exit(EXIT_SUCCESS);
735 break;
736 case 'L':
737 followsymlinks = 1;
738 break;
739 case 'f':
740 usefile = 1;
741 break;
742 case 'v':
743 verbose++;
744 break;
745 case 'X':
746 chain_output = 0;
747 case 'x':
748 chaining = 1;
749 case 'O':
750 if (open_module(optarg, &mod)) {
751 ret = EXIT_FAILURE;
752 goto cleanup;
755 if (init_module_options(argc, argv, mod))
756 want_help = 1;
759 * For modules which have no options at all (to keep getopt
760 * from interpreting the rest as arguments.
762 if (optind < argc) {
763 if (strcmp(argv[optind], "--") == 0)
764 optind++;
767 break;
768 case 'h':
769 default:
770 want_help = 1;
771 break;
775 /* The last module cannot be chained (syntax). */
776 if (!module_total || TEST_FLAG(modules[module_total-1]->flags, MODULE_CHAINED))
777 want_help = 1;
779 /* Cycle through the modules and output their help text. */
780 if (want_help) {
781 usage_header();
782 struct module_s **mod;
784 for (mod = modules; mod && *mod; mod++) {
785 module_help *m_help;
787 if (TEST_FLAG((*mod)->flags, MODULE_DUP))
788 continue;
790 if ((m_help = dlsym((*mod)->m, "ui_module_help")) == NULL) {
791 warnx("%s", dlerror());
792 continue;
795 printf("%s\n", (*mod)->name);
796 (*m_help) ();
799 usage();
800 cleanup_modules();
801 exit(EXIT_FAILURE);
804 if (argc == optind || strcmp(argv[optind], "-") == 0) {
805 while ((s = fgets(line, sizeof(line), stdin)) != NULL) {
806 if (s[strlen(s) - 1] == '\n')
807 s[strlen(s) - 1] = '\0';
809 ret |= junction(s);
812 else {
813 for (; optind < argc; optind++)
814 ret |= junction(argv[optind]);
817 cleanup:
818 cleanup_modules();
819 exit(ret);