/* Copyright (C) 2001-2006 Ben Kibbey This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif #ifdef HAVE_GETOPT_H #include #endif #ifndef HAVE_ERR_H #include "err.c" #endif #include "ui.h" static void *Realloc(void *p, size_t size) { void *p2; if ((p2 = realloc(p, size)) == NULL) err(EXIT_FAILURE, "%s", "realloc()"); return p2; } /* This may be used in modules to keep a consistant time format with other * modules. */ char *stamp(time_t epoch, const char *format) { static char buf[TIMEBUFSIZE]; struct tm *t; t = localtime(&epoch); strftime(buf, sizeof(buf), format, t); return buf; } /* * This may be used in modules to add a string to the buffer (ui_module_exec()). */ void add_string(char ***buf, const char *str) { char **s; int i = 0; if (*buf) { for (s = *buf; *s; s++) i++; } s = *buf; s = Realloc(s, (i + 2) * sizeof(char *)); s[i++] = strdup(str); s[i] = NULL; *buf = s; return; } /* This is for the field separators (-F and -m). */ static int escapes(const char *str) { int c = 0; if (str[0] != '\\') return str[0]; switch (*++str) { case 't': c = '\t'; break; case 'n': c = '\n'; break; case '\\': c = '\\'; break; case 'v': c = '\v'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'r': c = '\r'; break; case '\'': c = '\''; break; default: c = 0; break; } return c; } /* Help text. Module help text is displayed after this. */ static void usage_header() { printf("Usage: %s [-vhVL] [-c ] [-t fmt] [-m c] [-F c] [-d]\n" "\t[[-xX] -O [options] [-- [-xX] -O [...]]]\n" "\t[- | username | -f filename] [...]\n\n", __progname); return; } /* Help text. Module help text is displayed before this. */ static void usage() { printf(" -d\tLoad the default modules (passwd.so, mail.so, and login.so).\n"); printf(" -c\tRead a configuration file. Can be used more than once.\n"); printf(" -O\tLoad a module. Can be used more than once.\n"); printf(" -x\tChain module1's output to module2's input.\n"); printf(" -X\tDon't output module1's info, only chain it.\n"); printf(" -F c\tSeparate output with the specified character " "('%c').\n", delimchar); printf(" -m c\tSeparate multi-string values with the specified " "character ('%c').\n", multichar); printf(" -t tf\tstrftime(3) time format ('%s').\n", DEFAULT_TIMEFORMAT); printf(" -f\tUsers are the owners of the specified files.\n"); printf(" -L\tFollow symbolic links.\n"); printf(" -v\tVerbose output when possible (twice for all modules).\n"); printf(" -h\tThis help text.\n"); printf(" -V\tVersion information.\n\n"); printf("Output key: %s=unknown/error, %s=none, %s=yes/on, " "%s=no/off\n", UNKNOWN, NONE, ON, OFF); return; } /* * Add a module to the array of loaded modules. The index argument is the * current item number being added stored in an integer. The module is also * initialized here with ui_module_init(). */ static int open_module(char *filename, int *idx) { void *m; module_init *init; char *p, s[FILENAME_MAX]; int i; int chainable = 0; strncpy(s, filename, sizeof(s)); if ((p = strrchr(s, '/')) != NULL) p++; else { strncpy(s, filename, sizeof(s)); p = s; } for (i = 0; i < module_index; i++) { if (strcmp(p, modules[i].name) == 0) { if (TEST_FLAG(modules[i].flags, MODULE_DUP)) break; SET_FLAG(modules[i].flags, MODULE_DUP); warnx("%s: a module by this name is already loaded", p); } } if ((m = dlopen(filename, RTLD_NOW)) == NULL) { warnx("%s", dlerror()); chaining = 0; chain_output = 1; return 1; } modules = Realloc(modules, (module_index + 2) * sizeof(struct module)); modules[module_index].m = m; strncpy(modules[module_index].name, p, sizeof(modules[module_index].name)); *idx = module_index++; if ((init = dlsym(modules[*idx].m, "ui_module_init")) == NULL) warnx("%s", dlerror()); else (*init) (&chainable); if (chainable) SET_FLAG(modules[*idx].flags, MODULE_CHAINABLE); if (*idx - 1 >= 0 && TEST_FLAG(modules[*idx - 1].flags, MODULE_CHAINED) && !TEST_FLAG(modules[*idx].flags, MODULE_CHAINABLE)) { warnx("%s: this module is not chainable", modules[*idx].name); return 1; } /* Module chaining. See junction() for more info. */ if (chaining) SET_FLAG(modules[*idx].flags, MODULE_CHAINED); if (chain_output) SET_FLAG(modules[*idx].flags, MODULE_OUTPUT); if (verbose) SET_FLAG(modules[*idx].flags, MODULE_VERBOSE); chaining = 0; chain_output = 1; verbose = (verbose < 2) ? 0 : 2; return 0; } /* This just free's up the array of modules. The modules should clean up after * themselves via the ui_module_exit() function. */ static void cleanup_modules() { int i; for (i = 0; i < module_index; i++) { module_exit *e; if ((e = dlsym(modules[i].m, "ui_module_exit")) == NULL) warnx("%s", dlerror()); else (*e) (); dlclose(modules[i].m); } free(modules); return; } static void output(char **s, const int sep, int which) { int i; if (s) { for (i = 0; s[i]; i++) { printf("%s", s[i]); if (s[i + 1]) printf("%c", sep); } } if (which == OUTPUT_DONE) printf("\n"); else if (which == OUTPUT_APPEND) printf("%c", sep); return; } /* Pass the argument to each loaded module. */ static int junction(const char *arg) { int i; struct passwd *pw; struct stat st; int ret = EXIT_SUCCESS; char **s = NULL; if (usefile) { if ((STAT(arg, &st)) == -1) { warn("%s", arg); return EXIT_FAILURE; } errno = 0; if ((pw = getpwuid(st.st_uid)) == NULL) { #ifdef __NetBSD__ warnx("%s: no such user", arg); #else if (errno == 0 || errno == ENOENT || errno == EPERM || errno == EBADF || errno == ESRCH) warnx("%s: no such uid %u", arg, st.st_uid); else warn("%s", "getpwuid()"); #endif return EXIT_FAILURE; } } else { errno = 0; if ((pw = getpwnam(arg)) == NULL) { #ifdef __NetBSD__ warnx("%s: no such user", arg); #else if (errno == 0 || errno == ENOENT || errno == EPERM || errno == EBADF || errno == ESRCH) warnx("%s: no such user", arg); else warn("%s", "getpwnam()"); #endif return EXIT_FAILURE; } } for (i = 0; i < module_index; i++) { module_exec *m_exec; char **p; if ((m_exec = dlsym(modules[i].m, "ui_module_exec")) == NULL) { warnx("%s", dlerror()); continue; } ret |= (*m_exec) (&s, pw, multichar, TEST_FLAG(modules[i].flags, MODULE_VERBOSE), tf); if (!TEST_FLAG(modules[i].flags, MODULE_CHAINED) || (TEST_FLAG(modules[i].flags, MODULE_CHAINED) && TEST_FLAG(modules[i].flags, MODULE_OUTPUT))) { output(s, delimchar, ((i + 1) < module_index) ? OUTPUT_APPEND : OUTPUT_DONE); if (!TEST_FLAG(modules[i].flags, MODULE_CHAINED)) { for (p = s; *p; p++) { free(*p); } free(s); s = NULL; } } } return ret; } /* Copy options for each module into it's own argc and argv variables stopping * at -- (getopt(3)). */ static int init_module_options(int the_argc, char **the_argv, struct module mod) { char tmp[255]; module_options *m; module_options_init *o; int old_optind = optind; int argc = 0; char **argv = NULL; int opt; int ret = EXIT_SUCCESS; char *optstring = NULL; char *defaults = NULL; int have_an_argument = 0; if ((o = dlsym(mod.m, "ui_module_options_init")) == NULL) { warnx("%s", dlerror()); return EXIT_FAILURE; } if ((optstring = (*o) (&defaults))) { argv = Realloc(argv, (argc + 2) * sizeof(char *)); argv[argc++] = strdup(__progname); argv[argc] = NULL; /* Probably a default module. */ if (the_argv == NULL) goto blah; while ((opt = getopt(the_argc, the_argv, optstring)) != -1) { switch (opt) { case '?': warnx("%s: invalid option -- %c\n", mod.name, optopt); return EXIT_FAILURE; default: break; } argv = Realloc(argv, (argc + 2) * sizeof(char *)); snprintf(tmp, sizeof(tmp), "-%c%s", opt, (optarg) ? optarg : ""); argv[argc++] = strdup(tmp); argv[argc] = NULL; have_an_argument = 1; } } else goto skip_option_stuff; blah: /* * No options were specified for this module. Set the modules default * options (ui_module_options_init()) if any. */ if (!have_an_argument && defaults) { argv = Realloc(argv, (argc + 2) * sizeof(char *)); snprintf(tmp, sizeof(tmp), "-%s", defaults); argv[argc++] = strdup(tmp); argv[argc] = NULL; } old_optind = optind; opterr = optind = optopt = 1; if ((m = dlsym(mod.m, "ui_module_options")) == NULL) { warnx("%s", dlerror()); return EXIT_FAILURE; } ret |= (*m) (argc, argv); optind = old_optind; skip_option_stuff: return ret; } /* * parseargs.c * * This will parse a line used as an argument list for the exec() line of * functions returning a dynamically allocated array of character pointers so * you should free() it afterwards. Both ' and " quoting is supported (with * escapes) for multi-word arguments. * * This is my second attempt at it. Works alot better than the first. :) * * 2002/10/05 * Ben Kibbey * * 2004/11/07 * Modified to handle argv[0] and argc. (Ben Kibbey ) */ static char **parseargv(char *str, const char *progname, int *me_argc) { char **pptr, *s; char arg[255]; int idx = 0; int quote = 0; int lastchar = 0; int i; int my_argc = 0; if (!str) return NULL; if (!(pptr = malloc(sizeof(char *)))) return NULL; pptr = Realloc(pptr, (idx + 2) * sizeof(char *)); pptr[idx++] = strdup(progname); my_argc++; for (i = 0, s = str; *s; lastchar = *s++) { if ((*s == '\"' || *s == '\'') && lastchar != '\\') { quote = (quote) ? 0 : 1; continue; } if (*s == ' ' && !quote) { arg[i] = 0; pptr = Realloc(pptr, (idx + 2) * sizeof(char *)); pptr[idx++] = strdup(arg); my_argc++; arg[0] = i = 0; continue; } if ((i + 1) == sizeof(arg)) continue; arg[i++] = *s; } arg[i] = 0; if (arg[0]) { pptr = Realloc(pptr, (idx + 2) * sizeof(char *)); pptr[idx++] = strdup(arg); my_argc++; } pptr[idx] = NULL; *me_argc = my_argc; return pptr; } static char *get_home_directory() { struct passwd *pw; static char dir[FILENAME_MAX]; errno = 0; if ((pw = getpwuid(getuid())) == NULL) { if (errno) warn("getpwuid()"); else warnx("getpwuid(): no such uid"); return NULL; } strncpy(dir, pw->pw_dir, sizeof(dir)); return dir; } /* Read in a configuration file adding modules to the module array and * checking any module options. */ static int parse_rc_file(const char *filename) { char line[LINE_MAX], *p; FILE *fp; int idx; int old_optind = optind; if ((fp = fopen(filename, "r")) == NULL) { warn("%s", filename); return 1; } while ((p = fgets(line, sizeof(line), fp)) != NULL) { char name[FILENAME_MAX], options[LINE_MAX], tmp[LINE_MAX], *s; int my_argc; char **my_argv; int lastchar = '\0'; while (*p && isspace((unsigned char) *p)) p++; if (*p == '#') continue; s = name; if (*p == '>' || *p == '-') { chaining = 1; if (*p == '-') chain_output = 0; p++; } while (*p && *p != ' ' && *p != '\t') { if (*p == '\n') { p++; break; } *s++ = *p++; } *s = '\0'; if (!name[0]) continue; s = options; while (*p && isspace((unsigned char) *p)) p++; lastchar = *p; while (*p) { if (*p == '\n' || (*p == '#' && lastchar != '\\')) break; if (*p == '#' && lastchar == '\\') { lastchar = *--s = *p++; s++; continue; } lastchar = *s++ = *p++; } *s = '\0'; p = name; if (*p == '~') { s = get_home_directory(); strncpy(tmp, s, sizeof(tmp)); p++; strncat(tmp, p, sizeof(tmp)); strncpy(name, tmp, sizeof(name)); } if (open_module(name, &idx)) continue; if ((my_argv = parseargv(options, __progname, &my_argc)) == NULL) continue; optind = 0; if (init_module_options(my_argc, my_argv, modules[idx])) { fclose(fp); return 2; } optind = old_optind; } fclose(fp); return 0; } int main(int argc, char *argv[]) { int i = 0; int ret = EXIT_SUCCESS; int opt; char line[LINE_MAX], *s = NULL; int want_help = 0; #ifndef HAVE___PROGNAME __progname = argv[0]; #endif delimchar = DEFAULT_DELIMINATING_CHAR; multichar = DEFAULT_MULTI_CHAR; strncpy(tf, DEFAULT_TIMEFORMAT, sizeof(tf)); chain_output = 1; while ((opt = getopt(argc, argv, "+x:X:dm:c:hO:F:t:vVLf")) != -1) { /* * See getopt(3). */ opterr = 0; switch (opt) { case 'd': i = module_index; if (open_module("passwd.so", &i) == 0) { if (init_module_options(1, NULL, modules[i])) want_help = 1; } else { ret = EXIT_FAILURE; goto cleanup; } if (open_module("mail.so", &i) == 0) { if (init_module_options(1, NULL, modules[i])) want_help = 1; } else { ret = EXIT_FAILURE; goto cleanup; } if (open_module("login.so", &i) == 0) { if (init_module_options(1, NULL, modules[i])) want_help = 1; } else { ret = EXIT_FAILURE; goto cleanup; } break; case 'm': if ((optarg[0] != '\\' && strlen(optarg) > 1) || (optarg[0] == '\\' && strlen(optarg) != 2)) { want_help = 1; break; } if ((multichar = escapes(optarg)) == 0) want_help = 1; break; case 'c': if ((ret = parse_rc_file(optarg)) != 0) { if (ret == 2) want_help = 1; else exit(EXIT_FAILURE); } break; case 'F': if ((optarg[0] != '\\' && strlen(optarg) > 1) || (optarg[0] == '\\' && strlen(optarg) != 2)) { want_help = 1; break; } if ((delimchar = escapes(optarg)) == 0) want_help = 1; break; case 't': strncpy(tf, optarg, sizeof(tf)); break; case 'V': printf("%s\n%s\n", PACKAGE_STRING, COPYRIGHT); exit(EXIT_SUCCESS); break; case 'L': followsymlinks = 1; break; case 'f': usefile = 1; break; case 'v': verbose++; break; case 'X': chain_output = 0; case 'x': chaining = 1; case 'O': if (open_module(optarg, &i)) { ret = EXIT_FAILURE; goto cleanup; } if (init_module_options(argc, argv, modules[i])) want_help = 1; /* * For modules which have no options at all (to keep getopt * from interpreting the rest as arguments. */ if (optind < argc) { if (strcmp(argv[optind], "--") == 0) optind++; } break; case 'h': default: want_help = 1; break; } } /* The last module cannot be chained (syntax). */ if (!module_index || TEST_FLAG(modules[module_index - 1].flags, MODULE_CHAINED)) want_help = 1; /* Cycle through the modules and output their help text. */ if (want_help) { usage_header(); for (i = 0; i < module_index; i++) { module_help *m_help; if (TEST_FLAG(modules[i].flags, MODULE_DUP)) continue; if ((m_help = dlsym(modules[i].m, "ui_module_help")) == NULL) { warnx("%s", dlerror()); continue; } fprintf(stderr, "%s\n", modules[i].name); (*m_help) (); } usage(); cleanup_modules(); exit(EXIT_FAILURE); } if (argc == optind || strcmp(argv[optind], "-") == 0) { while ((s = fgets(line, sizeof(line), stdin)) != NULL) { if (s[strlen(s) - 1] == '\n') s[strlen(s) - 1] = '\0'; ret |= junction(s); } } else { for (; optind < argc; optind++) ret |= junction(argv[optind]); } cleanup: cleanup_modules(); exit(ret); }