Updated the copyright year.
[userinfo.git] / src / ui.c
blob0d4f00e3f15a6bf1b2005dcbc7e6ba396a849a58
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 "ui.h"
42 static void *Realloc(void *p, size_t size)
44 void *p2;
46 if ((p2 = realloc(p, size)) == NULL)
47 err(EXIT_FAILURE, "%s", "realloc()");
49 return p2;
52 /* This may be used in modules to keep a consistant time format with other
53 * modules. */
54 char *stamp(time_t epoch, const char *format)
56 static char buf[TIMEBUFSIZE];
57 struct tm *t;
59 t = localtime(&epoch);
60 strftime(buf, sizeof(buf), format, t);
61 return buf;
64 /*
65 * This may be used in modules to add a string to the buffer (ui_module_exec()).
67 void add_string(char ***buf, const char *str)
69 char **s;
70 int i = 0;
72 if (*buf) {
73 for (s = *buf; *s; s++)
74 i++;
77 s = *buf;
78 s = Realloc(s, (i + 2) * sizeof(char *));
79 s[i++] = strdup(str);
80 s[i] = NULL;
81 *buf = s;
84 /* This is for the field separators (-F and -m). */
85 static int escapes(const char *str)
87 int c = 0;
89 if (str[0] != '\\')
90 return str[0];
92 switch (*++str) {
93 case 't':
94 c = '\t';
95 break;
96 case 'n':
97 c = '\n';
98 break;
99 case '\\':
100 c = '\\';
101 break;
102 case 'v':
103 c = '\v';
104 break;
105 case 'b':
106 c = '\b';
107 break;
108 case 'f':
109 c = '\f';
110 break;
111 case 'r':
112 c = '\r';
113 break;
114 case '\'':
115 c = '\'';
116 break;
117 default:
118 c = 0;
119 break;
122 return c;
125 /* Help text. Module help text is displayed after this. */
126 static void usage_header()
128 printf("Usage: %s [-vhVL] [-c <filename>] [-t fmt] [-m c] [-F c] [-d]\n"
129 "\t[[-xX] -O <module1> [options] [-- [-xX] -O <module2> [...]]]\n"
130 "\t[- | username | -f filename] [...]\n\n", __progname);
133 /* Help text. Module help text is displayed before this. */
134 static void usage()
136 printf(" -d\tLoad the default modules (passwd.so, mail.so, and login.so).\n");
137 printf(" -c\tRead a configuration file. Can be used more than once.\n");
138 printf(" -O\tLoad a module. Can be used more than once.\n");
139 printf(" -x\tChain module1's output to module2's input.\n");
140 printf(" -X\tDon't output module1's info, only chain it.\n");
141 printf(" -F c\tSeparate output with the specified character "
142 "('%c').\n", delimchar);
143 printf(" -m c\tSeparate multi-string values with the specified "
144 "character ('%c').\n", multichar);
145 printf(" -t tf\tstrftime(3) time format ('%s').\n", DEFAULT_TIMEFORMAT);
146 printf(" -f\tUsers are the owners of the specified files.\n");
147 printf(" -L\tFollow symbolic links.\n");
148 printf(" -v\tVerbose output when possible (twice for all modules).\n");
149 printf(" -h\tThis help text.\n");
150 printf(" -V\tVersion information.\n\n");
151 printf("Output key: %s=unknown/error, %s=none, %s=yes/on, "
152 "%s=no/off\n", UNKNOWN, NONE, ON, OFF);
156 * Add a module to the array of loaded modules. The index argument is the
157 * current item number being added stored in an integer. The module is also
158 * initialized here with ui_module_init().
160 static int open_module(char *filename, int *idx)
162 void *m;
163 module_init *init;
164 char *p, s[FILENAME_MAX];
165 int i;
166 int chainable = 0;
168 strncpy(s, filename, sizeof(s));
170 if ((p = strrchr(s, '/')) != NULL)
171 p++;
172 else {
173 strncpy(s, filename, sizeof(s));
174 p = s;
177 for (i = 0; i < module_index; i++) {
178 if (strcmp(p, modules[i].name) == 0) {
179 if (TEST_FLAG(modules[i].flags, MODULE_DUP))
180 break;
182 SET_FLAG(modules[i].flags, MODULE_DUP);
183 warnx("%s: a module by this name is already loaded", p);
187 if ((m = dlopen(filename, RTLD_NOW)) == NULL) {
188 warnx("%s", dlerror());
189 chaining = 0;
190 chain_output = 1;
191 return 1;
194 modules = Realloc(modules, (module_index + 2) * sizeof(struct module));
195 modules[module_index].m = m;
197 strncpy(modules[module_index].name, p, sizeof(modules[module_index].name));
198 *idx = module_index++;
200 if ((init = dlsym(modules[*idx].m, "ui_module_init")) == NULL)
201 warnx("%s", dlerror());
202 else
203 (*init) (&chainable);
205 if (chainable)
206 SET_FLAG(modules[*idx].flags, MODULE_CHAINABLE);
208 if (*idx - 1 >= 0 && TEST_FLAG(modules[*idx - 1].flags, MODULE_CHAINED) &&
209 !TEST_FLAG(modules[*idx].flags, MODULE_CHAINABLE)) {
210 warnx("%s: this module is not chainable", modules[*idx].name);
211 return 1;
214 /* Module chaining. See junction() for more info. */
215 if (chaining)
216 SET_FLAG(modules[*idx].flags, MODULE_CHAINED);
218 if (chain_output)
219 SET_FLAG(modules[*idx].flags, MODULE_OUTPUT);
221 if (verbose)
222 SET_FLAG(modules[*idx].flags, MODULE_VERBOSE);
224 chaining = 0;
225 chain_output = 1;
226 verbose = (verbose < 2) ? 0 : 2;
227 return 0;
230 /* This just free's up the array of modules. The modules should clean up after
231 * themselves via the ui_module_exit() function. */
232 static void cleanup_modules()
234 int i;
236 for (i = 0; i < module_index; i++) {
237 module_exit *e;
239 if ((e = dlsym(modules[i].m, "ui_module_exit")) == NULL)
240 warnx("%s", dlerror());
241 else
242 (*e)();
244 dlclose(modules[i].m);
247 free(modules);
250 static void output(char **s, const int sep, int which)
252 int i;
254 if (s) {
255 for (i = 0; s[i]; i++) {
256 printf("%s", s[i]);
258 if (s[i + 1])
259 printf("%c", sep);
263 printf("%c", which == OUTPUT_DONE ? '\n' : sep);
266 /* Pass the argument to each loaded module. */
267 static int junction(const char *arg)
269 int i;
270 struct passwd *pw;
271 struct stat st;
272 int ret = EXIT_SUCCESS;
273 char **s = NULL;
275 if (usefile) {
276 if ((STAT(arg, &st)) == -1) {
277 warn("%s", arg);
278 return EXIT_FAILURE;
281 errno = 0;
283 if ((pw = getpwuid(st.st_uid)) == NULL) {
284 #ifdef __NetBSD__
285 warnx("%s: no such user", arg);
286 #else
287 if (errno == 0 || errno == ENOENT || errno == EPERM
288 || errno == EBADF || errno == ESRCH)
289 warnx("%s: no such uid %u", arg, st.st_uid);
290 else
291 warn("%s", "getpwuid()");
292 #endif
294 return EXIT_FAILURE;
297 else {
298 errno = 0;
300 if ((pw = getpwnam(arg)) == NULL) {
301 #ifdef __NetBSD__
302 warnx("%s: no such user", arg);
303 #else
304 if (errno == 0 || errno == ENOENT || errno == EPERM
305 || errno == EBADF || errno == ESRCH)
306 warnx("%s: no such user", arg);
307 else
308 warn("%s", "getpwnam()");
309 #endif
311 return EXIT_FAILURE;
315 for (i = 0; i < module_index; i++) {
316 module_exec *m_exec;
317 char **p;
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) || (TEST_FLAG(modules[i].flags, MODULE_CHAINED) && TEST_FLAG(modules[i].flags, MODULE_OUTPUT))) {
328 output(s, delimchar,
329 ((i + 1) < module_index) ? OUTPUT_APPEND : OUTPUT_DONE);
331 if (!TEST_FLAG(modules[i].flags, MODULE_CHAINED)) {
332 for (p = s; *p; p++) {
333 free(*p);
336 free(s);
337 s = NULL;
342 return ret;
345 /* Copy options for each module into it's own argc and argv variables stopping
346 * at -- (getopt(3)). */
347 static int init_module_options(int the_argc, char **the_argv, struct module mod)
349 char tmp[255];
350 module_options *m;
351 module_options_init *o;
352 int old_optind = optind;
353 int argc = 0;
354 char **argv = NULL;
355 int opt;
356 int ret = EXIT_SUCCESS;
357 char *optstring = NULL;
358 char *defaults = NULL;
359 int have_an_argument = 0;
361 if ((o = dlsym(mod.m, "ui_module_options_init")) == NULL) {
362 warnx("%s", dlerror());
363 return EXIT_FAILURE;
366 if ((optstring = (*o) (&defaults))) {
367 argv = Realloc(argv, (argc + 2) * sizeof(char *));
368 argv[argc++] = strdup(__progname);
369 argv[argc] = NULL;
371 /* Probably a default module. */
372 if (the_argv == NULL)
373 goto blah;
375 while ((opt = getopt(the_argc, the_argv, optstring)) != -1) {
376 switch (opt) {
377 case '?':
378 warnx("%s: invalid option -- %c\n", mod.name,
379 optopt);
380 return EXIT_FAILURE;
381 default:
382 break;
385 argv = Realloc(argv, (argc + 2) * sizeof(char *));
386 snprintf(tmp, sizeof(tmp), "-%c%s", opt, (optarg) ? optarg : "");
387 argv[argc++] = strdup(tmp);
388 argv[argc] = NULL;
389 have_an_argument = 1;
392 else
393 goto skip_option_stuff;
395 blah:
397 * No options were specified for this module. Set the modules default
398 * options (ui_module_options_init()) if any.
400 if (!have_an_argument && defaults) {
401 argv = Realloc(argv, (argc + 2) * sizeof(char *));
402 snprintf(tmp, sizeof(tmp), "-%s", defaults);
403 argv[argc++] = strdup(tmp);
404 argv[argc] = NULL;
407 old_optind = optind;
408 opterr = optind = optopt = 1;
410 if ((m = dlsym(mod.m, "ui_module_options")) == NULL) {
411 warnx("%s", dlerror());
412 return EXIT_FAILURE;
415 ret |= (*m) (argc, argv);
416 optind = old_optind;
418 skip_option_stuff:
419 return ret;
423 * parseargs.c
425 * This will parse a line used as an argument list for the exec() line of
426 * functions returning a dynamically allocated array of character pointers so
427 * you should free() it afterwards. Both ' and " quoting is supported (with
428 * escapes) for multi-word arguments.
430 * This is my second attempt at it. Works alot better than the first. :)
432 * 2002/10/05
433 * Ben Kibbey <bjk@luxsci.net>
435 * 2004/11/07
436 * Modified to handle argv[0] and argc. (Ben Kibbey <bjk@luxsci.net>)
438 static char **parseargv(char *str, const char *progname, int *me_argc)
440 char **pptr, *s;
441 char arg[255];
442 int idx = 0;
443 int quote = 0;
444 int lastchar = 0;
445 int i;
446 int my_argc = 0;
448 if (!str)
449 return NULL;
451 if (!(pptr = malloc(sizeof(char *))))
452 return NULL;
454 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
455 pptr[idx++] = strdup(progname);
456 my_argc++;
458 for (i = 0, s = str; *s; lastchar = *s++) {
459 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
460 quote = (quote) ? 0 : 1;
461 continue;
464 if (*s == ' ' && !quote) {
465 arg[i] = 0;
466 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
467 pptr[idx++] = strdup(arg);
468 my_argc++;
469 arg[0] = i = 0;
470 continue;
473 if ((i + 1) == sizeof(arg))
474 continue;
476 arg[i++] = *s;
479 arg[i] = 0;
481 if (arg[0]) {
482 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
483 pptr[idx++] = strdup(arg);
484 my_argc++;
487 pptr[idx] = NULL;
488 *me_argc = my_argc;
489 return pptr;
492 static char *get_home_directory()
494 struct passwd *pw;
495 static char dir[FILENAME_MAX];
497 errno = 0;
499 if ((pw = getpwuid(getuid())) == NULL) {
500 if (errno)
501 warn("getpwuid()");
502 else
503 warnx("getpwuid(): no such uid");
505 return NULL;
508 strncpy(dir, pw->pw_dir, sizeof(dir));
509 return dir;
512 /* Read in a configuration file adding modules to the module array and
513 * checking any module options. */
514 static int parse_rc_file(const char *filename)
516 char line[LINE_MAX], *p;
517 FILE *fp;
518 int idx;
519 int old_optind = optind;
521 if ((fp = fopen(filename, "r")) == NULL) {
522 warn("%s", filename);
523 return 1;
526 while ((p = fgets(line, sizeof(line), fp)) != NULL) {
527 char name[FILENAME_MAX], options[LINE_MAX], tmp[LINE_MAX], *s;
528 int my_argc;
529 char **my_argv;
530 int lastchar = '\0';
532 while (*p && isspace((unsigned char) *p))
533 p++;
535 if (*p == '#')
536 continue;
538 s = name;
540 if (*p == '>' || *p == '-') {
541 chaining = 1;
543 if (*p == '-')
544 chain_output = 0;
546 p++;
549 while (*p && *p != ' ' && *p != '\t') {
550 if (*p == '\n') {
551 p++;
552 break;
555 *s++ = *p++;
558 *s = '\0';
560 if (!name[0])
561 continue;
563 s = options;
565 while (*p && isspace((unsigned char) *p))
566 p++;
568 lastchar = *p;
570 while (*p) {
571 if (*p == '\n' || (*p == '#' && lastchar != '\\'))
572 break;
574 if (*p == '#' && lastchar == '\\') {
575 lastchar = *--s = *p++;
576 s++;
577 continue;
580 lastchar = *s++ = *p++;
583 *s = '\0';
584 p = name;
586 if (*p == '~') {
587 s = get_home_directory();
588 strncpy(tmp, s, sizeof(tmp));
589 p++;
590 strncat(tmp, p, sizeof(tmp));
591 strncpy(name, tmp, sizeof(name));
594 if (open_module(name, &idx))
595 continue;
597 if ((my_argv = parseargv(options, __progname, &my_argc)) == NULL)
598 continue;
600 optind = 0;
602 if (init_module_options(my_argc, my_argv, modules[idx])) {
603 fclose(fp);
604 return 2;
607 optind = old_optind;
610 fclose(fp);
611 return 0;
614 int main(int argc, char *argv[])
616 int i = 0;
617 int ret = EXIT_SUCCESS;
618 int opt;
619 char line[LINE_MAX], *s = NULL;
620 int want_help = 0;
622 #ifndef HAVE___PROGNAME
623 __progname = argv[0];
624 #endif
625 delimchar = DEFAULT_DELIMINATING_CHAR;
626 multichar = DEFAULT_MULTI_CHAR;
627 strncpy(tf, DEFAULT_TIMEFORMAT, sizeof(tf));
628 chain_output = 1;
630 while ((opt = getopt(argc, argv, "+x:X:dm:c:hO:F:t:vVLf")) != -1) {
632 * See getopt(3).
634 opterr = 0;
636 switch (opt) {
637 case 'd':
638 i = module_index;
640 if (open_module("passwd.so", &i) == 0) {
641 if (init_module_options(1, NULL, modules[i]))
642 want_help = 1;
644 else {
645 ret = EXIT_FAILURE;
646 goto cleanup;
649 if (open_module("mail.so", &i) == 0) {
650 if (init_module_options(1, NULL, modules[i]))
651 want_help = 1;
653 else {
654 ret = EXIT_FAILURE;
655 goto cleanup;
658 if (open_module("login.so", &i) == 0) {
659 if (init_module_options(1, NULL, modules[i]))
660 want_help = 1;
662 else {
663 ret = EXIT_FAILURE;
664 goto cleanup;
667 break;
668 case 'm':
669 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
670 (optarg[0] == '\\' && strlen(optarg) != 2)) {
671 want_help = 1;
672 break;
675 if ((multichar = escapes(optarg)) == 0)
676 want_help = 1;
678 break;
679 case 'c':
680 if ((ret = parse_rc_file(optarg)) != 0) {
681 if (ret == 2)
682 want_help = 1;
683 else
684 exit(EXIT_FAILURE);
686 break;
687 case 'F':
688 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
689 (optarg[0] == '\\' && strlen(optarg) != 2)) {
690 want_help = 1;
691 break;
694 if ((delimchar = escapes(optarg)) == 0)
695 want_help = 1;
697 break;
698 case 't':
699 strncpy(tf, optarg, sizeof(tf));
700 break;
701 case 'V':
702 printf("%s\n%s\n", PACKAGE_STRING, COPYRIGHT);
703 exit(EXIT_SUCCESS);
704 break;
705 case 'L':
706 followsymlinks = 1;
707 break;
708 case 'f':
709 usefile = 1;
710 break;
711 case 'v':
712 verbose++;
713 break;
714 case 'X':
715 chain_output = 0;
716 case 'x':
717 chaining = 1;
718 case 'O':
719 if (open_module(optarg, &i)) {
720 ret = EXIT_FAILURE;
721 goto cleanup;
724 if (init_module_options(argc, argv, modules[i]))
725 want_help = 1;
728 * For modules which have no options at all (to keep getopt
729 * from interpreting the rest as arguments.
731 if (optind < argc) {
732 if (strcmp(argv[optind], "--") == 0)
733 optind++;
736 break;
737 case 'h':
738 default:
739 want_help = 1;
740 break;
744 /* The last module cannot be chained (syntax). */
745 if (!module_index || TEST_FLAG(modules[module_index - 1].flags, MODULE_CHAINED))
746 want_help = 1;
748 /* Cycle through the modules and output their help text. */
749 if (want_help) {
750 usage_header();
752 for (i = 0; i < module_index; i++) {
753 module_help *m_help;
755 if (TEST_FLAG(modules[i].flags, MODULE_DUP))
756 continue;
758 if ((m_help = dlsym(modules[i].m, "ui_module_help")) == NULL) {
759 warnx("%s", dlerror());
760 continue;
763 fprintf(stderr, "%s\n", modules[i].name);
764 (*m_help) ();
767 usage();
768 cleanup_modules();
769 exit(EXIT_FAILURE);
772 if (argc == optind || strcmp(argv[optind], "-") == 0) {
773 while ((s = fgets(line, sizeof(line), stdin)) != NULL) {
774 if (s[strlen(s) - 1] == '\n')
775 s[strlen(s) - 1] = '\0';
777 ret |= junction(s);
780 else {
781 for (; optind < argc; optind++)
782 ret |= junction(argv[optind]);
785 cleanup:
786 cleanup_modules();
787 exit(ret);