/* * adcli * * Copyright (C) 2012 Red Hat Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * * Author: Stef Walter */ #include "config.h" #include "adcli.h" #include "adprivate.h" #include "tools.h" #include #include #include #include #include #include #include #include static char *adcli_temp_directory = NULL; static char *adcli_krb5_conf_filename = NULL; static char *adcli_krb5_d_directory = NULL; enum { CONNECTION_LESS = 1<<0, }; struct { const char *name; int (*function) (adcli_conn *, int, char *[]); const char *text; int flags; } commands[] = { { "info", adcli_tool_info, "Print information about a domain", CONNECTION_LESS }, { "join", adcli_tool_computer_join, "Join this machine to a domain", }, { "update", adcli_tool_computer_update, "Update machine membership in a domain", }, { "testjoin", adcli_tool_computer_testjoin, "Test if machine account password is valid", }, { "preset-computer", adcli_tool_computer_preset, "Pre setup computers accounts", }, { "reset-computer", adcli_tool_computer_reset, "Reset a computer account", }, { "delete-computer", adcli_tool_computer_delete, "Delete a computer account", }, { "create-user", adcli_tool_user_create, "Create a user account", }, { "delete-user", adcli_tool_user_delete, "Delete a user account", }, { "create-group", adcli_tool_group_create, "Create a group", }, { "delete-group", adcli_tool_group_delete, "Delete a group", }, { "add-member", adcli_tool_member_add, "Add users to a group", }, { "remove-member", adcli_tool_member_remove, "Remove users from a group", }, { 0, } }; static char short_option (int opt) { if (isalpha (opt) || isdigit (opt)) return (char)opt; return 0; } static const struct option * find_option (const struct option *longopts, int opt) { int i; for (i = 0; longopts[i].name != NULL; i++) { if (longopts[i].val == opt) return longopts + i; } return NULL; } void adcli_tool_usage (const struct option *longopts, const adcli_tool_desc *usages) { const struct option *longopt; const int indent = 28; const char *description; const char *next; char short_name; int spaces; int len; int i; for (i = 0; usages[i].text != NULL; i++) { /* If no option, then this is a heading */ if (!usages[i].option) { printf ("%s\n\n", usages[i].text); continue; } /* Only print out options we can actually parse */ longopt = find_option (longopts, usages[i].option); if (!longopt) continue; short_name = short_option (usages[i].option); if (short_name && longopt->name) len = printf (" -%c, --%s", (int)short_name, longopt->name); else if (longopt->name) len = printf (" --%s", longopt->name); else len = printf (" -%c", (int)short_name); if (longopt->has_arg) len += printf ("%s<%s>", longopt->name ? "=" : " ", usages[i].arg ? usages[i].arg : "..."); if (len < indent) { spaces = indent - len; } else { printf ("\n"); spaces = indent; } description = usages[i].text; while (description) { while (spaces-- > 0) fputc (' ', stdout); next = strchr (description, '\n'); if (next) { next += 1; printf ("%.*s", (int)(next - description), description); description = next; spaces = indent; } else { printf ("%s\n", description); break; } } } } int adcli_tool_getopt (int argc, char *argv[], const struct option *options) { int count = 0; char *shorts; char *p; int ret; char opt; int i; /* Number of characters */ for (i = 0; options[i].name != NULL; i++) count++; p = shorts = malloc ((count * 2) + 1); return_val_if_fail (shorts != NULL, -1); for (i = 0; i < count; i++) { opt = short_option (options[i].val); if (opt != 0) { *(p++) = (char)options[i].val; if (options[i].has_arg == required_argument) *(p++) = ':'; } } *(p++) = '\0'; ret = getopt_long (argc, argv, shorts, options, NULL); free (shorts); return ret; } static void command_usage (void) { int i; printf ("usage: adcli command ...\n"); printf ("\nCommon adcli commands are:\n"); for (i = 0; commands[i].name != NULL; i++) printf (" %-15s %s\n", commands[i].name, commands[i].text); printf ("\nSee 'adcli --help' for more information\n"); } char * adcli_prompt_password_func (adcli_login_type login_type, const char *name, int flags, void *unused_data) { char *prompt; char *password; char *result; if (asprintf (&prompt, "Password for %s: ", name) < 0) return_val_if_reached (NULL); password = getpass (prompt); free (prompt); if (password == NULL) return NULL; result = strdup (password); adcli_mem_clear (password, strlen (password)); return result; } char * adcli_read_password_func (adcli_login_type login_type, const char *name, int flags, void *unused_data) { char *buffer = NULL; size_t length = 0; size_t offset = 0; ssize_t res; for (;;) { if (offset >= length) { length += 4096; buffer = realloc (buffer, length + 1); return_val_if_fail (buffer != NULL, NULL); } res = read (0, buffer + offset, length - offset); if (res < 0) { if (errno == EAGAIN || errno == EINTR) continue; err (EFAIL, "couldn't read password from stdin"); } else if (res == 0) { buffer[offset] = '\0'; /* remove new line character */ if (offset > 0 && buffer[offset - 1] == '\n') { buffer[offset - 1] = '\0'; if (offset > 1 && buffer[offset - 2] == '\r') { buffer[offset - 2] = '\0'; } } return buffer; } else { if (memchr (buffer + offset, 0, res)) errx (EUSAGE, "unsupported null character present in password"); offset += res; } } } static void cleanup_krb5_conf_directory (void) { if (adcli_krb5_d_directory) { rmdir (adcli_krb5_d_directory); free (adcli_krb5_d_directory); adcli_krb5_d_directory = NULL; } if (adcli_krb5_conf_filename) { unlink (adcli_krb5_conf_filename); free (adcli_krb5_conf_filename); adcli_krb5_conf_filename = NULL; } if (adcli_temp_directory) { rmdir (adcli_temp_directory); free (adcli_temp_directory); adcli_temp_directory = NULL; } unsetenv ("KRB5_CONFIG"); } static void setup_krb5_conf_directory (adcli_conn *conn) { const char *parent; const char *krb5_conf; char *filename = NULL; char *snippets = NULL; char *contents = NULL; char *directory = NULL; struct stat sb; int failed = 0; int errn = 0; FILE *fo; krb5_conf = getenv ("KRB5_CONFIG"); if (!krb5_conf || !krb5_conf[0]) krb5_conf = KRB5_CONFIG; parent = getenv ("TMPDIR"); if (!parent || !*parent) parent = _PATH_TMP; /* Check that the config file exists, don't include if not */ if (stat (krb5_conf, &sb) < 0) { if (errno != ENOENT) warn ("couldn't access file: %s", krb5_conf); krb5_conf = NULL; } if (asprintf (&directory, "%s%sadcli-krb5-XXXXXX", parent, (parent[0] && parent[strlen(parent) - 1] == '/') ? "" : "/") < 0) errx (1, "unexpected: out of memory"); if (mkdtemp (directory) == NULL) { errn = errno; failed = 1; warnx ("couldn't create temporary directory in: %s: %s", parent, strerror (errn)); } else { if (asprintf (&filename, "%s/krb5.conf", directory) < 0 || asprintf (&snippets, "%s/krb5.d", directory) < 0 || asprintf (&contents, "includedir %s\n%s%s\n", snippets, krb5_conf ? "include " : "", krb5_conf ? krb5_conf : "") < 0) errx (1, "unexpected: out of memory"); } if (!failed) { fo = fopen (filename, "wb"); if (fo == NULL) { errn = errno; failed = 1; } else { fwrite (contents, 1, strlen (contents), fo); if (ferror (fo)) { errn = errno; failed = 1; fclose (fo); } else { if (fclose (fo) != 0) { failed = 1; errn = errno; } } } if (failed) { warnx ("couldn't write new krb5.conf file: %s: %s", filename, strerror (errn)); } } if (!failed && mkdir (snippets, 0700) < 0) { errn = errno; failed = 1; warnx ("couldn't write new krb5.d directory: %s: %s", snippets, strerror (errn)); } if (!failed) { adcli_conn_set_krb5_conf_dir (conn, snippets); adcli_temp_directory = directory; adcli_krb5_conf_filename = filename; adcli_krb5_d_directory = snippets; setenv ("KRB5_CONFIG", adcli_krb5_conf_filename, 1); } else { free (filename); free (snippets); free (directory); } free (contents); atexit (cleanup_krb5_conf_directory); } static void message_func (adcli_message_type type, const char *message) { const char *prefix = ""; switch (type) { case ADCLI_MESSAGE_INFO: prefix = " * "; break; case ADCLI_MESSAGE_WARNING: case ADCLI_MESSAGE_ERROR: prefix = " ! "; break; } fprintf (stderr, "%s%s\n", prefix, message); } int main (int argc, char *argv[]) { adcli_conn *conn = NULL; char *command = NULL; int skip; int in, out; int ret; int i; /* * Parse the global options. We rearrange the options as * necessary, in order to pass relevant options through * to the commands, but also have them take effect globally. */ for (in = 1, out = 1; in < argc; in++, out++) { skip = 0; /* The non-option is the command, take it out of the arguments */ if (argv[in][0] != '-') { if (!command) { skip = 1; command = argv[in]; } /* The global long options */ } else if (argv[in][1] == '-') { skip = 0; if (strcmp (argv[in], "--") == 0) { if (!command) errx (2, "no command specified"); } else if (strcmp (argv[in], "--verbose") == 0) { adcli_set_message_func (message_func); } else if (strcmp (argv[in], "--help") == 0) { if (!command) { command_usage (); return 0; } } else { if (!command) errx (2, "unknown option: %s", argv[in]); } /* The global short options */ } else { skip = 0; for (i = 1; argv[in][i] != '\0'; i++) { switch (argv[in][i]) { case 'h': if (!command) { command_usage (); return 0; } break; case 'v': adcli_set_message_func (message_func); break; default: if (!command) errx (2, "unknown option: -%c", (int)argv[in][i]); break; } } } /* Skipping this argument? */ if (skip) out--; else argv[out] = argv[in]; } if (command == NULL) { /* As a special favor if someone just typed 'adcli', help them out */ if (argc == 1) command_usage (); else warnx ("no command specified"); return 2; } argc = out; conn = NULL; /* Look for the command */ for (i = 0; commands[i].name != NULL; i++) { if (strcmp (commands[i].name, command) != 0) continue; if (!(commands[i].flags & CONNECTION_LESS)) { conn = adcli_conn_new (NULL); if (conn == NULL) errx (-1, "unexpected memory problems"); adcli_conn_set_password_func (conn, adcli_prompt_password_func, NULL, NULL); setup_krb5_conf_directory (conn); } argv[0] = command; ret = (commands[i].function) (conn, argc, argv); if (conn) adcli_conn_unref (conn); return ret; } /* At this point we have no command */ errx (2, "'%s' is not a valid adcli command. See 'adcli --help'", command); }