/*
Copyright (C) 2001-2006 Ben Kibbey <bjk@luxsci.net>
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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <ctype.h>
#include <pwd.h>
#include <time.h>
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#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 <filename>] [-t fmt] [-m c] [-F c] [-d]\n"
"\t[[-xX] -O <module1> [options] [-- [-xX] -O <module2> [...]]]\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 <bjk@luxsci.net>
*
* 2004/11/07
* Modified to handle argv[0] and argc. (Ben Kibbey <bjk@luxsci.net>)
*/
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);
}
syntax highlighted by Code2HTML, v. 0.9.1