2807fd8c2ec8be36d76a5ec9bd4a7dbf5c95622e
[userinfo.git] / src / ui.c
blob2807fd8c2ec8be36d76a5ec9bd4a7dbf5c95622e
1 /*
2 Copyright (C) 2001-2013 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;
76 /*
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));
183 if ((p = strrchr(s, '/')) != NULL)
184 p++;
185 else {
186 strncpy(s, filename, sizeof(s));
187 p = s;
190 for (mods = modules; mods && *mods; mods++) {
191 if (strcmp(p, (*mods)->name) == 0) {
192 dup = 1;
193 break;
197 if ((m = dlopen(filename, RTLD_NOW)) == NULL) {
198 warnx("%s", dlerror());
199 chaining = 0;
200 chain_output = 1;
201 return 1;
204 last = modules ? modules[module_total-1] : NULL;
205 modules = Realloc(modules, (module_total+2) * sizeof(struct module_s *));
206 mod = calloc(1, sizeof(struct module_s));
207 modules[module_total++] = mod;
208 modules[module_total] = NULL;
209 mod->m = m;
210 strncpy(mod->name, p, sizeof(mod->name));
212 if ((init = dlsym(mod->m, "ui_module_init")) == NULL)
213 warnx("%s", dlerror());
214 else
215 (*init) (&chainable);
217 if (chainable)
218 SET_FLAG(mod->flags, MODULE_CHAINABLE);
220 if (last && TEST_FLAG(last->flags, MODULE_CHAINED) &&
221 !TEST_FLAG(mod->flags, MODULE_CHAINABLE)) {
222 warnx("%s: this module is not chainable", mod->name);
223 module_total--;
224 modules[module_total] = NULL;
225 free(mod);
226 return 1;
229 /* Module chaining. See junction() for more info. */
230 if (chaining)
231 SET_FLAG(mod->flags, MODULE_CHAINED);
233 if (chain_output)
234 SET_FLAG(mod->flags, MODULE_OUTPUT);
236 if (verbose)
237 SET_FLAG(mod->flags, MODULE_VERBOSE);
239 if (dup) {
240 SET_FLAG(mod->flags, MODULE_DUP);
241 warnx("%s: a module by this name is already loaded", p);
244 chaining = 0;
245 chain_output = 1;
246 verbose = (verbose < 2) ? 0 : 2;
247 *ptr = mod;
248 return 0;
251 /* This just free's up the array of modules. The modules should clean up after
252 * themselves via the ui_module_exit() function. */
253 static void cleanup_modules()
255 struct module_s **mod;
257 if (!modules)
258 return;
260 for (mod = modules; *mod; mod++) {
261 module_exit *e;
262 char **p;
264 if ((e = dlsym((*mod)->m, "ui_module_exit")) == NULL)
265 warnx("%s", dlerror());
266 else
267 (*e)();
269 dlclose((*mod)->m);
271 if ((*mod)->argv) {
272 for (p = (*mod)->argv; *p; p++)
273 free(*p);
275 free((*mod)->argv);
278 free(*mod);
281 free(modules);
284 static void output(char **s, const int sep, int which)
286 char **p = s;
288 if (p) {
289 for (; *p; p++) {
290 printf("%s", *p);
292 if (*(p+1))
293 printf("%c", sep);
297 printf("%c", which == OUTPUT_DONE ? '\n' : sep);
300 /* Pass the argument to each loaded module. */
301 static int junction(const char *arg)
303 struct passwd *pw;
304 struct stat st;
305 int ret = EXIT_SUCCESS;
306 char **s = NULL;
307 struct module_s **mod;
309 if (usefile) {
310 if ((STAT(arg, &st)) == -1) {
311 warn("%s", arg);
312 return EXIT_FAILURE;
315 errno = 0;
317 if ((pw = getpwuid(st.st_uid)) == NULL) {
318 #ifdef __NetBSD__
319 warnx("%s: no such user", arg);
320 #else
321 if (errno == 0 || errno == ENOENT || errno == EPERM
322 || errno == EBADF || errno == ESRCH)
323 warnx("%s: no such uid %u", arg, st.st_uid);
324 else
325 warn("%s", "getpwuid()");
326 #endif
328 return EXIT_FAILURE;
331 else {
332 errno = 0;
334 if ((pw = getpwnam(arg)) == NULL) {
335 #ifdef __NetBSD__
336 warnx("%s: no such user", arg);
337 #else
338 if (errno == 0 || errno == ENOENT || errno == EPERM
339 || errno == EBADF || errno == ESRCH)
340 warnx("%s: no such user", arg);
341 else
342 warn("%s", "getpwnam()");
343 #endif
345 return EXIT_FAILURE;
349 for (mod = modules; *mod; mod++) {
350 module_exec *m_exec;
352 if ((m_exec = dlsym((*mod)->m, "ui_module_exec")) == NULL) {
353 warnx("%s", dlerror());
354 continue;
357 ret |= (*m_exec) (&s, pw, multichar,
358 TEST_FLAG((*mod)->flags, MODULE_VERBOSE), tf);
360 if (!TEST_FLAG((*mod)->flags, MODULE_CHAINED) ||
361 (TEST_FLAG((*mod)->flags, MODULE_CHAINED) &&
362 TEST_FLAG((*mod)->flags, MODULE_OUTPUT))) {
363 output(s, delimchar,
364 (*(mod+1)) ? OUTPUT_APPEND : OUTPUT_DONE);
366 if (!TEST_FLAG((*mod)->flags, MODULE_CHAINED)) {
367 char **p;
369 for (p = s; *p; p++) {
370 free(*p);
373 free(s);
374 s = NULL;
379 return ret;
382 /* Copy options for each module into it's own argc and argv variables stopping
383 * at -- (getopt(3)). */
384 static int init_module_options(int the_argc, char **the_argv,
385 struct module_s *mod)
387 char tmp[255];
388 module_options *m;
389 module_options_init *o;
390 int old_optind = optind;
391 int opt;
392 int ret = EXIT_SUCCESS;
393 char *optstring = NULL;
394 char *defaults = NULL;
395 int have_an_argument = 0;
397 if ((o = dlsym(mod->m, "ui_module_options_init")) == NULL) {
398 warnx("%s", dlerror());
399 return EXIT_FAILURE;
402 if ((optstring = (*o) (&defaults))) {
403 mod->argv = Realloc(mod->argv, (mod->argc + 2) * sizeof(char *));
404 mod->argv[mod->argc++] = strdup(__progname);
405 mod->argv[mod->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: invalid option -- %c\n", mod->name,
415 optopt);
416 return EXIT_FAILURE;
417 default:
418 break;
421 mod->argv = Realloc(mod->argv, (mod->argc + 2) * sizeof(char *));
422 snprintf(tmp, sizeof(tmp), "-%c%s", opt, (optarg) ? optarg : "");
423 mod->argv[mod->argc++] = strdup(tmp);
424 mod->argv[mod->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 (ui_module_options_init()) if any.
436 if (!have_an_argument && defaults) {
437 mod->argv = Realloc(mod->argv, (mod->argc + 2) * sizeof(char *));
438 snprintf(tmp, sizeof(tmp), "-%s", defaults);
439 mod->argv[mod->argc++] = strdup(tmp);
440 mod->argv[mod->argc] = NULL;
443 old_optind = optind;
444 opterr = optind = optopt = 1;
446 if ((m = dlsym(mod->m, "ui_module_options")) == NULL) {
447 warnx("%s", dlerror());
448 return EXIT_FAILURE;
451 ret |= (*m) (mod->argc, mod->argv);
452 optind = old_optind;
454 skip_option_stuff:
455 return ret;
459 * parseargs.c
461 * This will parse a line used as an argument list for the exec() line of
462 * functions returning a dynamically allocated array of character pointers so
463 * you should free() it afterwards. Both ' and " quoting is supported (with
464 * escapes) for multi-word arguments.
466 * This is my second attempt at it. Works alot better than the first. :)
468 * 2002/10/05
469 * Ben Kibbey <bjk@luxsci.net>
471 * 2004/11/07
472 * Modified to handle argv[0] and argc. (Ben Kibbey <bjk@luxsci.net>)
474 static char **parseargv(char *str, const char *progname, int *me_argc)
476 char **pptr, *s;
477 char arg[LINE_MAX];
478 int idx = 0;
479 int quote = 0;
480 int lastchar = 0;
481 int i;
482 int my_argc = 0;
484 if (!str)
485 return NULL;
487 if (!(pptr = malloc(sizeof(char *))))
488 return NULL;
490 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
491 pptr[idx++] = strdup(progname);
492 my_argc++;
494 for (i = 0, s = str; *s; lastchar = *s++) {
495 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
496 quote = (quote) ? 0 : 1;
497 continue;
500 if (*s == ' ' && !quote) {
501 arg[i] = 0;
502 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
503 pptr[idx++] = strdup(arg);
504 my_argc++;
505 arg[0] = i = 0;
506 continue;
509 if ((i + 1) == sizeof(arg))
510 continue;
512 arg[i++] = *s;
515 arg[i] = 0;
517 if (arg[0]) {
518 pptr = Realloc(pptr, (idx + 2) * sizeof(char *));
519 pptr[idx++] = strdup(arg);
520 my_argc++;
523 pptr[idx] = NULL;
524 *me_argc = my_argc;
525 return pptr;
528 static char *get_home_directory()
530 struct passwd *pw;
531 static char dir[PATH_MAX];
533 errno = 0;
535 if ((pw = getpwuid(getuid())) == NULL) {
536 if (errno)
537 warn("getpwuid()");
538 else
539 warnx("getpwuid(): no such uid");
541 return NULL;
544 strncpy(dir, pw->pw_dir, sizeof(dir));
545 return dir;
548 /* Read in a configuration file adding modules to the module array and
549 * checking any module options. */
550 static int parse_rc_file(const char *filename)
552 char line[LINE_MAX], *p;
553 FILE *fp;
554 int old_optind = optind;
555 struct module_s *mod;
557 if ((fp = fopen(filename, "r")) == NULL) {
558 warn("%s", filename);
559 return 1;
562 while ((p = fgets(line, sizeof(line), fp)) != NULL) {
563 char name[PATH_MAX], options[LINE_MAX], tmp[PATH_MAX], *s;
564 int my_argc;
565 char **my_argv, **ap;
566 int lastchar = '\0';
567 int n;
569 while (*p && isspace((unsigned char) *p))
570 p++;
572 if (*p == '#')
573 continue;
575 s = name;
577 if (*p == '>' || *p == '-') {
578 chaining = 1;
580 if (*p == '-')
581 chain_output = 0;
583 p++;
586 while (*p && *p != ' ' && *p != '\t') {
587 if (*p == '\n') {
588 p++;
589 break;
592 *s++ = *p++;
595 *s = '\0';
597 if (!name[0])
598 continue;
600 s = options;
602 while (*p && isspace((unsigned char) *p))
603 p++;
605 lastchar = *p;
607 while (*p) {
608 if (*p == '\n' || (*p == '#' && lastchar != '\\'))
609 break;
611 if (*p == '#' && lastchar == '\\') {
612 lastchar = *--s = *p++;
613 s++;
614 continue;
617 lastchar = *s++ = *p++;
620 *s = '\0';
621 p = name;
623 if (*p == '~') {
624 s = get_home_directory();
625 strncpy(tmp, s, sizeof(tmp));
626 p++;
627 safe_strncat(tmp, p, sizeof(tmp));
628 strncpy(name, tmp, sizeof(name));
631 if (open_module(name, &mod))
632 continue;
634 if ((my_argv = parseargv(options, __progname, &my_argc)) == NULL)
635 continue;
637 optind = 0;
638 n = init_module_options(my_argc, my_argv, mod);
640 for (ap = my_argv; *ap; ap++)
641 free(*ap);
643 free(my_argv);
645 if (n) {
646 fclose(fp);
647 return 2;
650 optind = old_optind;
653 fclose(fp);
654 return 0;
657 int main(int argc, char *argv[])
659 int ret = EXIT_SUCCESS;
660 int opt;
661 char line[LINE_MAX], *s = NULL;
662 int want_help = 0;
664 #ifndef HAVE___PROGNAME
665 __progname = argv[0];
666 #endif
667 delimchar = DEFAULT_DELIMINATING_CHAR;
668 multichar = DEFAULT_MULTI_CHAR;
669 strncpy(tf, DEFAULT_TIMEFORMAT, sizeof(tf));
670 chain_output = 1;
672 while ((opt = getopt(argc, argv, "+x:X:dm:c:hO:F:t:vVLf")) != -1) {
673 struct module_s *mod;
676 * See getopt(3).
678 opterr = 0;
680 switch (opt) {
681 case 'd':
682 if (open_module("passwd.so", &mod) == 0) {
683 if (init_module_options(1, NULL, mod))
684 want_help = 1;
686 else {
687 ret = EXIT_FAILURE;
688 goto cleanup;
691 if (open_module("mail.so", &mod) == 0) {
692 if (init_module_options(1, NULL, mod))
693 want_help = 1;
695 else {
696 ret = EXIT_FAILURE;
697 goto cleanup;
700 if (open_module("login.so", &mod) == 0) {
701 if (init_module_options(1, NULL, mod))
702 want_help = 1;
704 else {
705 ret = EXIT_FAILURE;
706 goto cleanup;
709 break;
710 case 'm':
711 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
712 (optarg[0] == '\\' && strlen(optarg) != 2)) {
713 want_help = 1;
714 break;
717 if ((multichar = escapes(optarg)) == 0)
718 want_help = 1;
720 break;
721 case 'c':
722 if ((ret = parse_rc_file(optarg)) != 0) {
723 if (ret == 2)
724 want_help = 1;
725 else
726 exit(EXIT_FAILURE);
728 break;
729 case 'F':
730 if ((optarg[0] != '\\' && strlen(optarg) > 1) ||
731 (optarg[0] == '\\' && strlen(optarg) != 2)) {
732 want_help = 1;
733 break;
736 if ((delimchar = escapes(optarg)) == 0)
737 want_help = 1;
739 break;
740 case 't':
741 strncpy(tf, optarg, sizeof(tf));
742 break;
743 case 'V':
744 printf("%s\n%s\n", PACKAGE_STRING, COPYRIGHT);
745 exit(EXIT_SUCCESS);
746 break;
747 case 'L':
748 followsymlinks = 1;
749 break;
750 case 'f':
751 usefile = 1;
752 break;
753 case 'v':
754 verbose++;
755 break;
756 case 'X':
757 chain_output = 0;
758 case 'x':
759 chaining = 1;
760 case 'O':
761 if (open_module(optarg, &mod)) {
762 ret = EXIT_FAILURE;
763 goto cleanup;
766 if (init_module_options(argc, argv, mod))
767 want_help = 1;
770 * For modules which have no options at all (to keep getopt
771 * from interpreting the rest as arguments.
773 if (optind < argc) {
774 if (strcmp(argv[optind], "--") == 0)
775 optind++;
778 break;
779 case 'h':
780 default:
781 want_help = 1;
782 break;
786 /* The last module cannot be chained (syntax). */
787 if (!module_total || TEST_FLAG(modules[module_total-1]->flags, MODULE_CHAINED))
788 want_help = 1;
790 /* Cycle through the modules and output their help text. */
791 if (want_help) {
792 usage_header();
793 struct module_s **mod;
795 for (mod = modules; mod && *mod; mod++) {
796 module_help *m_help;
798 if (TEST_FLAG((*mod)->flags, MODULE_DUP))
799 continue;
801 if ((m_help = dlsym((*mod)->m, "ui_module_help")) == NULL) {
802 warnx("%s", dlerror());
803 continue;
806 printf("%s\n", (*mod)->name);
807 (*m_help) ();
810 usage();
811 cleanup_modules();
812 exit(EXIT_FAILURE);
815 if (argc == optind || strcmp(argv[optind], "-") == 0) {
816 while ((s = fgets(line, sizeof(line), stdin)) != NULL) {
817 if (s[strlen(s) - 1] == '\n')
818 s[strlen(s) - 1] = '\0';
820 ret |= junction(s);
823 else {
824 for (; optind < argc; optind++)
825 ret |= junction(argv[optind]);
828 cleanup:
829 cleanup_modules();
830 exit(ret);