From b6c720096e296f514cc8b254be8838f963fd9e06 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Sun, 29 Mar 2020 17:39:54 +0300 Subject: [PATCH] Use wordsplit for keyword expansion. * .gitmodules: Add wordsplit * configure.ac: Likewise. * lib/Makefile.am: Likewise. * lib/graypam.h (gray_expand_argv): Remove. (gray_expand_string): Change prototype. * lib/vartab.c (gray_expand_argv): Remove. (gray_expand_string): Rewrite using wordsplit. * pam_ldaphome/pam_ldaphome.c (import_public_key): Assume sshPublicKey as a default attribute. * pam_log/pam_log.c (_pam_parse): Take two return arguments. (echo): Use gray_expand_string. * pam_sql/pam_mysql.c: Update gray_expand_string usage. * pam_sql/pam_pgsql.c: Likewise. * pam_sql/pam_sql.c: Likewise. * pam_sql/pam_sql.h (gpam_sql_get_query): Change signature. * pam_umotd/pam_umotd.c (pam_sm_open_session): Update gray_expand_string usage. --- .gitmodules | 3 + configure.ac | 2 +- lib/Makefile.am | 9 +- lib/graypam.h | 5 +- lib/vartab.c | 229 ++++++++++-------------------------- lib/wordsplit | 1 + pam_ldaphome/pam_ldaphome.c | 36 ++---- pam_log/pam_log.c | 47 +++++--- pam_sql/pam_mysql.c | 13 +- pam_sql/pam_pgsql.c | 12 +- pam_sql/pam_sql.c | 36 ++---- pam_sql/pam_sql.h | 3 +- pam_umotd/pam_umotd.c | 62 +++++----- 13 files changed, 167 insertions(+), 291 deletions(-) create mode 160000 lib/wordsplit diff --git a/.gitmodules b/.gitmodules index 527e24f..67e703b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "imprimatur"] path = imprimatur url = git://git.gnu.org.ua/imprimatur.git +[submodule "lib/wordsplit"] + path = lib/wordsplit + url = git://git.gnu.org.ua/wordsplit.git diff --git a/configure.ac b/configure.ac index e146a65..a784780 100644 --- a/configure.ac +++ b/configure.ac @@ -20,7 +20,7 @@ AC_INIT(pam-modules, 2.3.90, bug-pam-modules@gnu.org.ua) AC_CONFIG_SRCDIR(pam_fshadow/pam_fshadow.c) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) -AM_INIT_AUTOMAKE([1.11 no-exeext tar-ustar dist-xz silent-rules]) +AM_INIT_AUTOMAKE([1.11 no-exeext tar-ustar dist-xz silent-rules subdir-objects]) AM_CONFIG_HEADER(config.h) # Enable silent rules by default: diff --git a/lib/Makefile.am b/lib/Makefile.am index cbceb4c..013d54e 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,5 +1,5 @@ # This file is part of pam-modules. -# Copyright (C) 2008, 2010-2012, 2014-2015, 2018 Sergey Poznyakoff +# Copyright (C) 2008-2020 Sergey Poznyakoff # # 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 @@ -32,6 +32,9 @@ libgraypam_la_SOURCES = \ sha1.h\ strutil.c\ vartab.c\ - escape.c + escape.c\ + wordsplit/wordsplit.c -noinst_HEADERS = graypam.h +noinst_HEADERS = graypam.h wordsplit/wordsplit.h + +AM_CPPFLAGS = -I$(srcdir)/wordsplit diff --git a/lib/graypam.h b/lib/graypam.h index dcb9dde..f9ce0fc 100644 --- a/lib/graypam.h +++ b/lib/graypam.h @@ -177,10 +177,7 @@ ssize_t gray_base64_decode(gray_slist_t slist, const char *iptr, size_t isize); int gray_check_ldap_pass (const char *db_pass, const char *pass); -void gray_expand_argv(pam_handle_t *pamh, int argc, const char **argv, - gray_slist_t slist); -void gray_expand_string(pam_handle_t *pamh, const char *str, - gray_slist_t slist); +int gray_expand_string(pam_handle_t *pamh, const char *str, char **output); void gray_escape_string(gray_slist_t slist, const char *str, size_t len); struct keyword { diff --git a/lib/vartab.c b/lib/vartab.c index 40fa89f..89974cd 100644 --- a/lib/vartab.c +++ b/lib/vartab.c @@ -1,5 +1,5 @@ /* This file is part of pam-modules. - Copyright (C) 2009-2012, 2014-2015, 2018 Sergey Poznyakoff + Copyright (C) 2009-2020 Sergey Poznyakoff 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 @@ -15,6 +15,7 @@ with this program. If not, see . */ #include +#include "wordsplit/wordsplit.h" struct keyword * gray_find_keyword(struct keyword *kwtab, const char *str, size_t len) @@ -25,177 +26,77 @@ gray_find_keyword(struct keyword *kwtab, const char *str, size_t len) return kwtab; return NULL; } - + static struct keyword vartab[] = { - DCL("service", PAM_SERVICE), - DCL("user", PAM_USER), - DCL("tty", PAM_TTY), - DCL("rhost", PAM_RHOST), - DCL("ruser", PAM_RUSER), - DCL("prompt", PAM_USER_PROMPT), - DCL("password", PAM_AUTHTOK), - { NULL } + DCL("service", PAM_SERVICE), + DCL("user", PAM_USER), + DCL("tty", PAM_TTY), + DCL("rhost", PAM_RHOST), + DCL("ruser", PAM_RUSER), + DCL("prompt", PAM_USER_PROMPT), + DCL("password", PAM_AUTHTOK), + { NULL } }; static int -var_tok(const char *str, const char ** pvar, size_t *plen) +get_pam_var(char **ret, const char *var, size_t len, void *clos) { - size_t len; - - for (len = 0; str[len]; len++) { - if (str[len] == '}' || str[len] == ':') { - *pvar = str; - *plen = len; - return 0; - } - } - return 1; -} - -static int -repl_tok(const char *str, const char ** pret, size_t *plen) -{ - size_t len; - - for (len = 0; str[len]; len++) { - if (str[len] == '}') { - *pret = str; - *plen = len; - return 0; - } - } - return 1; -} - -#define ISKW(c) ((c) && (isalnum(c) || (c) == '_')) - -static int -get_variable(pam_handle_t *pamh, const char *str, gray_slist_t slist, - const char **endp) -{ - const char *name; - size_t namelen; - const char *repl = NULL; - size_t repllen = 0; - const char *val; - size_t vallen; + pam_handle_t *pamh = clos; struct keyword *kw; - const char *end; + char const *val; + int rc; + char *s; + + kw = gray_find_keyword(vartab, var, len); + if (!kw) + return WRDSE_UNDEF; + rc = pam_get_item(pamh, kw->code, (const void**) &val); + if (rc) { + _pam_log(LOG_ERR, + "cannot obtain variable %s: %s", + kw->name, pam_strerror(pamh, rc)); + return WRDSE_UNDEF; + } + if (!val) { + val = ""; + } + s = strdup(val); + if (!s) + return WRDSE_NOSPACE; + *ret = s; + return WRDSE_OK; +} + +int +gray_expand_string(pam_handle_t *pamh, const char *str, char **output_ptr) +{ + struct wordsplit ws; + int wsflags = WRDSF_NOSPLIT + | WRDSF_GETVAR + | WRDSF_CLOSURE + | WRDSF_NOCMD + | WRDSF_UNDEF; int rc; - str++; /* Get past the initial $ */ - if (*str == '{') { - str++; - - if (var_tok(str, &name, &namelen)) - return 1; - - end = str + namelen; - if (*end == ':') { - end++; - if (*end == '-') - end++; - if (repl_tok(end, &repl, &repllen)) - return 1; - end += repllen; - } - end++; - } else { - name = end = str; - for (namelen = 0; ISKW(*end); namelen++, end++) - ; - } - - kw = gray_find_keyword(vartab, name, namelen); - if (!kw) { - _pam_log(LOG_ERR, - "unknown PAM variable: %*.*s", - namelen, namelen, name); - return 1; - } - - rc = pam_get_item(pamh, kw->code, (const void**) &val); - if (rc) { - _pam_log(LOG_ERR, - "cannot obtain variable %s: %s", - kw->name, pam_strerror(pamh, rc)); - return 1; - } - - if (!val) { - if (repl) { - val = repl; - vallen = repllen; - } else { - val = ""; - vallen = 0; - } - } else - vallen = strlen(val); - - gray_escape_string(slist, val, vallen); - *endp = end; - return 0; -} - -void -gray_expand_argv(pam_handle_t *pamh, int argc, const char **argv, - gray_slist_t slist) -{ - int i; - - for (i = 0; i < argc; i++) { - if (i > 0) - gray_slist_append_char(slist, ' '); - if (strchr(argv[i], '$') == 0) - gray_slist_append(slist, argv[i], strlen(argv[i])); - else { - const char *p; - - for (p = argv[i]; *p; p++) { - if (*p == '\\') { - p++; - gray_slist_append_char(slist, *p); - } else if (*p == '$') { - if (get_variable(pamh, p, slist, &p)) - gray_slist_append_char(slist, - *p); - else - p--; - } else - gray_slist_append_char(slist, *p); - } - } - } -} - -void -gray_expand_string(pam_handle_t *pamh, const char *str, gray_slist_t slist) -{ - const char *p; -#define FLUSH() gray_slist_append(slist, str, p - str); str = p + ws.ws_getvar = get_pam_var; + ws.ws_closure = pamh; - for (p = str; *p; ) { - if (*p == '\\') { - FLUSH(); - p++; - if (*p) { - gray_slist_append_char(slist, *p); - p++; - } else { - gray_slist_append_char(slist, '\\'); - break; - } - str = p; - } else if (*p == '$') { - FLUSH(); - if (get_variable(pamh, p, slist, &p)) { - gray_slist_append_char(slist, *p); - p++; - } - str = p; - } else - p++; + rc = wordsplit(str, &ws, wsflags); + + if (rc) { + if (ws.ws_errctx) + _pam_log(LOG_ERR, + "string split: %s: %s", + wordsplit_strerror (&ws), ws.ws_errctx); + else + _pam_log(LOG_ERR, + "string split: %s", + wordsplit_strerror (&ws)); + rc = (rc == WRDSE_NOSPACE) ? PAM_BUF_ERR : PAM_SERVICE_ERR; + } else { + *output_ptr = ws.ws_wordv[0]; + ws.ws_wordv[0] = NULL; } - FLUSH(); + wordsplit_free(&ws); + return rc; } diff --git a/lib/wordsplit b/lib/wordsplit new file mode 160000 index 0000000..66e5c11 --- /dev/null +++ b/lib/wordsplit @@ -0,0 +1 @@ +Subproject commit 66e5c112850e85e3a37efbec30a0d7b8d7cff83e diff --git a/pam_ldaphome/pam_ldaphome.c b/pam_ldaphome/pam_ldaphome.c index 0b492dd..5269af0 100644 --- a/pam_ldaphome/pam_ldaphome.c +++ b/pam_ldaphome/pam_ldaphome.c @@ -1576,10 +1576,8 @@ import_public_key(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env) _pam_log(LOG_ERR, "configuration variable `filter' not set"); return PAM_SERVICE_ERR; } - if (!attr) { - _pam_log(LOG_ERR, "configuration variable `attr' not set"); - return PAM_SERVICE_ERR; - } + if (!attr) + attr = "sshPublicKey"; ld = ldap_connect(env); if (!ld) @@ -1588,27 +1586,15 @@ import_public_key(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env) retval = PAM_SERVICE_ERR; else { char *filter; - gray_slist_t slist; - - slist = gray_slist_create(); - if (!slist) - retval = errno_to_pam(errno); - else { - gray_expand_string(pamh, filter_pat, slist); - gray_slist_append_char(slist, 0); - filter = gray_slist_finish(slist); - if (filter) { - char **keys = get_pubkeys(ld, base, filter, - attr); - if (keys) { - retval = store_pubkeys(keys, pw, env); - argcvz_free(keys); - } else - retval = PAM_SUCCESS; - } else { - retval = errno_to_pam(gray_slist_err(slist)); - } - gray_slist_free(&slist); + + retval = gray_expand_string(pamh, filter_pat, &filter); + if (retval == PAM_SUCCESS) { + char **keys = get_pubkeys(ld, base, filter, attr); + if (keys) { + retval = store_pubkeys(keys, pw, env); + argcvz_free(keys); + } else + retval = PAM_SUCCESS; } } ldap_unbind(ld); diff --git a/pam_log/pam_log.c b/pam_log/pam_log.c index c59bc16..ae155dc 100644 --- a/pam_log/pam_log.c +++ b/pam_log/pam_log.c @@ -1,5 +1,5 @@ /* This file is part of pam-modules. - Copyright (C) 2006-2008, 2010-2012, 2014-2015, 2018 Sergey Poznyakoff + Copyright (C) 2006-2020 Sergey Poznyakoff 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 @@ -20,8 +20,6 @@ /* Command line parsing */ static long debug_level; -static int xargc; -static const char **xargv; static int priority = LOG_INFO; static int facility = LOG_AUTHPRIV; static const char *syslog_tag = MODULE_NAME; @@ -103,7 +101,8 @@ struct pam_opt pam_opt[] = { static int -_pam_parse(pam_handle_t *pamh, int argc, const char **argv) +_pam_parse(pam_handle_t *pamh, int argc, const char **argv, + int *ret_argc, char const ***ret_argv) { int i; const char **targv; @@ -125,8 +124,8 @@ _pam_parse(pam_handle_t *pamh, int argc, const char **argv) gray_parseopt(pam_opt, i, targv); free(targv); - xargc = argc - i; - xargv = argv + i; + *ret_argc = argc - i; + *ret_argv = argv + i; closelog(); gray_log_init(!do_open, syslog_tag, facility); @@ -136,7 +135,10 @@ _pam_parse(pam_handle_t *pamh, int argc, const char **argv) static int echo(pam_handle_t *pamh, const char *prefix, int argc, const char **argv) { - if (_pam_parse(pamh, argc, argv) == PAM_SUCCESS) { + int xargc; + const char **xargv; + + if (_pam_parse(pamh, argc, argv, &xargc, &xargv) == PAM_SUCCESS) { gray_slist_t slist; slist = gray_slist_create(); @@ -144,20 +146,35 @@ echo(pam_handle_t *pamh, const char *prefix, int argc, const char **argv) errno_to_pam(errno); } else { char *str; + int rc = PAM_SUCCESS; + int i; + if (prefix) { gray_slist_append(slist, prefix, strlen(prefix)); gray_slist_append(slist, ": ", 2); } - gray_expand_argv(pamh, xargc, xargv, slist); - gray_slist_append_char(slist, 0); - str = gray_slist_finish(slist); - if (str) - _pam_log(priority, "%s", str); - else - errno_to_pam(gray_slist_err(slist)); + + for (i = 0; i < xargc; i++) { + char *s; + if (i) + gray_slist_append(slist, " ", 1); + rc = gray_expand_string(pamh, xargv[i], &s); + if (rc != PAM_SUCCESS) + break; + gray_slist_append(slist, s, strlen(s)); + free(s); + } + if (rc == PAM_SUCCESS) { + gray_slist_append_char(slist, 0); + str = gray_slist_finish(slist); + if (str) + _pam_log(priority, "%s", str); + else + errno_to_pam(gray_slist_err(slist)); + } + gray_slist_free(&slist); } - gray_slist_free(&slist); } return PAM_IGNORE; } diff --git a/pam_sql/pam_mysql.c b/pam_sql/pam_mysql.c index 3f4a0d6..a2e7c23 100644 --- a/pam_sql/pam_mysql.c +++ b/pam_sql/pam_mysql.c @@ -358,21 +358,16 @@ gpam_sql_verify_user_pass(pam_handle_t *pamh, const char *password, rc = mysql_do_query(&mysql, query); if (rc == PAM_SUCCESS) { - const char *q; - gray_slist_t slist; - + char *q; rc = check_query_result(&mysql, password); /* FIXME: This comment is needed to pacify `make check-sql-config' in doc: gpam_sql_find_config("setenv-query") */ if (rc == PAM_SUCCESS) { - rc = gpam_sql_get_query(pamh, "setenv-query", 0, - &slist, &q); + rc = gpam_sql_get_query(pamh, "setenv-query", 0, &q); if (rc == PAM_SUCCESS) { - if (q) { - mysql_setenv(pamh, &mysql, q); - gray_slist_free(&slist); - } + mysql_setenv(pamh, &mysql, q); + free(q); } } mysql_close(&mysql); diff --git a/pam_sql/pam_pgsql.c b/pam_sql/pam_pgsql.c index 5f66b53..ed7a465 100644 --- a/pam_sql/pam_pgsql.c +++ b/pam_sql/pam_pgsql.c @@ -114,8 +114,7 @@ gpam_sql_verify_user_pass(pam_handle_t *pamh, const char *password, } else { char *p; int n; - gray_slist_t slist; - char const *query; + char *query; n = PQntuples(res); DEBUG(20,("Returned %d tuples", n)); @@ -155,13 +154,10 @@ gpam_sql_verify_user_pass(pam_handle_t *pamh, const char *password, `make check-sql-config' in doc: gpam_sql_find_config("setenv-query") */ if (rc == PAM_SUCCESS) { - rc = gpam_sql_get_query(pamh, "setenv-query", 0, - &slist, &query); + rc = gpam_sql_get_query(pamh, "setenv-query", 0, &query); if (rc == PAM_SUCCESS) { - if (query) { - pgsql_setenv(pamh, pgconn, query); - gray_slist_free(&slist); - } + pgsql_setenv(pamh, pgconn, query); + free(query); } } } diff --git a/pam_sql/pam_sql.c b/pam_sql/pam_sql.c index 6707eb3..7a11df3 100644 --- a/pam_sql/pam_sql.c +++ b/pam_sql/pam_sql.c @@ -1,5 +1,5 @@ /* This file is part of pam-modules. - Copyright (C) 2005-2008, 2010-2012, 2014-2015, 2018 Sergey Poznyakoff + Copyright (C) 2005-2020 Sergey Poznyakoff 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 @@ -161,11 +161,10 @@ gpam_sql_check_boolean_config(const char *name, int defval) int gpam_sql_get_query(pam_handle_t *pamh, const char *name, int required, - gray_slist_t *pslist, const char **retptr) + char **retptr) { - gray_slist_t slist; const char *query = gpam_sql_find_config(name); - char *retval; + int rc; if (!query) { if (required) { @@ -173,24 +172,13 @@ gpam_sql_get_query(pam_handle_t *pamh, const char *name, int required, gpam_sql_config_file, name); return PAM_AUTHINFO_UNAVAIL; } - *pslist = NULL; *retptr = NULL; return PAM_SUCCESS; } - slist = gray_slist_create(); - if (!slist) - return errno_to_pam(errno); - gray_expand_string(pamh, query, slist); - gray_slist_append_char(slist, 0); - *pslist = slist; - retval = gray_slist_finish(slist); - if (gray_slist_err(slist)) { - int rc = errno_to_pam(gray_slist_err(slist)); - gray_slist_free(&slist); + rc = gray_expand_string(pamh, query, retptr); + if (rc != PAM_SUCCESS) return rc; - } - *retptr = retval; return PAM_SUCCESS; } @@ -226,18 +214,16 @@ pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) if (gray_env_read(gpam_sql_config_file, &config_env)) retval = PAM_SERVICE_ERR; else { - gray_slist_t slist; - char const *query; + char *query; /* FIXME: This comment is needed to pacify `make check-sql-config' in doc: gpam_sql_find_config("passwd-query") */ - retval = gpam_sql_get_query(pamh, "passwd-query", 1, - &slist, &query); + retval = gpam_sql_get_query(pamh, "passwd-query", 1, &query); if (retval == PAM_SUCCESS) { retval = gpam_sql_verify_user_pass(pamh, password, query); - gray_slist_free(&slist); + free(query); } } @@ -279,13 +265,13 @@ sql_session_mgmt(pam_handle_t *pamh, int flags, retval = PAM_SERVICE_ERR; else { gray_slist_t slist; - char const *query; + char *query; - retval = gpam_sql_get_query(pamh, query_name, 0, &slist, &query); + retval = gpam_sql_get_query(pamh, query_name, 0, &query); if (retval == PAM_SUCCESS) { if (query) { retval = gpam_sql_acct(pamh, query); - gray_slist_free(&slist); + free(query); } } } diff --git a/pam_sql/pam_sql.h b/pam_sql/pam_sql.h index 5dc9525..2515c32 100644 --- a/pam_sql/pam_sql.h +++ b/pam_sql/pam_sql.h @@ -48,7 +48,6 @@ int gpam_sql_acct(pam_handle_t *pamh, const char *query); char *gpam_sql_find_config(const char *name); int gpam_sql_get_query(pam_handle_t *pamh, char const *var, - int required, - gray_slist_t *pslist, const char **query); + int required, char **query); int gpam_sql_check_boolean_config(const char *name, int defval); diff --git a/pam_umotd/pam_umotd.c b/pam_umotd/pam_umotd.c index 008e87b..3638aed 100644 --- a/pam_umotd/pam_umotd.c +++ b/pam_umotd/pam_umotd.c @@ -1,5 +1,5 @@ /* This file is part of pam-modules. - Copyright (C) 2012, 2014-2015, 2018 Sergey Poznyakoff + Copyright (C) 2012-2020 Sergey Poznyakoff 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 @@ -314,11 +314,20 @@ exec_file(pam_handle_t *pamh, char **argv, const char *logfile) return PAM_SUCCESS; } +static void +argcv_free(int wc, char **wv) +{ + int i; + + for (i = 0; i < wc; i++) + free(wv[i]); + free(wv); +} + PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { int retval = PAM_IGNORE; - gray_slist_t slist; cntl_flags = 0; debug_level = 0; @@ -338,22 +347,14 @@ pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) if (motd_file_name) { char *file; - - slist = gray_slist_create(); - if (!slist) - retval = errno_to_pam(errno); - else { - gray_expand_string(pamh, motd_file_name, slist); - gray_slist_append_char(slist, 0); - file = gray_slist_finish(slist); - if (file) - retval = read_file(pamh, file); - else - retval = errno_to_pam(errno); + retval = gray_expand_string(pamh, motd_file_name, &file); + if (retval == PAM_SUCCESS) { + retval = read_file(pamh, file); + free(file); } - gray_slist_free(&slist); } else if (optindex >= 0) { char **xargv; + int i; argc -= optindex; argv += optindex; @@ -361,30 +362,21 @@ pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) _pam_log(LOG_INFO, "empty command line"); return retval; } - xargv = calloc((argc + 1), sizeof (xargv[0])); + xargv = calloc((argc + 1), sizeof(xargv[0])); if (!xargv) return errno_to_pam(errno); - slist = gray_slist_create(); - if (!slist) - retval = errno_to_pam(errno); - else { - int i; - int ec; - - for (i = 0; i < argc; i++) { - gray_expand_string(pamh, argv[i], slist); - gray_slist_append_char(slist, 0); - xargv[i] = gray_slist_finish(slist); + for (i = 0; i < argc; i++) { + retval = gray_expand_string(pamh, argv[i], &xargv[i]); + if (retval != PAM_SUCCESS) { + argc = i; + break; } - xargv[i] = NULL; - ec = gray_slist_err(slist); - if (ec) - retval = errno_to_pam(ec); - else - retval = exec_file(pamh, xargv, logfile_name); - free(xargv); - gray_slist_free(&slist); } + if (retval == PAM_SUCCESS) { + xargv[i] = NULL; + retval = exec_file(pamh, xargv, logfile_name); + } + argcv_free(argc, xargv); } else _pam_log(LOG_ERR, "invalid usage: either file or exec must be specified");