Modules are located in src/modules.
[userinfo.git] / src / ui.c
blobd8790ff57cdc6f6ec8bc189834da73b3fb315385
1 /* $Id: ui.c,v 2.1 2005-07-24 13:50:57 bjk Exp $ */
2 /*
3 Copyright (C) 2001-2005 Ben Kibbey <bjk@arbornet.org>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <unistd.h>
21 #include <stdlib.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <errno.h>
25 #include <ctype.h>
26 #include <pwd.h>
27 #include <time.h>
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
33 #include "ui.h"
35 #ifndef HAVE_ERR_H
36 #include "err.c"
37 #endif
39 static void *Realloc(void *p, size_t size)
41 void *p2;
43 if ((p2 = realloc(p, size)) == NULL)
44 err(EXIT_FAILURE, "%s", "realloc()");
46 return p2;
49 /* This may be used in modules to keep a consistant time format with other
50 * modules. */
51 char *stamp(time_t epoch, const char *format)
53 static char buf[TIMEBUFSIZE];
54 struct tm *t;
56 t = localtime(&epoch);
57 strftime(buf, sizeof(buf), format, t);
59 return buf;
62 /* This may be used in modules to add a string to the buffer (module_exec()).
64 void add_string(char ***buf, int *index, const char *str)
66 char **s = *buf;
67 int idx = *index;
69 if ((s = realloc(s, (idx + 2) * sizeof(char *))) == NULL) {
70 warn("%s", "realloc()");
71 return;
74 s[idx++] = strdup(str);
75 s[idx] = NULL;
76 *index = idx;
77 *buf = s;
78 return;
81 /* This is for the field separators (-F and -m). */
82 static int escapes(const char *str)
84 int c = 0;
86 if (str[0] != '\\')
87 return str[0];
89 switch (*++str) {
90 case 't':
91 c = '\t';
92 break;
93 case 'n':
94 c = '\n';
95 break;
96 case '\\':
97 c = '\\';
98 break;
99 case 'v':
100 c = '\v';
101 break;
102 case 'b':
103 c = '\b';
104 break;
105 case 'f':
106 c = '\f';
107 break;
108 case 'r':
109 c = '\r';
110 break;
111 case '\'':
112 c = '\'';
113 break;
114 default:
115 c = 0;
116 break;
119 return c;
122 /* Help text. Module help text is displayed after this. */
123 static void usage_header()
125 printf("Usage: %s [-vhVlf] [-c <filename>] [-t fmt] [-m c] [-F c] [-d]\n"
126 "\t[[-xX] -O <module1> [options] [-- [-xX] -O <module2> [...]]]\n"
127 "\t[- | username | filename] [...]\n\n", __progname);
128 return;
131 /* Help text. Module help text is displayed before this. */
132 static void usage()
134 printf(" -d\tLoad the default modules (passwd.so, mail.so, and login.so).\n");
135 printf(" -c\tRead a configuration file. Can be used more than once.\n");
136 printf(" -O\tLoad a module. Can be used more than once.\n");
137 printf(" -x\tChain module1's output to module2's input.\n");
138 printf(" -X\tDon't output module1's info, only chain it.\n");
139 printf(" -F c\tSeparate output with the specified character "
140 "('%c').\n", delimchar);
141 printf(" -m c\tSeparate multi-string values with the specified "
142 "character ('%c').\n", multichar);
143 printf(" -t tf\tstrftime(3) time format ('%s').\n", DEFAULT_TIMEFORMAT);
144 printf(" -f\tUsers are the owners of the specified files.\n");
145 printf(" -l\tFollow symbolic links.\n");
146 printf(" -v\tVerbose output when possible.\n");
147 printf(" -h\tThis help text.\n");
148 printf(" -V\tVersion information.\n\n");
149 printf("Output key: %s=unknown/error, %s=none, %s=yes/on, "
150 "%s=no/off\n", UNKNOWN, NONE, ON, OFF);
151 return;
154 /* Add a module to the array of loaded modules. The index argument is the
155 * current item number being added stored in an integer. The filename must
156 * have a .so extension. The module is also initialized here with
157 * module_init(). */
158 static int open_module(char *filename, int *index)
160 void *m;
161 module_init *init;
162 char *p, s[FILENAME_MAX];
163 int i;
164 int chainable = 0;
165 char tmp[64];
167 strncpy(s, filename, sizeof(s));
168 s[strlen(s) - 3] = '\0';
170 if ((p = strrchr(s, '/')) != NULL)
171 p++;
172 else {
173 strncpy(s, filename, sizeof(s));
174 p = s;
175 p[strlen(p) - 3] = '\0';
178 for (i = 0; i < module_index; i++) {
179 if (strcmp(p, modules[i].name) == 0) {
180 warnx("%s.so: a module by this name is already loaded", p);
182 chaining = 0;
183 chain_output = 1;
184 return 1;
189 if ((m = dlopen(filename, RTLD_LAZY)) == NULL) {
190 warnx("%s", dlerror());
191 chaining = 0;
192 chain_output = 1;
193 return 1;
196 modules = Realloc(modules, (module_index + 2) * sizeof(struct module));
197 modules[module_index].m = m;
199 strncpy(modules[module_index].name, p, sizeof(modules[module_index].name));
200 *index = module_index++;
202 snprintf(tmp, sizeof(tmp), "%s_init", modules[*index].name);
204 if ((init = dlsym(modules[*index].m, tmp)) == NULL)
205 warnx("%s", dlerror());
206 else
207 (*init) (&chainable);
209 modules[*index].chainable = chainable;
211 if (*index - 1 >= 0 && modules[*index - 1].chained &&
212 modules[*index].chainable == 0) {
213 warnx("%s: this module is not chainable", modules[*index].name);
214 return 1;
217 /* Module chaining. See junction() for more info. */
218 if (chaining) {
219 modules[*index].chained = 1;
221 if (chain_output)
222 modules[*index].output = 1;
223 else
224 modules[*index].output = 0;
226 chaining = 0;
227 chain_output = 1;
230 return 0;
233 /* This just free's up the array of modules. The modules should clean up after
234 * themselves via the module_exit() function. */
235 static void cleanup_modules()
237 int i;
239 for (i = 0; i < module_index; i++) {
240 char tmp[64];
241 module_exit *e;
243 snprintf(tmp, sizeof(tmp), "%s_exit", modules[i].name);
245 if ((e = dlsym(modules[i].m, tmp)) == NULL)
246 warnx("%s", dlerror());
247 else
248 (*e) ();
250 dlclose(modules[i].m);
253 free(modules);
254 return;
257 static void output(char **s, const int sep, int which)
259 int i;
261 if (s) {
262 for (i = 0; s[i]; i++) {
263 printf("%s", s[i]);
265 if (s[i + 1])
266 printf("%c", sep);
270 if (which == OUTPUT_DONE)
271 printf("\n");
272 else if (which == OUTPUT_APPEND)
273 printf("%c", sep);
275 return;
278 static void free_strings(char **s)
280 int n;
282 for (n = 0; s[n]; n++) {
283 if (s[n])
284 free(s[n]);
287 return;
290 /* Pass the argument to each loaded module. */
291 static int junction(const char *arg)
293 int i;
294 struct passwd *pw;
295 struct stat st;
296 int ret = EXIT_SUCCESS;
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 (i = 0; i < module_index; i++) {
339 module_exec *m_exec;
340 char tmp[64];
341 char **s;
343 snprintf(tmp, sizeof(tmp), "%s_exec", modules[i].name);
345 if ((m_exec = dlsym(modules[i].m, tmp)) == NULL) {
346 warnx("%s", dlerror());
347 continue;
350 ret |= (*m_exec) (&s, pw, multichar, verbose, tf);
352 if (modules[i].chained == 0 || (modules[i].chained &&
353 modules[i].output)) {
354 output(s, delimchar,
355 ((i + 1) < module_index) ? OUTPUT_APPEND : OUTPUT_DONE);
357 if (modules[i].chained == 0)
358 free_strings(s);
360 else {
361 /* Chaining with no output. */
362 snprintf(tmp, sizeof(tmp), "%s_exec", modules[++i].name);
364 if ((m_exec = dlsym(modules[i].m, tmp)) == NULL) {
365 warnx("%s", dlerror());
366 continue;
369 ret |= (*m_exec) (&s, pw, multichar, verbose, tf);
370 output(s, delimchar,
371 ((i + 1) < module_index) ? OUTPUT_APPEND : OUTPUT_DONE);
372 free_strings(s);
376 return ret;
379 /* Copy options for each module into it's own argc and argv variables stopping
380 * at -- (getopt(3)). */
381 static int init_module_options(int the_argc, char **the_argv, int index)
383 char tmp[255];
384 module_options *m;
385 module_options_init *o;
386 int old_optind = optind;
387 int argc = 0;
388 char **argv = NULL;
389 int opt;
390 int ret = EXIT_SUCCESS;
391 char *optstring = NULL;
392 char *defaults = NULL;
393 int have_an_argument = 0;
395 snprintf(tmp, sizeof(tmp), "%s_options_init", modules[index].name);
397 if ((o = dlsym(modules[index].m, tmp)) == NULL) {
398 warnx("%s", dlerror());
399 return EXIT_FAILURE;
402 if ((optstring = (*o) (&defaults))) {
403 argv = Realloc(argv, (argc + 2) * sizeof(char *));
404 argv[argc++] = strdup(__progname);
405 argv[argc] = NULL;
407 /* Probably a default module. */
408 if (the_argv == NULL)
409 goto blah;
411 while ((opt = getopt(the_argc, the_argv, optstring)) != -1) {
412 switch (opt) {
413 case '?':
414 warnx("%s.so: invalid option -- %c\n", modules[index].name,
415 optopt);
416 return EXIT_FAILURE;
417 default:
418 break;
421 argv = Realloc(argv, (argc + 2) * sizeof(char *));
422 snprintf(tmp, sizeof(tmp), "-%c%s", opt, (optarg) ? optarg : "");
423 argv[argc++] = strdup(tmp);
424 argv[argc] = NULL;
425 have_an_argument = 1;
428 else
429 goto skip_option_stuff;
431 blah:
433 * No options were specified for this module. Set the modules default
434 * options (module_options_init()) if any.
436 if (!have_an_argument && defaults) {
437 argv = Realloc(argv, (argc + 2) * sizeof(char *));
438 snprintf(tmp, sizeof(tmp), "-%s", defaults);
439 argv[argc++] = strdup(tmp);
440 argv[argc] = NULL;
443 old_optind = optind;
444 opterr = optind = optopt = 1;
446 snprintf(tmp, sizeof(tmp), "%s_options", modules[index].name);
448 if ((m = dlsym(modules[index].m, tmp)) == NULL) {
449 warnx("%s", dlerror());
450 return EXIT_FAILURE;
453 ret |= (*m) (argc, argv);
454 optind = old_optind;
456 skip_option_stuff:
457 return ret;
461 * parseargs.c
463 * This will parse a line used as an argument list for the exec() line of
464 * functions returning a dynamically allocated array of character pointers so
465 * you should free() it afterwards. Both ' and " quoting is supported (with
466 * escapes) for multi-word arguments.
468 * This is my second attempt at it. Works alot better than the first. :)
470 * 2002/10/05
471 * Ben Kibbey <bjk@arbornet.org>
473 * 2004/11/07
474 * Modified to handle argv[0] and argc. (Ben Kibbey <bjk@arbornet.org>)
476 static char **parseargv(char *str, const char *progname, int *me_argc)
478 char **pptr, *s;
479 char arg[255];
480 int index = 0;
481 int quote = 0;
482 int lastchar = 0;
483 int i;
484 int my_argc = 0;
486 if (!str)
487 return NULL;
489 if (!(pptr = malloc(sizeof(char *))))
490 return NULL;
492 pptr = realloc(pptr, (index + 2) * sizeof(char *));
493 pptr[index++] = strdup(progname);
494 my_argc++;
496 for (i = 0, s = str; *s; lastchar = *s++) {
497 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
498 quote = (quote) ? 0 : 1;
499 continue;
502 if (*s == ' ' && !quote) {
503 arg[i] = 0;
504 pptr = realloc(pptr, (index + 2) * sizeof(char *));
505 pptr[index++] = strdup(arg);
506 my_argc++;
507 arg[0] = i = 0;
508 continue;
511 if ((i + 1) == sizeof(arg))
512 continue;
514 arg[i++] = *s;
517 arg[i] = 0;
519 if (arg[0]) {
520 pptr = realloc(pptr, (index + 2) * sizeof(char *));
521 pptr[index++] = strdup(arg);
522 my_argc++;
525 pptr[index] = NULL;
526 *me_argc = my_argc;
527 return pptr;
530 static char *get_home_directory()
532 struct passwd *pw;
533 static char dir[FILENAME_MAX];
535 errno = 0;
537 if ((pw = getpwuid(getuid())) == NULL) {
538 if (errno)
539 warn("getpwuid()");
540 else
541 warnx("getpwuid(): no such uid");
543 return NULL;
546 strncpy(dir, pw->pw_dir, sizeof(dir));
547 return dir;
550 /* Read in a configuration file adding modules to the module array and
551 * checking any module options. */
552 static int parse_rc_file(const char *filename)
554 char line[LINE_MAX], *p;
555 FILE *fp;
556 int index;
557 int old_optind = optind;
559 if ((fp = fopen(filename, "r")) == NULL) {
560 warn("%s", filename);
561 return 1;
564 while ((p = fgets(line, sizeof(line), fp)) != NULL) {
565 char name[FILENAME_MAX], options[LINE_MAX], tmp[LINE_MAX], *s;
566 int my_argc;
567 char **my_argv;
568 int lastchar = '\0';
570 while (*p && isspace((unsigned char) *p))
571 p++;
573 if (*p == '#')
574 continue;
576 s = name;
578 if (*p == '>' || *p == '-') {
579 chaining = 1;
581 if (*p == '-')
582 chain_output = 0;
584 p++;
587 while (*p && *p != ' ' && *p != '\t') {
588 if (*p == '\n') {
589 p++;
590 break;
593 *s++ = *p++;
596 *s = '\0';
598 if (!name[0])
599 continue;
601 s = options;
603 while (*p && isspace((unsigned char) *p))
604 p++;
606 lastchar = *p;
608 while (*p) {
609 if (*p == '\n' || (*p == '#' && lastchar != '\\'))
610 break;
612 if (*p == '#' && lastchar == '\\') {
613 lastchar = *--s = *p++;
614 s++;
615 continue;
618 lastchar = *s++ = *p++;
621 *s = '\0';
622 p = name;
624 if (*p == '~') {
625 s = get_home_directory();
626 strncpy(tmp, s, sizeof(tmp));
627 p++;
628 strncat(tmp, p, sizeof(tmp));
629 strncpy(name, tmp, sizeof(name));
632 if (open_module(name, &index))
633 continue;
635 if ((my_argv = parseargv(options, __progname, &my_argc)) == NULL)
636 continue;
638 optind = 0;
640 if (init_module_options(my_argc, my_argv, index)) {
641 fclose(fp);
642 return 2;
645 optind = old_optind;
648 fclose(fp);
649 return 0;
652 int main(int argc, char *argv[])
654 int i = 0;
655 int ret = EXIT_SUCCESS;
656 int opt;
657 char line[LINE_MAX], *s = NULL;
658 char tmp[64];
659 int want_help = 0;
661 #ifndef HAVE___PROGNAME
662 __progname = argv[0];
663 #endif
664 delimchar = DEFAULT_DELIMINATING_CHAR;
665 multichar = DEFAULT_MULTI_CHAR;
666 strncpy(tf, DEFAULT_TIMEFORMAT, sizeof(tf));
667 chain_output = 1;
669 while ((opt = getopt(argc, argv, "+x:X:dm:c:hO:F:t:vVlf")) != -1) {
671 * See getopt(3).
673 opterr = 0;
675 switch (opt) {
676 case 'd':
677 i = module_index;
679 if (open_module("passwd.so", &i) == 0) {
680 if (init_module_options(1, NULL, i))
681 want_help = 1;
683 else {
684 ret = EXIT_FAILURE;
685 goto cleanup;
688 if (open_module("mail.so", &i) == 0) {
689 if (init_module_options(1, NULL, i))
690 want_help = 1;
692 else {
693 ret = EXIT_FAILURE;
694 goto cleanup;
697 if (open_module("login.so", &i) == 0) {
698 if (init_module_options(1, NULL, i))
699 want_help = 1;
701 else {
702 ret = EXIT_FAILURE;
703 goto cleanup;
706 break;
707 case 'm':
708 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
709 (optarg[0] == '\\' && strlen(optarg) != 2)) {
710 want_help = 1;
711 break;
714 if ((multichar = escapes(optarg)) == 0)
715 want_help = 1;
717 break;
718 case 'c':
719 if ((ret = parse_rc_file(optarg)) != 0) {
720 if (ret == 2)
721 want_help = 1;
722 else
723 exit(EXIT_FAILURE);
725 break;
726 case 'F':
727 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
728 (optarg[0] == '\\' && strlen(optarg) != 2)) {
729 want_help = 1;
730 break;
733 if ((delimchar = escapes(optarg)) == 0)
734 want_help = 1;
736 break;
737 case 't':
738 strncpy(tf, optarg, sizeof(tf));
739 break;
740 case 'V':
741 printf("%s\n%s\n", PACKAGE_STRING, COPYRIGHT);
742 exit(EXIT_SUCCESS);
743 break;
744 case 'l':
745 followsymlinks = 1;
746 break;
747 case 'f':
748 usefile = 1;
749 break;
750 case 'v':
751 verbose = 1;
752 break;
753 case 'X':
754 chain_output = 0;
755 case 'x':
756 chaining = 1;
757 case 'O':
758 if (open_module(optarg, &i)) {
759 ret = EXIT_FAILURE;
760 goto cleanup;
763 if (init_module_options(argc, argv, i))
764 want_help = 1;
766 break;
767 case 'h':
768 default:
769 want_help = 1;
770 break;
774 /* The last module cannot be chained (syntax). */
775 if (!module_index || modules[module_index - 1].chained)
776 want_help = 1;
778 /* Cycle through the modules and output their help text. */
779 if (want_help) {
780 usage_header();
782 for (i = 0; i < module_index; i++) {
783 module_help *m_help;
785 snprintf(tmp, sizeof(tmp), "%s_help", modules[i].name);
787 if ((m_help = dlsym(modules[i].m, tmp)) == NULL) {
788 warnx("%s", dlerror());
789 continue;
792 (*m_help) ();
795 usage();
796 cleanup_modules();
797 exit(EXIT_FAILURE);
800 if (argc == optind || strcmp(argv[optind], "-") == 0) {
801 while ((s = fgets(line, sizeof(line), stdin)) != NULL) {
802 if (s[strlen(s) - 1] == '\n')
803 s[strlen(s) - 1] = '\0';
805 ret |= junction(s);
808 else {
809 for (; optind < argc; optind++)
810 ret |= junction(argv[optind]);
813 cleanup:
814 cleanup_modules();
815 exit(ret);