1 /* $Id: ui.c,v 2.1 2005-07-24 13:50:57 bjk Exp $ */
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
22 #include <sys/types.h>
39 static void *Realloc(void *p
, size_t size
)
43 if ((p2
= realloc(p
, size
)) == NULL
)
44 err(EXIT_FAILURE
, "%s", "realloc()");
49 /* This may be used in modules to keep a consistant time format with other
51 char *stamp(time_t epoch
, const char *format
)
53 static char buf
[TIMEBUFSIZE
];
56 t
= localtime(&epoch
);
57 strftime(buf
, sizeof(buf
), format
, t
);
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
)
69 if ((s
= realloc(s
, (idx
+ 2) * sizeof(char *))) == NULL
) {
70 warn("%s", "realloc()");
74 s
[idx
++] = strdup(str
);
81 /* This is for the field separators (-F and -m). */
82 static int escapes(const char *str
)
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
);
131 /* Help text. Module help text is displayed before this. */
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
);
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
158 static int open_module(char *filename
, int *index
)
162 char *p
, s
[FILENAME_MAX
];
167 strncpy(s
, filename
, sizeof(s
));
168 s
[strlen(s
) - 3] = '\0';
170 if ((p
= strrchr(s
, '/')) != NULL
)
173 strncpy(s
, filename
, sizeof(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
);
189 if ((m
= dlopen(filename
, RTLD_LAZY
)) == NULL
) {
190 warnx("%s", dlerror());
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());
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
);
217 /* Module chaining. See junction() for more info. */
219 modules
[*index
].chained
= 1;
222 modules
[*index
].output
= 1;
224 modules
[*index
].output
= 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()
239 for (i
= 0; i
< module_index
; i
++) {
243 snprintf(tmp
, sizeof(tmp
), "%s_exit", modules
[i
].name
);
245 if ((e
= dlsym(modules
[i
].m
, tmp
)) == NULL
)
246 warnx("%s", dlerror());
250 dlclose(modules
[i
].m
);
257 static void output(char **s
, const int sep
, int which
)
262 for (i
= 0; s
[i
]; i
++) {
270 if (which
== OUTPUT_DONE
)
272 else if (which
== OUTPUT_APPEND
)
278 static void free_strings(char **s
)
282 for (n
= 0; s
[n
]; n
++) {
290 /* Pass the argument to each loaded module. */
291 static int junction(const char *arg
)
296 int ret
= EXIT_SUCCESS
;
299 if ((STAT(arg
, &st
)) == -1) {
306 if ((pw
= getpwuid(st
.st_uid
)) == NULL
) {
308 warnx("%s: no such user", arg
);
310 if (errno
== 0 || errno
== ENOENT
|| errno
== EPERM
311 || errno
== EBADF
|| errno
== ESRCH
)
312 warnx("%s: no such uid %u", arg
, st
.st_uid
);
314 warn("%s", "getpwuid()");
323 if ((pw
= getpwnam(arg
)) == NULL
) {
325 warnx("%s: no such user", arg
);
327 if (errno
== 0 || errno
== ENOENT
|| errno
== EPERM
328 || errno
== EBADF
|| errno
== ESRCH
)
329 warnx("%s: no such user", arg
);
331 warn("%s", "getpwnam()");
338 for (i
= 0; i
< module_index
; i
++) {
343 snprintf(tmp
, sizeof(tmp
), "%s_exec", modules
[i
].name
);
345 if ((m_exec
= dlsym(modules
[i
].m
, tmp
)) == NULL
) {
346 warnx("%s", dlerror());
350 ret
|= (*m_exec
) (&s
, pw
, multichar
, verbose
, tf
);
352 if (modules
[i
].chained
== 0 || (modules
[i
].chained
&&
353 modules
[i
].output
)) {
355 ((i
+ 1) < module_index
) ? OUTPUT_APPEND
: OUTPUT_DONE
);
357 if (modules
[i
].chained
== 0)
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());
369 ret
|= (*m_exec
) (&s
, pw
, multichar
, verbose
, tf
);
371 ((i
+ 1) < module_index
) ? OUTPUT_APPEND
: OUTPUT_DONE
);
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
)
385 module_options_init
*o
;
386 int old_optind
= optind
;
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());
402 if ((optstring
= (*o
) (&defaults
))) {
403 argv
= Realloc(argv
, (argc
+ 2) * sizeof(char *));
404 argv
[argc
++] = strdup(__progname
);
407 /* Probably a default module. */
408 if (the_argv
== NULL
)
411 while ((opt
= getopt(the_argc
, the_argv
, optstring
)) != -1) {
414 warnx("%s.so: invalid option -- %c\n", modules
[index
].name
,
421 argv
= Realloc(argv
, (argc
+ 2) * sizeof(char *));
422 snprintf(tmp
, sizeof(tmp
), "-%c%s", opt
, (optarg
) ? optarg
: "");
423 argv
[argc
++] = strdup(tmp
);
425 have_an_argument
= 1;
429 goto skip_option_stuff
;
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
);
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());
453 ret
|= (*m
) (argc
, argv
);
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. :)
471 * Ben Kibbey <bjk@arbornet.org>
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
)
489 if (!(pptr
= malloc(sizeof(char *))))
492 pptr
= realloc(pptr
, (index
+ 2) * sizeof(char *));
493 pptr
[index
++] = strdup(progname
);
496 for (i
= 0, s
= str
; *s
; lastchar
= *s
++) {
497 if ((*s
== '\"' || *s
== '\'') && lastchar
!= '\\') {
498 quote
= (quote
) ? 0 : 1;
502 if (*s
== ' ' && !quote
) {
504 pptr
= realloc(pptr
, (index
+ 2) * sizeof(char *));
505 pptr
[index
++] = strdup(arg
);
511 if ((i
+ 1) == sizeof(arg
))
520 pptr
= realloc(pptr
, (index
+ 2) * sizeof(char *));
521 pptr
[index
++] = strdup(arg
);
530 static char *get_home_directory()
533 static char dir
[FILENAME_MAX
];
537 if ((pw
= getpwuid(getuid())) == NULL
) {
541 warnx("getpwuid(): no such uid");
546 strncpy(dir
, pw
->pw_dir
, sizeof(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
;
557 int old_optind
= optind
;
559 if ((fp
= fopen(filename
, "r")) == NULL
) {
560 warn("%s", filename
);
564 while ((p
= fgets(line
, sizeof(line
), fp
)) != NULL
) {
565 char name
[FILENAME_MAX
], options
[LINE_MAX
], tmp
[LINE_MAX
], *s
;
570 while (*p
&& isspace((unsigned char) *p
))
578 if (*p
== '>' || *p
== '-') {
587 while (*p
&& *p
!= ' ' && *p
!= '\t') {
603 while (*p
&& isspace((unsigned char) *p
))
609 if (*p
== '\n' || (*p
== '#' && lastchar
!= '\\'))
612 if (*p
== '#' && lastchar
== '\\') {
613 lastchar
= *--s
= *p
++;
618 lastchar
= *s
++ = *p
++;
625 s
= get_home_directory();
626 strncpy(tmp
, s
, sizeof(tmp
));
628 strncat(tmp
, p
, sizeof(tmp
));
629 strncpy(name
, tmp
, sizeof(name
));
632 if (open_module(name
, &index
))
635 if ((my_argv
= parseargv(options
, __progname
, &my_argc
)) == NULL
)
640 if (init_module_options(my_argc
, my_argv
, index
)) {
652 int main(int argc
, char *argv
[])
655 int ret
= EXIT_SUCCESS
;
657 char line
[LINE_MAX
], *s
= NULL
;
661 #ifndef HAVE___PROGNAME
662 __progname
= argv
[0];
664 delimchar
= DEFAULT_DELIMINATING_CHAR
;
665 multichar
= DEFAULT_MULTI_CHAR
;
666 strncpy(tf
, DEFAULT_TIMEFORMAT
, sizeof(tf
));
669 while ((opt
= getopt(argc
, argv
, "+x:X:dm:c:hO:F:t:vVlf")) != -1) {
679 if (open_module("passwd.so", &i
) == 0) {
680 if (init_module_options(1, NULL
, i
))
688 if (open_module("mail.so", &i
) == 0) {
689 if (init_module_options(1, NULL
, i
))
697 if (open_module("login.so", &i
) == 0) {
698 if (init_module_options(1, NULL
, i
))
708 if ((optarg
[0] != '\\' && strlen(optarg
) > 1) ||
709 (optarg
[0] == '\\' && strlen(optarg
) != 2)) {
714 if ((multichar
= escapes(optarg
)) == 0)
719 if ((ret
= parse_rc_file(optarg
)) != 0) {
727 if ((optarg
[0] != '\\' && strlen(optarg
) > 1) ||
728 (optarg
[0] == '\\' && strlen(optarg
) != 2)) {
733 if ((delimchar
= escapes(optarg
)) == 0)
738 strncpy(tf
, optarg
, sizeof(tf
));
741 printf("%s\n%s\n", PACKAGE_STRING
, COPYRIGHT
);
758 if (open_module(optarg
, &i
)) {
763 if (init_module_options(argc
, argv
, i
))
774 /* The last module cannot be chained (syntax). */
775 if (!module_index
|| modules
[module_index
- 1].chained
)
778 /* Cycle through the modules and output their help text. */
782 for (i
= 0; i
< module_index
; i
++) {
785 snprintf(tmp
, sizeof(tmp
), "%s_help", modules
[i
].name
);
787 if ((m_help
= dlsym(modules
[i
].m
, tmp
)) == NULL
) {
788 warnx("%s", dlerror());
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';
809 for (; optind
< argc
; optind
++)
810 ret
|= junction(argv
[optind
]);