/* -*- mode: c; c-backslash-column: 78; c-backslash-max-column: 78 -*-
*
* Copyright (c) 2003,2004,2005,2006 David Lichteblau
* Copyright (c) 2006 Perry Nguyen
*
* 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 <popt.h>
#include "common.h"
#include "version.h"
static void parse_configuration(char *, cmdline *, GPtrArray *);
#define USAGE \
"Usage: ldapvi [OPTION]... [FILTER] [AD]...\n" \
"Quickstart:\n" \
" ldapvi --discover --host HOSTNAME\n" \
"Perform an LDAP search and update results using a text editor.\n" \
"\n" \
"Other usage:\n" \
" ldapvi --out [OPTION]... [FILTER] [AD]... Print entries\n" \
" ldapvi --in [OPTION]... [FILENAME] Load change records\n" \
" ldapvi --delete [OPTION]... DN... Edit a delete record\n" \
" ldapvi --rename [OPTION]... DN1 DN2 Edit a rename record\n" \
"\n" \
"Connection options:\n" \
" -h, --host URL Server.\n" \
" -D, --user USER Search filter or DN: User to bind as. [1]\n" \
" Sets --bind simple.\n" \
" -w, --password SECRET Password (also valid for SASL).\n" \
" --bind [simple,sasl]\n" \
" Disable or enable SASL.\n" \
" --bind-dialog [never,auto,always]\n" \
" Interactive login dialog.\n" \
"\n" \
"SASL options (these parameters set --bind sasl):\n" \
" -I, --sasl-interactive Set --bind-dialog always.\n" \
" -O, --sasl-secprops P SASL security properties.\n" \
" -Q, --sasl-quiet Set --bind-dialog never.\n" \
" -R, --sasl-realm R SASL realm.\n" \
" -U, --sasl-authcid AC SASL authentication identity.\n" \
" -X, --sasl-authzid AZ SASL authorization identity.\n" \
" -Y, --sasl-mech MECH SASL mechanism.\n" \
"\n" \
"Search parameters:\n" \
" -b, --base DN Search base.\n" \
" -s, --scope SCOPE Search scope. One of base|one|sub.\n" \
" -S, --sort KEYS Sort control (critical).\n" \
"\n" \
"Miscellaneous options:\n" \
" --add (Only with --in, --ldapmodify:)\n" \
" Treat attrval records as new entries to add.\n" \
" -o, --class OBJCLASS Class to add. Can be repeated. Implies -A.\n" \
" --config Print parameters in ldap.conf syntax.\n" \
" -c --continue Ignore LDAP errors and continue processing.\n" \
" --deleteoldrdn (Only with --rename:) Delete the old RDN.\n" \
" -a, --deref never|searching|finding|always\n" \
" -d, --discover Auto-detect naming contexts. [2]\n" \
" -A, --empty Don't search, start with empty file. See -o.\n" \
" --encoding [ASCII|UTF-8|binary]\n" \
" The encoding to allow. Default is UTF-8.\n" \
" -H, --help This help.\n" \
" --ldap-conf Always read libldap configuration.\n" \
" -m, --may Show missing optional attributes as comments.\n" \
" -M, --managedsait manageDsaIT control (critical).\n" \
" --noquestions Commit without asking for confirmation.\n" \
" -!, --noninteractive Never ask any questions.\n" \
" -q, --quiet Disable progress output.\n" \
" -R, --read DN Same as -b DN -s base '(objectclass=*)' + *\n" \
" -Z, --starttls Require startTLS.\n" \
" --tls [never|allow|try|strict] Level of TLS strictess.\n" \
" -v, --verbose Note every update.\n" \
"\n" \
"Shortcuts:\n" \
" --ldapsearch Short for --quiet --out\n" \
" --ldapmodify Short for --noninteractive --in\n" \
" --ldapdelete Short for --noninteractive --delete\n" \
" --ldapmoddn Short for --noninteractive --rename\n" \
"\n" \
"Environment variables: VISUAL, EDITOR, PAGER.\n" \
"\n" \
"[1] User names can be specified as distinguished names:\n" \
" uid=foo,ou=bar,dc=acme,dc=com\n" \
" or search filters:\n" \
" (uid=foo)\n" \
" Note the use of parenthesis, which can be omitted from search\n" \
" filters usually but are required here. For this searching bind to\n" \
" work, your client library must be configured with appropriate\n" \
" default search parameters.\n" \
"\n" \
"[2] Repeat the search for each naming context found and present the\n" \
" concatenation of all search results. Conflicts with --base.\n" \
" With --config, show a BASE configuration line for each context.\n" \
"\n" \
"A special (offline) option is --diff, which compares two files\n" \
"and writes any changes to standard output in LDIF format.\n" \
"\n" \
"Report bugs to \"ldapvi@lists.askja.de\"."
enum ldapvi_option_numbers {
OPTION_TLS = 1000, OPTION_ENCODING, OPTION_LDIF, OPTION_LDAPVI,
OPTION_OUT, OPTION_IN, OPTION_DELETE, OPTION_RENAME, OPTION_MODRDN,
OPTION_NOQUESTIONS, OPTION_LDAPSEARCH, OPTION_LDAPMODIFY,
OPTION_LDAPDELETE, OPTION_LDAPMODDN, OPTION_LDAPMODRDN, OPTION_ADD,
OPTION_CONFIG, OPTION_READ, OPTION_LDAP_CONF, OPTION_BIND,
OPTION_BIND_DIALOG
};
static struct poptOption options[] = {
{"host", 'h', POPT_ARG_STRING, 0, 'h', 0, 0},
{"scope", 's', POPT_ARG_STRING, 0, 's', 0, 0},
{"base", 'b', POPT_ARG_STRING, 0, 'b', 0, 0},
{"user", 'D', POPT_ARG_STRING, 0, 'D', 0, 0},
{"sasl-interactive",'I',0, 0, 'I', 0, 0},
{"sasl-quiet" ,'Q',0, 0, 'Q', 0, 0},
{"sasl-secprops",'O', POPT_ARG_STRING, 0, 'O', 0, 0},
{"sasl-realm", 'R', POPT_ARG_STRING, 0, 'R', 0, 0},
{"sasl-mech", 'Y', POPT_ARG_STRING, 0, 'Y', 0, 0},
{"sasl-authzid",'X', POPT_ARG_STRING, 0, 'X', 0, 0},
{"sasl-authcid",'U', POPT_ARG_STRING, 0, 'U', 0, 0},
{"password", 'w', POPT_ARG_STRING, 0, 'w', 0, 0},
{"chase", 'C', POPT_ARG_STRING, 0, 'C', 0, 0},
{"deref", 'a', POPT_ARG_STRING, 0, 'a', 0, 0},
{"sort", 'S', POPT_ARG_STRING, 0, 'S', 0, 0},
{"class", 'o', POPT_ARG_STRING, 0, 'o', 0, 0},
{"read", 0, POPT_ARG_STRING, 0, OPTION_READ, 0, 0},
{"profile", 'p', POPT_ARG_STRING, 0, 'p', 0, 0},
{"tls", 0, POPT_ARG_STRING, 0, OPTION_TLS, 0, 0},
{"encoding", 0, POPT_ARG_STRING, 0, OPTION_ENCODING, 0, 0},
{"bind", 0, POPT_ARG_STRING, 0, OPTION_BIND, 0, 0},
{"bind-dialog", 0, POPT_ARG_STRING, 0, OPTION_BIND_DIALOG, 0, 0},
{"continuous", 'c', 0, 0, 'c', 0, 0},
{"continue", 'c', 0, 0, 'c', 0, 0},
{"empty", 'A', 0, 0, 'A', 0, 0},
{"discover", 'd', 0, 0, 'd', 0, 0},
{"quiet", 'q', 0, 0, 'q', 0, 0},
{"verbose", 'v', 0, 0, 'v', 0, 0},
{"managedsait", 'M', 0, 0, 'M', 0, 0},
{"may", 'm', 0, 0, 'm', 0, 0},
{"starttls", 'Z', 0, 0, 'Z', 0, 0},
{"help", 'H', 0, 0, 'H', 0, 0},
{"version", 'V', 0, 0, 'V', 0, 0},
{"noninteractive", '!', 0, 0, '!', 0, 0},
{"deleteoldrdn", 'r', 0, 0, 'r', 0, 0},
{"add", 0, 0, 0, OPTION_ADD, 0, 0},
{"config", 0, 0, 0, OPTION_CONFIG, 0, 0},
{"noquestions", 0, 0, 0, OPTION_NOQUESTIONS, 0, 0},
{"ldap-conf", 0, 0, 0, OPTION_LDAP_CONF, 0, 0},
{"ldif", 0, 0, 0, OPTION_LDIF, 0, 0},
{"ldapvi", 0, 0, 0, OPTION_LDAPVI, 0, 0},
{"out", 0, 0, 0, OPTION_OUT, 0, 0},
{"in", 0, 0, 0, OPTION_IN, 0, 0},
{"delete", 0, 0, 0, OPTION_DELETE, 0, 0},
{"rename", 0, 0, 0, OPTION_RENAME, 0, 0},
{"modrdn", 0, 0, 0, OPTION_MODRDN, 0, 0},
{"ldapsearch", 0, 0, 0, OPTION_LDAPSEARCH, 0, 0},
{"ldapmodify", 0, 0, 0, OPTION_LDAPMODIFY, 0, 0},
{"ldapdelete", 0, 0, 0, OPTION_LDAPDELETE, 0, 0},
{"ldapmoddn", 0, 0, 0, OPTION_LDAPMODDN, 0, 0},
{"ldapmodrdn", 0, 0, 0, OPTION_LDAPMODRDN, 0, 0},
{0, 0, 0, 0, 0}
};
void
usage(int fd, int rc)
{
if (fd == -1 && rc == 0 && isatty(1)) {
int fd;
int pid = pipeview(&fd);
write(fd, USAGE, strlen(USAGE));
close(fd);
pipeview_wait(pid);
} else {
if (fd != -1) dup2(fd, 1);
puts(USAGE);
}
if (rc != -1) exit(rc);
}
void
init_cmdline(cmdline *cmdline)
{
cmdline->server = 0;
cmdline->basedns = g_ptr_array_new();
cmdline->scope = LDAP_SCOPE_SUBTREE;
cmdline->filter = 0;
cmdline->attrs = 0;
cmdline->quiet = 0;
cmdline->referrals = 1;
cmdline->classes = 0;
cmdline->ldapmodify_add = 0;
cmdline->managedsait = 0;
cmdline->sortkeys = 0;
cmdline->starttls = 0;
cmdline->tls = LDAP_OPT_X_TLS_TRY;
cmdline->deref = LDAP_DEREF_NEVER;
cmdline->verbose = 0;
cmdline->noquestions = 0;
cmdline->noninteractive = 0;
cmdline->discover = 0;
cmdline->config = 0;
cmdline->ldif = 0;
cmdline->ldapvi = 0;
cmdline->mode = ldapvi_mode_edit;
cmdline->rename_dor = 0;
cmdline->schema_comments = 0;
cmdline->continuous = 0;
cmdline->profileonlyp = 0;
cmdline->bind_options.authmethod = LDAP_AUTH_SIMPLE;
cmdline->bind_options.dialog = BD_AUTO;
cmdline->bind_options.user = 0;
cmdline->bind_options.password = 0;
cmdline->bind_options.sasl_authcid = 0;
cmdline->bind_options.sasl_authzid = 0;
cmdline->bind_options.sasl_mech = 0;
cmdline->bind_options.sasl_realm = 0;
cmdline->bind_options.sasl_secprops = 0;
}
static void
parse_argument(int c, char *arg, cmdline *result, GPtrArray *ctrls)
{
LDAPControl *control;
switch (c) {
case 'H':
usage(-1, 0);
case 'h':
result->server = arg;
break;
case 's':
if (!strcmp(arg, "base"))
result->scope = LDAP_SCOPE_BASE;
else if (!strcmp(arg, "one"))
result->scope = LDAP_SCOPE_ONELEVEL;
else if (!strcmp(arg, "sub"))
result->scope = LDAP_SCOPE_SUBTREE;
else {
fprintf(stderr, "invalid scope: %s\n", arg);
usage(2, 1);
}
break;
case 'b':
g_ptr_array_add(result->basedns, arg);
break;
case 'D':
result->bind_options.authmethod = LDAP_AUTH_SIMPLE;
result->bind_options.user = *arg ? arg : 0;
break;
case 'w':
result->bind_options.password = arg;
break;
case 'd':
result->discover = 1;
break;
case 'c':
result->continuous = 1;
break;
case OPTION_CONFIG:
result->config = 1;
break;
case 'q':
result->quiet = 1;
break;
case 'A':
if (!result->classes)
result->classes = g_ptr_array_new();
break;
case 'o':
if (!result->classes)
result->classes = g_ptr_array_new();
adjoin_str(result->classes, arg);
break;
case 'C':
if (!strcasecmp(arg, "yes"))
result->referrals = 1;
else if (!strcasecmp(arg, "no"))
result->referrals = 0;
else {
fprintf(stderr, "--chase invalid%s\n", arg);
usage(2, 1);
}
break;
case 'm':
result->schema_comments = 1;
break;
case 'M':
result->managedsait = 1;
control = malloc(sizeof(LDAPControl));
control->ldctl_oid = LDAP_CONTROL_MANAGEDSAIT;
control->ldctl_value.bv_len = 0;
control->ldctl_value.bv_val = 0;
control->ldctl_iscritical = 1;
g_ptr_array_add(ctrls, control);
break;
case 'V':
puts("ldapvi " VERSION);
exit(0);
case 'S':
result->sortkeys = arg;
break;
case 'Z':
result->starttls = 1;
break;
case OPTION_TLS:
if (!strcmp(arg, "never"))
result->tls = LDAP_OPT_X_TLS_NEVER;
else if (!strcmp(arg, "allow"))
result->tls = LDAP_OPT_X_TLS_ALLOW;
else if (!strcmp(arg, "try"))
result->tls = LDAP_OPT_X_TLS_TRY;
else if (!strcmp(arg, "strict"))
result->tls = LDAP_OPT_X_TLS_HARD;
else {
fprintf(stderr, "invalid tls level: %s\n",
arg);
usage(2, 1);
}
break;
case OPTION_ENCODING:
if (!strcasecmp(arg, "ASCII"))
print_binary_mode = PRINT_ASCII;
else if (!strcasecmp(arg, "binary"))
print_binary_mode = PRINT_JUNK;
else if (!strcasecmp(arg, "UTF-8")
|| !strcasecmp(arg, "UTF_8")
|| !strcasecmp(arg, "UTF8"))
print_binary_mode = PRINT_UTF8;
else {
fprintf(stderr, "invalid encoding: %s\n", arg);
usage(2, 1);
}
break;
case OPTION_LDIF:
result->ldif = 1;
break;
case OPTION_LDAPVI:
result->ldapvi = 1;
break;
case OPTION_ADD:
result->ldapmodify_add = 1;
break;
case OPTION_LDAPSEARCH:
result->quiet = 1;
result->noninteractive = 1;
/* fall through */
case OPTION_OUT:
result->mode = ldapvi_mode_out;
break;
case OPTION_LDAPMODIFY:
result->noninteractive = 1;
/* fall through */
case OPTION_IN:
result->mode = ldapvi_mode_in;
break;
case OPTION_LDAPDELETE:
result->noninteractive = 1;
/* fall through */
case OPTION_DELETE:
result->mode = ldapvi_mode_delete;
break;
case OPTION_LDAPMODDN:
result->noninteractive = 1;
/* fall through */
case OPTION_RENAME:
result->mode = ldapvi_mode_rename;
break;
case OPTION_LDAPMODRDN:
result->noninteractive = 1;
/* fall through */
case OPTION_MODRDN:
result->mode = ldapvi_mode_modrdn;
break;
case 'r':
result->rename_dor = 1;
break;
case OPTION_READ:
g_ptr_array_add(result->basedns, arg);
result->scope = LDAP_SCOPE_BASE;
result->filter = "(objectclass=*)";
{
static char *attrs[3] = {"+", "*", 0};
result->attrs = attrs;
}
break;
case 'a':
if (!strcasecmp(arg, "never"))
result->deref = LDAP_DEREF_NEVER;
else if (!strcasecmp(arg, "searching"))
result->deref = LDAP_DEREF_SEARCHING;
else if (!strcasecmp(arg, "finding"))
result->deref = LDAP_DEREF_FINDING;
else if (!strcasecmp(arg, "always"))
result->deref = LDAP_DEREF_ALWAYS;
else {
fprintf(stderr, "--deref invalid: %s\n", arg);
usage(2, 1);
}
break;
case 'v':
result->verbose = 1;
break;
case OPTION_BIND:
if (!strcasecmp(arg, "simple"))
result->bind_options.authmethod = LDAP_AUTH_SIMPLE;
else if (!strcasecmp(arg, "sasl"))
result->bind_options.authmethod = LDAP_AUTH_SASL;
else {
fprintf(stderr, "--bind invalid: %s\n", arg);
usage(2, 1);
}
break;
case OPTION_BIND_DIALOG:
if (!strcasecmp(arg, "always"))
result->bind_options.dialog = BD_ALWAYS;
else if (!strcasecmp(arg, "auto"))
result->bind_options.dialog = BD_AUTO;
else if (!strcasecmp(arg, "never"))
result->bind_options.dialog = BD_NEVER;
else {
fprintf(stderr, "--bind-dialog invalid: %s\n", arg);
usage(2, 1);
}
break;
case 'I':
result->bind_options.authmethod = LDAP_AUTH_SASL;
result->bind_options.dialog = BD_ALWAYS;
break;
case 'Q':
result->bind_options.authmethod = LDAP_AUTH_SASL;
result->bind_options.dialog = BD_NEVER;
break;
case 'U':
result->bind_options.authmethod = LDAP_AUTH_SASL;
result->bind_options.sasl_authcid = arg;
break;
case 'X':
result->bind_options.authmethod = LDAP_AUTH_SASL;
result->bind_options.sasl_authzid = arg;
break;
case 'Y':
result->bind_options.authmethod = LDAP_AUTH_SASL;
result->bind_options.sasl_mech = arg;
break;
case 'R':
result->bind_options.authmethod = LDAP_AUTH_SASL;
result->bind_options.sasl_realm = arg;
break;
case 'O':
result->bind_options.authmethod = LDAP_AUTH_SASL;
result->bind_options.sasl_secprops = arg;
break;
case '!':
result->noninteractive = 1;
break;
case OPTION_NOQUESTIONS:
result->noquestions = 1;
break;
case OPTION_LDAP_CONF:
result->profileonlyp = 0;
break;
case 'p':
parse_configuration(arg, result, ctrls);
break;
default:
abort();
}
}
static void
parse_profile_line(tattribute *attribute, cmdline *result, GPtrArray *ctrls)
{
char *name = attribute_ad(attribute);
GPtrArray *values = attribute_values(attribute);
int i;
struct poptOption *o = 0;
if (!strcmp(name, "filter")) {
int last = values->len - 1;
result->filter = array2string(g_ptr_array_index(values, last));
return;
}
if (!strcmp(name, "ad")) {
int n = values->len;
char **attrs = xalloc((n + 1) * sizeof(char *));
for (i = 0; i < n; i++)
attrs[i] = array2string(g_ptr_array_index(values, i));
attrs[n] = 0;
result->attrs = attrs;
return;
}
for (i = 0; options[i].longName; i++)
if (!strcmp(name, options[i].longName)) {
o = &options[i];
break;
}
if (!o) {
fprintf(stderr, "Error: unknown configuration option: '%s'\n",
name);
exit(1);
}
for (i = 0; i < values->len; i++) {
char *value = array2string(g_ptr_array_index(values, i));
if (o->argInfo == 0)
if (!strcmp(value, "no"))
continue;
else if (strcmp(value, "yes")) {
fprintf(stderr,
"invalid value '%s' to configuration"
" option '%s', expected 'yes' or"
" 'no'.\n",
value,
name);
exit(1);
}
parse_argument(o->val, value, result, ctrls);
}
}
static void
parse_configuration(char *profile_name, cmdline *result, GPtrArray *ctrls)
{
struct stat st;
char *profile_requested = profile_name;
char *filename = home_filename(".ldapvirc");
FILE *s;
tentry *p;
tentry *profile_found = 0;
int duplicate = 0;
if (!profile_name)
profile_name = "default";
if (!filename || stat(filename, &st)) {
filename = "/etc/ldapvi.conf";
if (stat(filename, &st))
filename = 0;
}
if (!filename) {
if (profile_requested) {
fputs("Error: ldapvi configuration file not found.\n",
stderr);
exit(1);
}
return;
}
if ( !(s = fopen(filename, "r"))) syserr();
for (;;) {
p = 0;
if (read_profile(s, &p)) {
fputs("Error in configuration file, giving up.\n",
stderr);
exit(1);
}
if (!p)
break;
if (strcmp(entry_dn(p), profile_name))
entry_free(p);
else if (profile_found)
duplicate = 1;
else
profile_found = p;
}
if (duplicate) {
fprintf(stderr,
"Error: Duplicate configuration profile '%s'.\n",
profile_name);
exit(1);
}
if (profile_found) {
result->profileonlyp = 1;
GPtrArray *attributes = entry_attributes(profile_found);
int i;
for (i = 0; i < attributes->len; i++) {
tattribute *a = g_ptr_array_index(attributes, i);
parse_profile_line(a, result, ctrls);
}
entry_free(profile_found);
} else if (profile_requested) {
fprintf(stderr,
"Error: Configuration profile not found: '%s'.\n",
profile_name);
exit(1);
}
if (fclose(s) == EOF) syserr();
}
void
parse_arguments(int argc, const char **argv, cmdline *result, GPtrArray *ctrls)
{
int c;
poptContext ctx;
char *profile = 0;
ctx = poptGetContext(
0, argc, argv, options, POPT_CONTEXT_POSIXMEHARDER);
while ( (c = poptGetNextOpt(ctx)) > 0) {
char *arg = (char *) poptGetOptArg(ctx);
if (c != 'p') continue;
if (profile) {
fputs("Multiple profile options given.\n", stderr);
usage(2, 1);
}
profile = arg;
}
parse_configuration(profile, result, ctrls);
poptResetContext(ctx);
while ( (c = poptGetNextOpt(ctx)) > 0) {
char *arg = (char *) poptGetOptArg(ctx);
if (c != 'p')
parse_argument(c, arg, result, ctrls);
}
if (c != -1) {
fprintf(stderr, "%s: %s\n",
poptBadOption(ctx, POPT_BADOPTION_NOALIAS),
poptStrerror(c));
usage(2, 1);
}
if (result->classes
&& result->mode != ldapvi_mode_edit
&& result->mode != ldapvi_mode_out)
{
fputs("Error: Conflicting options given;"
" cannot use --class in this mode.\n",
stderr);
exit(1);
}
switch (result->mode) {
case ldapvi_mode_edit: /* fall through */
case ldapvi_mode_out:
if (!result->filter)
result->filter = (char *) poptGetArg(ctx);
if (!result->attrs)
result->attrs = (char **) poptGetArgs(ctx);
break;
case ldapvi_mode_delete:
result->delete_dns = (char **) poptGetArgs(ctx);
break;
case ldapvi_mode_rename: /* fall through */
case ldapvi_mode_modrdn:
result->rename_old = (char *) poptGetArg(ctx);
result->rename_new = (char *) poptGetArg(ctx);
if (poptGetArg(ctx)) {
fputs("Error: Too many command line arguments.\n",
stderr);
exit(1);
}
break;
case ldapvi_mode_in:
result->in_file = (char *) poptGetArg(ctx);
if (poptGetArg(ctx)) {
fputs("Error: Too many command line arguments.\n",
stderr);
exit(1);
}
break;
default:
abort();
}
if (result->profileonlyp)
if (setenv("LDAPNOINIT", "thanks", 1)) syserr();
/* don't free! */
/* poptFreeContext(ctx); */
}
syntax highlighted by Code2HTML, v. 0.9.1