Update copyright year to 2015.
[userinfo.git] / src / ui.c
blob1835a591d08e079c0285acd8dff5734c3e96947e
1 /*
2 Copyright (C) 2001-2015 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_LIMITS_H
33 #include <limits.h>
34 #ifndef LINE_MAX
35 #ifdef _POSIX2_LINE_MAX
36 #define LINE_MAX _POSIX2_LINE_MAX
37 #else
38 #define LINE_MAX 2048
39 #endif
40 #endif
41 #endif
43 #ifdef HAVE_GETOPT_H
44 #include <getopt.h>
45 #endif
47 #ifndef HAVE_ERR_H
48 #include "err.c"
49 #endif
51 #include "safe_strncat.c"
52 #include "ui.h"
54 static void *Realloc(void *p, size_t size)
56 void *p2;
58 if ((p2 = realloc(p, size)) == NULL)
59 err(EXIT_FAILURE, "%s", "realloc()");
61 return p2;
64 /* This may be used in modules to keep a consistant time format with other
65 * modules. */
66 char *stamp(time_t epoch, const char *format)
68 static char buf[TIMEBUFSIZE];
69 struct tm *t;
71 t = localtime(&epoch);
72 strftime(buf, sizeof(buf), format, t);
73 return buf;
77 * This may be used in modules to add a string to the buffer (ui_module_exec()).
79 void add_string(char ***buf, const char *str)
81 char **s;
82 int i = 0;
84 if (*buf) {
85 for (s = *buf; *s; s++)
86 i++;
89 s = *buf;
90 s = Realloc(s, (i + 2) * sizeof(char *));
91 s[i++] = strdup(str);
92 s[i] = NULL;
93 *buf = s;
96 /* This is for the field separators (-F and -m). */
97 static int escapes(const char *str)
99 int c = 0;
101 if (str[0] != '\\')
102 return str[0];
104 switch (*++str) {
105 case 't':
106 c = '\t';
107 break;
108 case 'n':
109 c = '\n';
110 break;
111 case '\\':
112 c = '\\';
113 break;
114 case 'v':
115 c = '\v';
116 break;
117 case 'b':
118 c = '\b';
119 break;
120 case 'f':
121 c = '\f';
122 break;
123 case 'r':
124 c = '\r';
125 break;
126 case '\'':
127 c = '\'';
128 break;
129 default:
130 c = 0;
131 break;
134 return c;
137 /* Help text. Module help text is displayed after this. */
138 static void usage_header()
140 printf("Usage: %s [-vhVL] [-c <filename>] [-t fmt] [-m c] [-F c] [-d]\n"
141 "\t[[-xX] -O <module1> [options] [-- [-xX] -O <module2> [...]]]\n"
142 "\t[- | username | -f filename] [...]\n\n", __progname);
145 /* Help text. Module help text is displayed before this. */
146 static void usage()
148 printf(" -d\tLoad the default modules (passwd.so, mail.so, and login.so).\n");
149 printf(" -c\tRead a configuration file. Can be used more than once.\n");
150 printf(" -O\tLoad a module. Can be used more than once.\n");
151 printf(" -x\tChain module1's output to module2's input.\n");
152 printf(" -X\tDon't output module1's info, only chain it.\n");
153 printf(" -F c\tSeparate output with the specified character "
154 "('%c').\n", delimchar);
155 printf(" -m c\tSeparate multi-string values with the specified "
156 "character ('%c').\n", multichar);
157 printf(" -t tf\tstrftime(3) time format ('%s').\n", DEFAULT_TIMEFORMAT);
158 printf(" -f\tUsers are the owners of the specified files.\n");
159 printf(" -L\tFollow symbolic links.\n");
160 printf(" -v\tVerbose output when possible (twice for all modules).\n");
161 printf(" -h\tThis help text.\n");
162 printf(" -V\tVersion information.\n\n");
163 printf("Output key: %s=unknown/error, %s=none, %s=yes/on, "
164 "%s=no/off\n", UNKNOWN, NONE, ON, OFF);
168 * Add a module to the array of loaded modules. The index argument is the
169 * current item number being added stored in an integer. The module is also
170 * initialized here with ui_module_init().
172 static int open_module(char *filename, struct module_s **ptr)
174 void *m;
175 module_init *init;
176 char *p, s[PATH_MAX];
177 int chainable = 0;
178 struct module_s *mod, **mods, *last;
179 int dup = 0;
181 strncpy(s, filename, sizeof(s));
182 s[sizeof(s)-1] = 0;
184 if ((p = strrchr(s, '/')) != NULL)
185 p++;
186 else {
187 strncpy(s, filename, sizeof(s));
188 s[sizeof(s)-1] = 0;
189 p = s;
192 for (mods = modules; mods && *mods; mods++) {
193 if (strcmp(p, (*mods)->name) == 0) {
194 dup = 1;
195 break;
199 if ((m = dlopen(filename, RTLD_NOW)) == NULL) {
200 warnx("%s", dlerror());
201 chaining = 0;
202 chain_output = 1;
203 return 1;
206 last = modules ? modules[module_total-1] : NULL;
207 modules = Realloc(modules, (module_total+2) * sizeof(struct module_s *));
208 mod = calloc(1, sizeof(struct module_s));
209 modules[module_total++] = mod;
210 modules[module_total] = NULL;
211 mod->m = m;
212 strncpy(mod->name, p, sizeof(mod->name));
213 mod->name[sizeof(mod->name)-1] = 0;
215 if ((init = dlsym(mod->m, "ui_module_init")) == NULL)
216 warnx("%s", dlerror());
217 else
218 (*init) (&chainable);
220 if (chainable)
221 SET_FLAG(mod->flags, MODULE_CHAINABLE);
223 if (last && TEST_FLAG(last->flags, MODULE_CHAINED) &&
224 !TEST_FLAG(mod->flags, MODULE_CHAINABLE)) {
225 warnx("%s: this module is not chainable", mod->name);
226 module_total--;
227 modules[module_total] = NULL;
228 free(mod);
229 return 1;
232 /* Module chaining. See junction() for more info. */
233 if (chaining)
234 SET_FLAG(mod->flags, MODULE_CHAINED);
236 if (chain_output)
237 SET_FLAG(mod->flags, MODULE_OUTPUT);
239 if (verbose)
240 SET_FLAG(mod->flags, MODULE_VERBOSE);
242 if (dup) {
243 SET_FLAG(mod->flags, MODULE_DUP);
244 warnx("%s: a module by this name is already loaded", p);
247 chaining = 0;
248 chain_output = 1;
249 verbose = (verbose < 2) ? 0 : 2;
250 *ptr = mod;
251 return 0;
254 /* This just free's up the array of modules. The modules should clean up after
255 * themselves via the ui_module_exit() function. */
256 static void cleanup_modules()
258 struct module_s **mod;
260 if (!modules)
261 return;
263 for (mod = modules; *mod; mod++) {
264 module_exit *e;
265 char **p;
267 if ((e = dlsym((*mod)->m, "ui_module_exit")) == NULL)
268 warnx("%s", dlerror());
269 else
270 (*e)();
272 dlclose((*mod)->m);
274 if ((*mod)->argv) {
275 for (p = (*mod)->argv; *p; p++)
276 free(*p);
278 free((*mod)->argv);
281 free(*mod);
284 free(modules);
287 static void output(char **s, const int sep, int which)
289 char **p = s;
291 if (p) {
292 for (; *p; p++) {
293 printf("%s", *p);
295 if (*(p+1))
296 printf("%c", sep);
300 printf("%c", which == OUTPUT_DONE ? '\n' : sep);
303 /* Pass the argument to each loaded module. */
304 static int junction(const char *arg)
306 struct passwd *pw;
307 struct stat st;
308 int ret = EXIT_SUCCESS;
309 char **s = NULL;
310 struct module_s **mod;
312 if (usefile) {
313 if ((STAT(arg, &st)) == -1) {
314 warn("%s", arg);
315 return EXIT_FAILURE;
318 errno = 0;
320 if ((pw = getpwuid(st.st_uid)) == NULL) {
321 #ifdef __NetBSD__
322 warnx("%s: no such user", arg);
323 #else
324 if (errno == 0 || errno == ENOENT || errno == EPERM
325 || errno == EBADF || errno == ESRCH)
326 warnx("%s: no such uid %u", arg, st.st_uid);
327 else
328 warn("%s", "getpwuid()");
329 #endif
331 return EXIT_FAILURE;
334 else {
335 errno = 0;
337 if ((pw = getpwnam(arg)) == NULL) {
338 #ifdef __NetBSD__
339 warnx("%s: no such user", arg);
340 #else
341 if (errno == 0 || errno == ENOENT || errno == EPERM
342 || errno == EBADF || errno == ESRCH)
343 warnx("%s: no such user", arg);
344 else
345 warn("%s", "getpwnam()");
346 #endif
348 return EXIT_FAILURE;
352 for (mod = modules; *mod; mod++) {
353 module_exec *m_exec;
355 if ((m_exec = dlsym((*mod)->m, "ui_module_exec")) == NULL) {
356 warnx("%s", dlerror());
357 continue;
360 ret |= (*m_exec) (&s, pw, multichar,
361 TEST_FLAG((*mod)->flags, MODULE_VERBOSE), tf);
363 if (!TEST_FLAG((*mod)->flags, MODULE_CHAINED) ||
364 (TEST_FLAG((*mod)->flags, MODULE_CHAINED) &&
365 TEST_FLAG((*mod)->flags, MODULE_OUTPUT))) {
366 output(s, delimchar,
367 (*(mod+1)) ? OUTPUT_APPEND : OUTPUT_DONE);
369 if (!TEST_FLAG((*mod)->flags, MODULE_CHAINED)) {
370 char **p;
372 for (p = s; *p; p++) {
373 free(*p);
376 free(s);
377 s = NULL;
382 return ret;
385 /* Copy options for each module into it's own argc and argv variables stopping
386 * at -- (getopt(3)). */
387 static int init_module_options(int the_argc, char **the_argv,
388 struct module_s *mod)
390 char tmp[255];
391 module_options *m;
392 module_options_init *o;
393 int old_optind = optind;
394 int opt;
395 int ret = EXIT_SUCCESS;
396 char *optstring = NULL;
397 char *defaults = NULL;
398 int have_an_argument = 0;
400 if ((o = dlsym(mod->m, "ui_module_options_init")) == NULL) {
401 warnx("%s", dlerror());
402 return EXIT_FAILURE;
405 if ((optstring = (*o) (&defaults))) {
406 mod->argv = Realloc(mod->argv, (mod->argc + 2) * sizeof(char *));
407 mod->argv[mod->argc++] = strdup(__progname);
408 mod->argv[mod->argc] = NULL;
410 /* Probably a default module. */
411 if (the_argv == NULL)
412 goto blah;
414 while ((opt = getopt(the_argc, the_argv, optstring)) != -1) {
415 switch (opt) {
416 case '?':
417 warnx("%s: invalid option -- %c\n", mod->name,
418 optopt);
419 return EXIT_FAILURE;
420 default:
421 break;
424 mod->argv = Realloc(mod->argv, (mod->argc + 2) * sizeof(char *));
425 snprintf(tmp, sizeof(tmp), "-%c%s", opt, (optarg) ? optarg : "");
426 mod->argv[mod->argc++] = strdup(tmp);
427 mod->argv[mod->argc] = NULL;
428 have_an_argument = 1;
431 else
432 goto skip_option_stuff;
434 blah:
436 * No options were specified for this module. Set the modules default
437 * options (ui_module_options_init()) if any.
439 if (!have_an_argument && defaults) {
440 mod->argv = Realloc(mod->argv, (mod->argc + 2) * sizeof(char *));
441 snprintf(tmp, sizeof(tmp), "-%s", defaults);
442 mod->argv[mod->argc++] = strdup(tmp);
443 mod->argv[mod->argc] = NULL;
446 old_optind = optind;
447 opterr = optind = optopt = 1;
449 if ((m = dlsym(mod->m, "ui_module_options")) == NULL) {
450 warnx("%s", dlerror());
451 return EXIT_FAILURE;
454 ret |= (*m) (mod->argc, mod->argv);
455 optind = old_optind;
457 skip_option_stuff:
458 return ret;
462 * parseargs.c
464 * This will parse a line used as an argument list for the exec() line of
465 * functions returning a dynamically allocated array of character pointers so
466 * you should free() it afterwards. Both ' and " quoting is supported (with
467 * escapes) for multi-word arguments.
469 * This is my second attempt at it. Works alot better than the first. :)
471 * 2002/10/05
472 * Ben Kibbey <bjk@luxsci.net>
474 * 2004/11/07
475 * Modified to handle argv[0] and argc. (Ben Kibbey <bjk@luxsci.net>)
477 static char **parseargv(char *str, const char *progname, int *me_argc)
479 char **pptr, *s;
480 char arg[LINE_MAX];
481 int idx = 0;
482 int quote = 0;
483 int lastchar = 0;
484 int i;
485 int my_argc = 0;
487 if (!str)
488 return NULL;
490 if (!(pptr = malloc(sizeof(char *))))
491 return NULL;
493 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
494 pptr[idx++] = strdup(progname);
495 my_argc++;
497 for (i = 0, s = str; *s; lastchar = *s++) {
498 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
499 quote = (quote) ? 0 : 1;
500 continue;
503 if (*s == ' ' && !quote) {
504 arg[i] = 0;
505 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
506 pptr[idx++] = strdup(arg);
507 my_argc++;
508 arg[0] = i = 0;
509 continue;
512 if ((i + 1) == sizeof(arg))
513 continue;
515 arg[i++] = *s;
518 arg[i] = 0;
520 if (arg[0]) {
521 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
522 pptr[idx++] = strdup(arg);
523 my_argc++;
526 pptr[idx] = NULL;
527 *me_argc = my_argc;
528 return pptr;
531 static char *get_home_directory()
533 struct passwd *pw;
534 static char dir[PATH_MAX];
536 errno = 0;
538 if ((pw = getpwuid(getuid())) == NULL) {
539 if (errno)
540 warn("getpwuid()");
541 else
542 warnx("getpwuid(): no such uid");
544 return NULL;
547 strncpy(dir, pw->pw_dir, sizeof(dir));
548 dir[sizeof(dir)-1] = 0;
549 return dir;
552 /* Read in a configuration file adding modules to the module array and
553 * checking any module options. */
554 static int parse_rc_file(const char *filename)
556 char line[LINE_MAX], *p;
557 FILE *fp;
558 int old_optind = optind;
559 struct module_s *mod;
561 if ((fp = fopen(filename, "r")) == NULL) {
562 warn("%s", filename);
563 return 1;
566 while ((p = fgets(line, sizeof(line), fp)) != NULL) {
567 char name[PATH_MAX], options[LINE_MAX], tmp[PATH_MAX], *s;
568 int my_argc;
569 char **my_argv, **ap;
570 int lastchar = '\0';
571 int n;
573 while (*p && isspace((unsigned char) *p))
574 p++;
576 if (*p == '#')
577 continue;
579 s = name;
581 if (*p == '>' || *p == '-') {
582 chaining = 1;
584 if (*p == '-')
585 chain_output = 0;
587 p++;
590 while (*p && *p != ' ' && *p != '\t') {
591 if (*p == '\n') {
592 p++;
593 break;
596 *s++ = *p++;
599 *s = '\0';
601 if (!name[0])
602 continue;
604 s = options;
606 while (*p && isspace((unsigned char) *p))
607 p++;
609 lastchar = *p;
611 while (*p) {
612 if (*p == '\n' || (*p == '#' && lastchar != '\\'))
613 break;
615 if (*p == '#' && lastchar == '\\') {
616 lastchar = *--s = *p++;
617 s++;
618 continue;
621 lastchar = *s++ = *p++;
624 *s = '\0';
625 p = name;
627 if (*p == '~') {
628 s = get_home_directory();
629 strncpy(tmp, s, sizeof(tmp));
630 tmp[sizeof(tmp)-1] = 0;
631 p++;
632 safe_strncat(tmp, p, sizeof(tmp));
633 strncpy(name, tmp, sizeof(name));
634 name[sizeof(name)-1] = 0;
637 if (open_module(name, &mod))
638 continue;
640 if ((my_argv = parseargv(options, __progname, &my_argc)) == NULL)
641 continue;
643 optind = 0;
644 n = init_module_options(my_argc, my_argv, mod);
646 for (ap = my_argv; *ap; ap++)
647 free(*ap);
649 free(my_argv);
651 if (n) {
652 fclose(fp);
653 return 2;
656 optind = old_optind;
659 fclose(fp);
660 return 0;
663 int main(int argc, char *argv[])
665 int ret = EXIT_SUCCESS;
666 int opt;
667 char line[LINE_MAX], *s = NULL;
668 int want_help = 0;
670 #ifndef HAVE___PROGNAME
671 __progname = argv[0];
672 #endif
673 delimchar = DEFAULT_DELIMINATING_CHAR;
674 multichar = DEFAULT_MULTI_CHAR;
675 strncpy(tf, DEFAULT_TIMEFORMAT, sizeof(tf));
676 chain_output = 1;
678 while ((opt = getopt(argc, argv, "+x:X:dm:c:hO:F:t:vVLf")) != -1) {
679 struct module_s *mod;
682 * See getopt(3).
684 opterr = 0;
686 switch (opt) {
687 case 'd':
688 if (open_module("passwd.so", &mod) == 0) {
689 if (init_module_options(1, NULL, mod))
690 want_help = 1;
692 else {
693 ret = EXIT_FAILURE;
694 goto cleanup;
697 if (open_module("mail.so", &mod) == 0) {
698 if (init_module_options(1, NULL, mod))
699 want_help = 1;
701 else {
702 ret = EXIT_FAILURE;
703 goto cleanup;
706 if (open_module("login.so", &mod) == 0) {
707 if (init_module_options(1, NULL, mod))
708 want_help = 1;
710 else {
711 ret = EXIT_FAILURE;
712 goto cleanup;
715 break;
716 case 'm':
717 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
718 (optarg[0] == '\\' && strlen(optarg) != 2)) {
719 want_help = 1;
720 break;
723 if ((multichar = escapes(optarg)) == 0)
724 want_help = 1;
726 break;
727 case 'c':
728 if ((ret = parse_rc_file(optarg)) != 0) {
729 if (ret == 2)
730 want_help = 1;
731 else
732 exit(EXIT_FAILURE);
734 break;
735 case 'F':
736 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
737 (optarg[0] == '\\' && strlen(optarg) != 2)) {
738 want_help = 1;
739 break;
742 if ((delimchar = escapes(optarg)) == 0)
743 want_help = 1;
745 break;
746 case 't':
747 strncpy(tf, optarg, sizeof(tf));
748 break;
749 case 'V':
750 printf("%s\n%s\n", PACKAGE_STRING, COPYRIGHT);
751 exit(EXIT_SUCCESS);
752 break;
753 case 'L':
754 followsymlinks = 1;
755 break;
756 case 'f':
757 usefile = 1;
758 break;
759 case 'v':
760 verbose++;
761 break;
762 case 'X':
763 chain_output = 0;
764 case 'x':
765 chaining = 1;
766 case 'O':
767 if (open_module(optarg, &mod)) {
768 ret = EXIT_FAILURE;
769 goto cleanup;
772 if (init_module_options(argc, argv, mod))
773 want_help = 1;
776 * For modules which have no options at all (to keep getopt
777 * from interpreting the rest as arguments.
779 if (optind < argc) {
780 if (strcmp(argv[optind], "--") == 0)
781 optind++;
784 break;
785 case 'h':
786 default:
787 want_help = 1;
788 break;
792 /* The last module cannot be chained (syntax). */
793 if (!module_total || TEST_FLAG(modules[module_total-1]->flags, MODULE_CHAINED))
794 want_help = 1;
796 /* Cycle through the modules and output their help text. */
797 if (want_help) {
798 usage_header();
799 struct module_s **mod;
801 for (mod = modules; mod && *mod; mod++) {
802 module_help *m_help;
804 if (TEST_FLAG((*mod)->flags, MODULE_DUP))
805 continue;
807 if ((m_help = dlsym((*mod)->m, "ui_module_help")) == NULL) {
808 warnx("%s", dlerror());
809 continue;
812 printf("%s\n", (*mod)->name);
813 (*m_help) ();
816 usage();
817 cleanup_modules();
818 exit(EXIT_FAILURE);
821 if (argc == optind || strcmp(argv[optind], "-") == 0) {
822 while ((s = fgets(line, sizeof(line), stdin)) != NULL) {
823 if (s[strlen(s) - 1] == '\n')
824 s[strlen(s) - 1] = '\0';
826 ret |= junction(s);
829 else {
830 for (; optind < argc; optind++)
831 ret |= junction(argv[optind]);
834 cleanup:
835 cleanup_modules();
836 exit(ret);