summaryrefslogtreecommitdiffstats
path: root/client/ipa-join.c
diff options
context:
space:
mode:
Diffstat (limited to 'client/ipa-join.c')
-rw-r--r--client/ipa-join.c1161
1 files changed, 1161 insertions, 0 deletions
diff --git a/client/ipa-join.c b/client/ipa-join.c
new file mode 100644
index 000000000..ac8251fef
--- /dev/null
+++ b/client/ipa-join.c
@@ -0,0 +1,1161 @@
+/* Authors: Rob Crittenden <rcritten@redhat.com>
+ *
+ * Copyright (C) 2009 Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include "config.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/utsname.h>
+#include <krb5.h>
+/* Doesn't work w/mozldap */
+#include <ldap.h>
+#include <popt.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#include "xmlrpc-c/base.h"
+#include "xmlrpc-c/client.h"
+
+#include "ipa-client-common.h"
+
+#define NAME "ipa-join"
+
+#define JOIN_OID "2.16.840.1.113730.3.8.10.3"
+
+#define CAFILE "/etc/ipa/ca.crt"
+
+#define IPA_CONFIG "/etc/ipa/default.conf"
+
+char * read_config_file(const char *filename);
+char * get_config_entry(char * data, const char *section, const char *key);
+
+static int debug = 0;
+
+/*
+ * Translate some IPA exceptions into specific errors in this context.
+ */
+static int
+handle_fault(xmlrpc_env * const envP) {
+ if (envP->fault_occurred) {
+ switch(envP->fault_code) {
+ case 2100: /* unable to add new host entry or write objectClass */
+ fprintf(stderr,
+ _("No permission to join this host to the IPA domain.\n"));
+ break;
+ default:
+ fprintf(stderr, "%s\n", envP->fault_string);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+/* Get the IPA server from the configuration file.
+ * The caller is responsible for freeing this value
+ */
+static char *
+getIPAserver(char * data) {
+ return get_config_entry(data, "global", "server");
+}
+
+/* Make sure that the keytab is writable before doing anything */
+static int check_perms(const char *keytab)
+{
+ int ret;
+ int fd;
+
+ ret = access(keytab, W_OK);
+ if (ret == -1) {
+ switch(errno) {
+ case EACCES:
+ fprintf(stderr,
+ _("No write permissions on keytab file '%s'\n"),
+ keytab);
+ break;
+ case ENOENT:
+ /* file doesn't exist, lets touch it and see if writable */
+ fd = open(keytab, O_WRONLY | O_CREAT, 0600);
+ if (fd != -1) {
+ close(fd);
+ unlink(keytab);
+ return 0;
+ }
+ fprintf(stderr,
+ _("No write permissions on keytab file '%s'\n"),
+ keytab);
+ break;
+ default:
+ fprintf(stderr,
+ _("access() on %1$s failed: errno = %2$d\n"),
+ keytab, errno);
+ break;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * There is no API in xmlrpc-c to set arbitrary headers but we can fake it
+ * by using a specially-crafted User-Agent string.
+ *
+ * The caller is responsible for freeing the return value.
+ */
+char *
+set_user_agent(const char *ipaserver) {
+ int ret;
+ char *user_agent = NULL;
+
+ ret = asprintf(&user_agent, "%s/%s\r\nReferer: https://%s/ipa/xml\r\nX-Original-User-Agent:", NAME, VERSION, ipaserver);
+ if (ret == -1) {
+ fprintf(stderr, _("Out of memory!"));
+ return NULL;
+ }
+ return user_agent;
+}
+
+/*
+ * Make an XML-RPC call to methodName. This uses the curl client to make
+ * a connection over SSL using the CA cert that should have been installed
+ * by ipa-client-install.
+ */
+static void
+callRPC(char * user_agent,
+ xmlrpc_env * const envP,
+ xmlrpc_server_info * const serverInfoP,
+ const char * const methodName,
+ xmlrpc_value * const paramArrayP,
+ xmlrpc_value ** const resultPP) {
+
+ struct xmlrpc_clientparms clientparms;
+ struct xmlrpc_curl_xportparms * curlXportParmsP = NULL;
+ xmlrpc_client * clientP = NULL;
+
+ memset(&clientparms, 0, sizeof(clientparms));
+
+ XMLRPC_ASSERT(xmlrpc_value_type(paramArrayP) == XMLRPC_TYPE_ARRAY);
+
+ curlXportParmsP = malloc(sizeof(*curlXportParmsP));
+ if (curlXportParmsP == NULL) {
+ xmlrpc_env_set_fault(envP, XMLRPC_INTERNAL_ERROR, _("Out of memory!"));
+ return;
+ }
+ memset(curlXportParmsP, 0, sizeof(*curlXportParmsP));
+
+ /* Have curl do SSL certificate validation */
+ curlXportParmsP->no_ssl_verifypeer = 0;
+ curlXportParmsP->no_ssl_verifyhost = 0;
+ curlXportParmsP->cainfo = "/etc/ipa/ca.crt";
+ curlXportParmsP->user_agent = user_agent;
+ /* Enable GSSAPI credentials delegation */
+ curlXportParmsP->gssapi_delegation = 1;
+
+ clientparms.transport = "curl";
+ clientparms.transportparmsP = (struct xmlrpc_xportparms *)
+ curlXportParmsP;
+ clientparms.transportparm_size = XMLRPC_CXPSIZE(gssapi_delegation);
+ xmlrpc_client_create(envP, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION,
+ &clientparms, sizeof(clientparms),
+ &clientP);
+
+ /* Set up kerberos negotiate authentication in curl. */
+ xmlrpc_server_info_set_user(envP, serverInfoP, ":", "");
+ xmlrpc_server_info_allow_auth_negotiate(envP, serverInfoP);
+
+ /* Perform the XML-RPC call */
+ if (!envP->fault_occurred) {
+ xmlrpc_client_call2(envP, clientP, serverInfoP, methodName, paramArrayP, resultPP);
+ }
+
+ /* Cleanup */
+ xmlrpc_server_info_free(serverInfoP);
+ xmlrpc_client_destroy(clientP);
+ free((void*)clientparms.transportparmsP);
+}
+
+/* The caller is responsible for unbinding the connection if ld is not NULL */
+static LDAP *
+connect_ldap(const char *hostname, const char *binddn, const char *bindpw) {
+ LDAP *ld = NULL;
+ int ssl = LDAP_OPT_X_TLS_HARD;
+ int version = LDAP_VERSION3;
+ int ret;
+ int ldapdebug = 0;
+ char *uri;
+ struct berval bindpw_bv;
+
+ if (debug) {
+ ldapdebug = 2;
+ ret = ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ldapdebug);
+ if (ret != LDAP_OPT_SUCCESS) {
+ goto fail;
+ }
+ }
+
+ if (ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, CAFILE) != LDAP_OPT_SUCCESS)
+ goto fail;
+
+ ret = asprintf(&uri, "ldaps://%s:636", hostname);
+ if (ret == -1) {
+ fprintf(stderr, _("Out of memory!"));
+ goto fail;
+ }
+
+ ret = ldap_initialize(&ld, uri);
+ free(uri);
+ if(ret != LDAP_SUCCESS) {
+ fprintf(stderr, _("Unable to initialize connection to ldap server: %s"),
+ ldap_err2string(ret));
+ goto fail;
+ }
+
+ if (ldap_set_option(ld, LDAP_OPT_X_TLS, &ssl) != LDAP_OPT_SUCCESS) {
+ fprintf(stderr, _("Unable to enable SSL in LDAP\n"));
+ goto fail;
+ }
+
+ /* Don't do DNS canonicalization */
+ ret = ldap_set_option(ld, LDAP_OPT_X_SASL_NOCANON, LDAP_OPT_ON);
+ if (ret != LDAP_SUCCESS) {
+ fprintf(stderr, _("Unable to set LDAP_OPT_X_SASL_NOCANON\n"));
+ goto fail;
+ }
+
+ ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
+ if (ret != LDAP_SUCCESS) {
+ fprintf(stderr, _("Unable to set LDAP version\n"));
+ goto fail;
+ }
+
+ if (bindpw) {
+ bindpw_bv.bv_val = discard_const(bindpw);
+ bindpw_bv.bv_len = strlen(bindpw);
+ } else {
+ bindpw_bv.bv_val = NULL;
+ bindpw_bv.bv_len = 0;
+ }
+
+ ret = ldap_sasl_bind_s(ld, binddn, LDAP_SASL_SIMPLE, &bindpw_bv,
+ NULL, NULL, NULL);
+
+ if (ret != LDAP_SUCCESS) {
+ int err;
+
+ ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &err);
+ if (debug)
+ fprintf(stderr, _("Bind failed: %s\n"), ldap_err2string(err));
+ goto fail;
+ }
+
+ return ld;
+
+fail:
+ if (ld != NULL) {
+ ldap_unbind_ext(ld, NULL, NULL);
+ }
+ return NULL;
+}
+
+/*
+ * Given a list of naming contexts check each one to see if it has
+ * an IPA v2 server in it. The first one we find wins.
+ */
+static int
+check_ipa_server(LDAP *ld, char **ldap_base, struct berval **vals)
+{
+ struct berval **infovals;
+ LDAPMessage *entry, *res = NULL;
+ char *info_attrs[] = {"info", NULL};
+ int i, ret = 0;
+
+ for (i = 0; !*ldap_base && vals[i]; i++) {
+ ret = ldap_search_ext_s(ld, vals[i]->bv_val,
+ LDAP_SCOPE_BASE, "(info=IPA*)", info_attrs,
+ 0, NULL, NULL, NULL, 0, &res);
+
+ if (ret != LDAP_SUCCESS) {
+ break;
+ }
+
+ entry = ldap_first_entry(ld, res);
+ infovals = ldap_get_values_len(ld, entry, info_attrs[0]);
+ if (!strcmp(infovals[0]->bv_val, "IPA V2.0"))
+ *ldap_base = strdup(vals[i]->bv_val);
+ ldap_msgfree(res);
+ res = NULL;
+ }
+
+ return ret;
+}
+
+/*
+ * Determine the baseDN of the remote server. Look first for a
+ * defaultNamingContext, otherwise fall back to reviewing each
+ * namingContext.
+ */
+static int
+get_root_dn(const char *ipaserver, char **ldap_base)
+{
+ LDAP *ld = NULL;
+ char *root_attrs[] = {"namingContexts", "defaultNamingContext", NULL};
+ LDAPMessage *entry, *res = NULL;
+ struct berval **ncvals;
+ struct berval **defvals;
+ int ret, rval = 0;
+
+ ld = connect_ldap(ipaserver, NULL, NULL);
+ if (!ld) {
+ rval = 14;
+ goto done;
+ }
+
+ ret = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE,
+ "objectclass=*", root_attrs, 0,
+ NULL, NULL, NULL, 0, &res);
+
+ if (ret != LDAP_SUCCESS) {
+ fprintf(stderr, _("Search for %1$s on rootdse failed with error %2$d\n"),
+ root_attrs[0], ret);
+ rval = 14;
+ goto done;
+ }
+
+ *ldap_base = NULL;
+
+ entry = ldap_first_entry(ld, res);
+
+ defvals = ldap_get_values_len(ld, entry, root_attrs[1]);
+ if (defvals) {
+ ret = check_ipa_server(ld, ldap_base, defvals);
+ }
+ ldap_value_free_len(defvals);
+
+ /* loop through to find the IPA context */
+ if (ret == LDAP_SUCCESS && !*ldap_base) {
+ ncvals = ldap_get_values_len(ld, entry, root_attrs[0]);
+ if (!ncvals) {
+ fprintf(stderr, _("No values for %s"), root_attrs[0]);
+ rval = 14;
+ ldap_value_free_len(ncvals);
+ goto done;
+ }
+ ret = check_ipa_server(ld, ldap_base, ncvals);
+ ldap_value_free_len(ncvals);
+ }
+
+ if (ret != LDAP_SUCCESS) {
+ fprintf(stderr, _("Search for IPA namingContext failed with error %d\n"), ret);
+ rval = 14;
+ goto done;
+ }
+
+ if (!*ldap_base) {
+ fprintf(stderr, _("IPA namingContext not found\n"));
+ rval = 14;
+ goto done;
+ }
+
+
+done:
+ if (res) ldap_msgfree(res);
+ if (ld != NULL) {
+ ldap_unbind_ext(ld, NULL, NULL);
+ }
+
+ return rval;
+}
+
+/*
+ * Get the certificate subject base from the IPA configuration.
+ *
+ * Not considered a show-stopper if this fails for some reason.
+ *
+ * The caller is responsible for binding/unbinding to LDAP.
+ */
+static int
+get_subject(LDAP *ld, char *ldap_base, const char **subject, int quiet)
+{
+ char *attrs[] = {"ipaCertificateSubjectBase", NULL};
+ char *base = NULL;
+ LDAPMessage *entry, *res = NULL;
+ struct berval **ncvals;
+ int ret, rval = 0;
+
+ ret = asprintf(&base, "cn=ipaconfig,cn=etc,%s", ldap_base);
+ if (ret == -1)
+ {
+ if (!quiet)
+ fprintf(stderr, _("Out of memory!\n"));
+ rval = 3;
+ goto done;
+ }
+
+ ret = ldap_search_ext_s(ld, base, LDAP_SCOPE_BASE,
+ "objectclass=*", attrs, 0,
+ NULL, NULL, NULL, 0, &res);
+
+ if (ret != LDAP_SUCCESS) {
+ fprintf(stderr,
+ _("Search for ipaCertificateSubjectBase failed with error %d"),
+ ret);
+ rval = 14;
+ goto done;
+ }
+
+ entry = ldap_first_entry(ld, res);
+ ncvals = ldap_get_values_len(ld, entry, attrs[0]);
+ if (!ncvals) {
+ fprintf(stderr, _("No values for %s"), attrs[0]);
+ rval = 14;
+ goto done;
+ }
+
+ *subject = strdup(ncvals[0]->bv_val);
+
+ ldap_value_free_len(ncvals);
+
+done:
+ free(base);
+ if (res) ldap_msgfree(res);
+
+ return rval;
+}
+
+/* Join a host to the current IPA realm.
+ *
+ * There are several scenarios for this:
+ * 1. You are an IPA admin user with fullrights to add hosts and generate
+ * keytabs.
+ * 2. You are an IPA admin user with rights to generate keytabs but not
+ * write hosts.
+ * 3. You are a regular IPA user with a password that can be used to
+ * generate the host keytab.
+ *
+ * If a password is presented it will be used regardless of the rights of
+ * the user.
+ */
+
+/* If we only have a bindpw then try to join in a bit of a degraded mode.
+ * This is going to duplicate some of the server-side code to determine
+ * the state of the entry.
+ */
+static int
+join_ldap(const char *ipaserver, char *hostname, char ** binddn, const char *bindpw, const char *basedn, const char **princ, const char **subject, int quiet)
+{
+ LDAP *ld;
+ int rval = 0;
+ char *oidresult = NULL;
+ struct berval valrequest;
+ struct berval *valresult = NULL;
+ int rc, ret;
+ char *ldap_base = NULL;
+
+ *binddn = NULL;
+ *princ = NULL;
+ *subject = NULL;
+
+ if (NULL != basedn) {
+ ldap_base = strdup(basedn);
+ if (!ldap_base) {
+ if (!quiet)
+ fprintf(stderr, _("Out of memory!\n"));
+ rval = 3;
+ goto done;
+ }
+ } else {
+ if (get_root_dn(ipaserver, &ldap_base) != 0) {
+ if (!quiet)
+ fprintf(stderr, _("Unable to determine root DN of %s\n"),
+ ipaserver);
+ rval = 14;
+ goto done;
+ }
+ }
+
+ ret = asprintf(binddn, "fqdn=%s,cn=computers,cn=accounts,%s", hostname, ldap_base);
+ if (ret == -1)
+ {
+ if (!quiet)
+ fprintf(stderr, _("Out of memory!\n"));
+ rval = 3;
+ goto done;
+ }
+ ld = connect_ldap(ipaserver, *binddn, bindpw);
+ if (!ld) {
+ if (!quiet)
+ fprintf(stderr, _("Incorrect password.\n"));
+ rval = 15;
+ goto done;
+ }
+
+ if (get_subject(ld, ldap_base, subject, quiet) != 0) {
+ if (!quiet)
+ fprintf(stderr,
+ _("Unable to determine certificate subject of %s\n"),
+ ipaserver);
+ /* Not a critical failure */
+ }
+
+ valrequest.bv_val = (char *)hostname;
+ valrequest.bv_len = strlen(hostname);
+
+ if ((rc = ldap_extended_operation_s(ld, JOIN_OID, &valrequest, NULL, NULL, &oidresult, &valresult)) != LDAP_SUCCESS) {
+ char *s = NULL;
+#ifdef LDAP_OPT_DIAGNOSTIC_MESSAGE
+ ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, &s);
+#else
+ ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &s);
+#endif
+ if (!quiet)
+ fprintf(stderr, _("Enrollment failed. %s\n"), s);
+ if (debug) {
+ fprintf(stderr, "ldap_extended_operation_s failed: %s",
+ ldap_err2string(rc));
+ }
+ rval = 13;
+ goto ldap_done;
+ }
+
+ /* Get the value from the result returned by the server. */
+ *princ = strdup(valresult->bv_val);
+
+ldap_done:
+ if (ld != NULL) {
+ ldap_unbind_ext(ld, NULL, NULL);
+ }
+
+done:
+ free(ldap_base);
+ if (valresult) ber_bvfree(valresult);
+ if (oidresult) free(oidresult);
+ return rval;
+}
+
+static int
+join_krb5(const char *ipaserver, char *hostname, char **hostdn, const char **princ, const char **subject, int force, int quiet) {
+ xmlrpc_env env;
+ xmlrpc_value * argArrayP = NULL;
+ xmlrpc_value * paramArrayP = NULL;
+ xmlrpc_value * paramP = NULL;
+ xmlrpc_value * optionsP = NULL;
+ xmlrpc_value * resultP = NULL;
+ xmlrpc_value * structP = NULL;
+ xmlrpc_server_info * serverInfoP = NULL;
+ struct utsname uinfo;
+ xmlrpc_value *princP = NULL;
+ xmlrpc_value *krblastpwdchangeP = NULL;
+ xmlrpc_value *subjectP = NULL;
+ xmlrpc_value *hostdnP = NULL;
+ const char *krblastpwdchange = NULL;
+ char * url = NULL;
+ char * user_agent = NULL;
+ int rval = 0;
+ int ret;
+
+ *hostdn = NULL;
+ *subject = NULL;
+ *princ = NULL;
+
+ /* Start up our XML-RPC client library. */
+ xmlrpc_client_init(XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION);
+
+ uname(&uinfo);
+
+ xmlrpc_env_init(&env);
+
+ xmlrpc_client_setup_global_const(&env);
+
+#if 1
+ ret = asprintf(&url, "https://%s:443/ipa/xml", ipaserver);
+#else
+ ret = asprintf(&url, "http://%s:8888/", ipaserver);
+#endif
+ if (ret == -1)
+ {
+ if (!quiet)
+ fprintf(stderr, _("Out of memory!\n"));
+ rval = 3;
+ goto cleanup;
+ }
+
+ serverInfoP = xmlrpc_server_info_new(&env, url);
+
+ argArrayP = xmlrpc_array_new(&env);
+ paramArrayP = xmlrpc_array_new(&env);
+
+ if (hostname == NULL)
+ paramP = xmlrpc_string_new(&env, uinfo.nodename);
+ else
+ paramP = xmlrpc_string_new(&env, hostname);
+ xmlrpc_array_append_item(&env, argArrayP, paramP);
+#ifdef REALM
+ if (!quiet)
+ printf("Joining %s to IPA realm %s\n", uinfo.nodename, iparealm);
+#endif
+ xmlrpc_array_append_item(&env, paramArrayP, argArrayP);
+ xmlrpc_DECREF(paramP);
+
+ optionsP = xmlrpc_build_value(&env, "{s:s,s:s}",
+ "nsosversion", uinfo.release,
+ "nshardwareplatform", uinfo.machine);
+ xmlrpc_array_append_item(&env, paramArrayP, optionsP);
+ xmlrpc_DECREF(optionsP);
+
+ if ((user_agent = set_user_agent(ipaserver)) == NULL) {
+ rval = 3;
+ goto cleanup;
+ }
+ callRPC(user_agent, &env, serverInfoP, "join", paramArrayP, &resultP);
+ if (handle_fault(&env)) {
+ rval = 17;
+ goto cleanup_xmlrpc;
+ }
+
+ /* Return value is the form of an array. The first value is the
+ * DN, the second a struct of attribute values
+ */
+ xmlrpc_array_read_item(&env, resultP, 0, &hostdnP);
+ xmlrpc_read_string(&env, hostdnP, (const char **)hostdn);
+ xmlrpc_DECREF(hostdnP);
+ xmlrpc_array_read_item(&env, resultP, 1, &structP);
+
+ xmlrpc_struct_find_value(&env, structP, "krbprincipalname", &princP);
+ if (princP) {
+ xmlrpc_value * singleprincP = NULL;
+
+ /* FIXME: all values are returned as lists currently. Once this is
+ * fixed we can read the string directly.
+ */
+ xmlrpc_array_read_item(&env, princP, 0, &singleprincP);
+ xmlrpc_read_string(&env, singleprincP, &*princ);
+ xmlrpc_DECREF(princP);
+ xmlrpc_DECREF(singleprincP);
+ } else {
+ if (!quiet)
+ fprintf(stderr, _("principal not found in XML-RPC response\n"));
+ rval = 12;
+ goto cleanup;
+ }
+ xmlrpc_struct_find_value(&env, structP, "krblastpwdchange", &krblastpwdchangeP);
+ if (krblastpwdchangeP && !force) {
+ xmlrpc_value * singleprincP = NULL;
+
+ /* FIXME: all values are returned as lists currently. Once this is
+ * fixed we can read the string directly.
+ */
+ xmlrpc_array_read_item(&env, krblastpwdchangeP, 0, &singleprincP);
+ xmlrpc_read_string(&env, singleprincP, &krblastpwdchange);
+ xmlrpc_DECREF(krblastpwdchangeP);
+ if (!quiet)
+ fprintf(stderr, _("Host is already joined.\n"));
+ rval = 13;
+ goto cleanup;
+ }
+
+ xmlrpc_struct_find_value(&env, structP, "ipacertificatesubjectbase", &subjectP);
+ if (subjectP) {
+ xmlrpc_value * singleprincP = NULL;
+
+ /* FIXME: all values are returned as lists currently. Once this is
+ * fixed we can read the string directly.
+ */
+ xmlrpc_array_read_item(&env, subjectP, 0, &singleprincP);
+ xmlrpc_read_string(&env, singleprincP, *&subject);
+ xmlrpc_DECREF(subjectP);
+ }
+
+cleanup:
+ if (argArrayP) xmlrpc_DECREF(argArrayP);
+ if (paramArrayP) xmlrpc_DECREF(paramArrayP);
+ if (resultP) xmlrpc_DECREF(resultP);
+
+cleanup_xmlrpc:
+ free(user_agent);
+ free(url);
+ free((char *)krblastpwdchange);
+ xmlrpc_env_clean(&env);
+ xmlrpc_client_cleanup();
+
+ return rval;
+}
+
+static int
+unenroll_host(const char *server, const char *hostname, const char *ktname, int quiet)
+{
+ int rval = 0;
+ int ret;
+ char *ipaserver = NULL;
+ char *host = NULL;
+ struct utsname uinfo;
+ char *principal = NULL;
+ char *realm = NULL;
+
+ krb5_context krbctx = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_principal princ = NULL;
+ krb5_error_code krberr;
+ krb5_creds creds;
+ krb5_get_init_creds_opt gicopts;
+ char tgs[LINE_MAX];
+
+ xmlrpc_env env;
+ xmlrpc_value * argArrayP = NULL;
+ xmlrpc_value * paramArrayP = NULL;
+ xmlrpc_value * paramP = NULL;
+ xmlrpc_value * resultP = NULL;
+ xmlrpc_server_info * serverInfoP = NULL;
+ xmlrpc_value *princP = NULL;
+ char * url = NULL;
+ char * user_agent = NULL;
+
+ /* Start up our XML-RPC client library. */
+ xmlrpc_client_init(XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION);
+
+ xmlrpc_env_init(&env);
+
+ xmlrpc_client_setup_global_const(&env);
+
+ if (server) {
+ ipaserver = strdup(server);
+ } else {
+ char * conf_data = read_config_file(IPA_CONFIG);
+ if ((ipaserver = getIPAserver(conf_data)) == NULL) {
+ if (!quiet)
+ fprintf(stderr, _("Unable to determine IPA server from %s\n"),
+ IPA_CONFIG);
+ exit(1);
+ }
+ free(conf_data);
+ }
+
+ if (NULL == hostname) {
+ uname(&uinfo);
+ host = strdup(uinfo.nodename);
+ } else {
+ host = strdup(hostname);
+ }
+
+ if (NULL == host) {
+ rval = 3;
+ goto cleanup;
+ }
+
+ if (NULL == strstr(host, ".")) {
+ if (!quiet)
+ fprintf(stderr, _("The hostname must be fully-qualified: %s\n"),
+ host);
+ rval = 16;
+ goto cleanup;
+ }
+
+ krberr = krb5_init_context(&krbctx);
+ if (krberr) {
+ if (!quiet)
+ fprintf(stderr, _("Unable to join host: "
+ "Kerberos context initialization failed\n"));
+ rval = 1;
+ goto cleanup;
+ }
+ krberr = krb5_kt_resolve(krbctx, ktname, &keytab);
+ if (krberr != 0) {
+ if (!quiet)
+ fprintf(stderr, _("Error resolving keytab: %s.\n"),
+ error_message(krberr));
+ rval = 7;
+ goto cleanup;
+ }
+
+ krberr = krb5_get_default_realm(krbctx, &realm);
+ if (krberr != 0) {
+ if (!quiet)
+ fprintf(stderr, _("Error getting default Kerberos realm: %s.\n"),
+ error_message(krberr));
+ rval = 21;
+ goto cleanup;
+ }
+
+ ret = asprintf(&principal, "host/%s@%s", host, realm);
+ if (ret == -1)
+ {
+ if (!quiet)
+ fprintf(stderr, _("Out of memory!\n"));
+ rval = 3;
+ goto cleanup;
+ }
+
+ krberr = krb5_parse_name(krbctx, principal, &princ);
+ if (krberr != 0) {
+ if (!quiet)
+ fprintf(stderr, _("Error parsing \"%1$s\": %2$s.\n"),
+ principal, error_message(krberr));
+ rval = 4;
+ goto cleanup;
+ }
+ strcpy(tgs, KRB5_TGS_NAME);
+ snprintf(tgs + strlen(tgs), sizeof(tgs) - strlen(tgs), "/%.*s",
+ (krb5_princ_realm(krbctx, princ))->length,
+ (krb5_princ_realm(krbctx, princ))->data);
+ snprintf(tgs + strlen(tgs), sizeof(tgs) - strlen(tgs), "@%.*s",
+ (krb5_princ_realm(krbctx, princ))->length,
+ (krb5_princ_realm(krbctx, princ))->data);
+ memset(&creds, 0, sizeof(creds));
+ krb5_get_init_creds_opt_init(&gicopts);
+ krb5_get_init_creds_opt_set_forwardable(&gicopts, 1);
+ krberr = krb5_get_init_creds_keytab(krbctx, &creds, princ, keytab,
+ 0, tgs, &gicopts);
+ if (krberr != 0) {
+ if (!quiet)
+ fprintf(stderr, _("Error obtaining initial credentials: %s.\n"),
+ error_message(krberr));
+ rval = 19;
+ goto cleanup;
+ }
+
+ krberr = krb5_cc_resolve(krbctx, "MEMORY:ipa-join", &ccache);
+ if (krberr == 0) {
+ krberr = krb5_cc_initialize(krbctx, ccache, creds.client);
+ } else {
+ if (!quiet)
+ fprintf(stderr,
+ _("Unable to generate Kerberos Credential Cache\n"));
+ rval = 19;
+ goto cleanup;
+ }
+ krberr = krb5_cc_store_cred(krbctx, ccache, &creds);
+ if (krberr != 0) {
+ if (!quiet)
+ fprintf(stderr,
+ _("Error storing creds in credential cache: %s.\n"),
+ error_message(krberr));
+ rval = 19;
+ goto cleanup;
+ }
+ krb5_cc_close(krbctx, ccache);
+ ccache = NULL;
+ putenv("KRB5CCNAME=MEMORY:ipa-join");
+
+#if 1
+ ret = asprintf(&url, "https://%s:443/ipa/xml", ipaserver);
+#else
+ ret = asprintf(&url, "http://%s:8888/", ipaserver);
+#endif
+ if (ret == -1)
+ {
+ if (!quiet)
+ fprintf(stderr, _("Out of memory!\n"));
+ rval = 3;
+ goto cleanup;
+ }
+ serverInfoP = xmlrpc_server_info_new(&env, url);
+
+ argArrayP = xmlrpc_array_new(&env);
+ paramArrayP = xmlrpc_array_new(&env);
+
+ paramP = xmlrpc_string_new(&env, host);
+ xmlrpc_array_append_item(&env, argArrayP, paramP);
+ xmlrpc_array_append_item(&env, paramArrayP, argArrayP);
+ xmlrpc_DECREF(paramP);
+
+ if ((user_agent = set_user_agent(ipaserver)) == NULL) {
+ rval = 3;
+ goto cleanup;
+ }
+ callRPC(user_agent, &env, serverInfoP, "host_disable", paramArrayP, &resultP);
+ if (handle_fault(&env)) {
+ rval = 17;
+ goto cleanup;
+ }
+
+ xmlrpc_struct_find_value(&env, resultP, "result", &princP);
+ if (princP) {
+ xmlrpc_bool result;
+
+ xmlrpc_read_bool(&env, princP, &result);
+ if (result == 1) {
+ if (!quiet)
+ fprintf(stderr, _("Unenrollment successful.\n"));
+ } else {
+ if (!quiet)
+ fprintf(stderr, _("Unenrollment failed.\n"));
+ }
+
+ xmlrpc_DECREF(princP);
+ } else {
+ fprintf(stderr, _("result not found in XML-RPC response\n"));
+ rval = 20;
+ goto cleanup;
+ }
+
+cleanup:
+
+ free(user_agent);
+ if (keytab) krb5_kt_close(krbctx, keytab);
+ free(host);
+ free((char *)principal);
+ free((char *)ipaserver);
+ if (princ) krb5_free_principal(krbctx, princ);
+ if (ccache) krb5_cc_close(krbctx, ccache);
+ if (krbctx) krb5_free_context(krbctx);
+
+ free(url);
+ xmlrpc_env_clean(&env);
+ xmlrpc_client_cleanup();
+
+ return rval;
+}
+
+
+static int
+join(const char *server, const char *hostname, const char *bindpw, const char *basedn, const char *keytab, int force, int quiet)
+{
+ int rval = 0;
+ pid_t childpid = 0;
+ int status = 0;
+ char *ipaserver = NULL;
+ char *iparealm = NULL;
+ char * host = NULL;
+ const char * princ = NULL;
+ const char * subject = NULL;
+ char * hostdn = NULL;
+ struct utsname uinfo;
+
+ krb5_context krbctx = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_principal uprinc = NULL;
+ krb5_error_code krberr;
+
+ if (server) {
+ ipaserver = strdup(server);
+ } else {
+ char * conf_data = read_config_file(IPA_CONFIG);
+ if ((ipaserver = getIPAserver(conf_data)) == NULL) {
+ fprintf(stderr, _("Unable to determine IPA server from %s\n"),
+ IPA_CONFIG);
+ exit(1);
+ }
+ free(conf_data);
+ }
+
+ if (NULL == hostname) {
+ uname(&uinfo);
+ host = strdup(uinfo.nodename);
+ } else {
+ host = strdup(hostname);
+ }
+
+ if (NULL == strstr(host, ".")) {
+ fprintf(stderr, _("The hostname must be fully-qualified: %s\n"), host);
+ rval = 16;
+ goto cleanup;
+ }
+
+ if ((!strcmp(host, "localhost")) || (!strcmp(host, "localhost.localdomain"))){
+ fprintf(stderr, _("The hostname must not be: %s\n"), host);
+ rval = 16;
+ goto cleanup;
+ }
+
+ if (bindpw)
+ rval = join_ldap(ipaserver, host, &hostdn, bindpw, basedn, &princ, &subject, quiet);
+ else {
+ krberr = krb5_init_context(&krbctx);
+ if (krberr) {
+ fprintf(stderr, _("Unable to join host: "
+ "Kerberos context initialization failed\n"));
+ rval = 1;
+ goto cleanup;
+ }
+ krberr = krb5_cc_default(krbctx, &ccache);
+ if (krberr) {
+ fprintf(stderr, _("Unable to join host:"
+ " Kerberos Credential Cache not found\n"));
+ rval = 5;
+ goto cleanup;
+ }
+
+ krberr = krb5_cc_get_principal(krbctx, ccache, &uprinc);
+ if (krberr) {
+ fprintf(stderr, _("Unable to join host: Kerberos User Principal "
+ "not found and host password not provided.\n"));
+ rval = 6;
+ goto cleanup;
+ }
+ rval = join_krb5(ipaserver, host, &hostdn, &princ, &subject, force,
+ quiet);
+ }
+
+ if (rval) goto cleanup;
+
+ /* Fork off and let ipa-getkeytab generate the keytab for us */
+ childpid = fork();
+
+ if (childpid < 0) {
+ fprintf(stderr, _("fork() failed\n"));
+ rval = 1;
+ goto cleanup;
+ }
+
+ if (childpid == 0) {
+ char *argv[12];
+ char *path = "/usr/sbin/ipa-getkeytab";
+ int arg = 0;
+ int err;
+
+ argv[arg++] = path;
+ argv[arg++] = "-s";
+ argv[arg++] = ipaserver;
+ argv[arg++] = "-p";
+ argv[arg++] = (char *)princ;
+ argv[arg++] = "-k";
+ argv[arg++] = (char *)keytab;
+ if (bindpw) {
+ argv[arg++] = "-D";
+ argv[arg++] = (char *)hostdn;
+ argv[arg++] = "-w";
+ argv[arg++] = (char *)bindpw;
+ }
+ argv[arg++] = NULL;
+ err = execv(path, argv);
+ if (err == -1) {
+ switch(errno) {
+ case ENOENT:
+ fprintf(stderr, _("ipa-getkeytab not found\n"));
+ break;
+ case EACCES:
+ fprintf(stderr, _("ipa-getkeytab has bad permissions?\n"));
+ break;
+ default:
+ fprintf(stderr, _("executing ipa-getkeytab failed, "
+ "errno %d\n"), errno);
+ break;
+ }
+ }
+ } else {
+ wait(&status);
+ }
+
+ if WIFEXITED(status) {
+ rval = WEXITSTATUS(status);
+ if (rval != 0) {
+ fprintf(stderr, _("child exited with %d\n"), rval);
+ }
+ }
+
+cleanup:
+ if (NULL != subject && !quiet && rval == 0)
+ fprintf(stderr, _("Certificate subject base is: %s\n"), subject);
+
+ free((char *)princ);
+ free((char *)subject);
+ free(host);
+
+ if (bindpw)
+ ldap_memfree((void *)hostdn);
+ else
+ free((char *)hostdn);
+
+ free((char *)ipaserver);
+ free((char *)iparealm);
+ if (uprinc) krb5_free_principal(krbctx, uprinc);
+ if (ccache) krb5_cc_close(krbctx, ccache);
+ if (krbctx) krb5_free_context(krbctx);
+
+ return rval;
+}
+
+/*
+ * Note, an intention with return values is so that this is compatible with
+ * ipa-getkeytab. This is so based on the return value you can distinguish
+ * between errors common between the two (no kerbeors ccache) and those
+ * unique (host already added).
+ */
+int
+main(int argc, const char **argv) {
+ static const char *hostname = NULL;
+ static const char *server = NULL;
+ static const char *keytab = NULL;
+ static const char *bindpw = NULL;
+ static const char *basedn = NULL;
+ int quiet = 0;
+ int unenroll = 0;
+ int force = 0;
+ struct poptOption options[] = {
+ { "debug", 'd', POPT_ARG_NONE, &debug, 0,
+ _("Print the raw XML-RPC output in GSSAPI mode"), NULL },
+ { "quiet", 'q', POPT_ARG_NONE, &quiet, 0,
+ _("Quiet mode. Only errors are displayed."), NULL },
+ { "unenroll", 'u', POPT_ARG_NONE, &unenroll, 0,
+ _("Unenroll this host from IPA server"), NULL },
+ { "hostname", 'h', POPT_ARG_STRING, &hostname, 0,
+ _("Hostname of this server"), _("hostname") },
+ { "server", 's', POPT_ARG_STRING, &server, 0,
+ _("IPA Server to use"), _("hostname") },
+ { "keytab", 'k', POPT_ARG_STRING, &keytab, 0,
+ _("Specifies where to store keytab information."), _("filename") },
+ { "force", 'f', POPT_ARG_NONE, &force, 0,
+ _("Force the host join. Rejoin even if already joined."), NULL },
+ { "bindpw", 'w', POPT_ARG_STRING, &bindpw, 0,
+ _("LDAP password (if not using Kerberos)"), _("password") },
+ { "basedn", 'b', POPT_ARG_STRING, &basedn, 0,
+ _("LDAP basedn"), _("basedn") },
+ POPT_AUTOHELP
+ POPT_TABLEEND
+ };
+ poptContext pc;
+ int ret;
+
+ ret = init_gettext();
+ if (ret) {
+ exit(2);
+ }
+
+ pc = poptGetContext("ipa-join", argc, (const char **)argv, options, 0);
+ ret = poptGetNextOpt(pc);
+ if (ret != -1) {
+ if (!quiet) {
+ poptPrintUsage(pc, stderr, 0);
+ }
+ exit(2);
+ }
+ poptFreeContext(pc);
+ if (debug)
+ setenv("XMLRPC_TRACE_XML", "1", 1);
+
+
+ if (!keytab)
+ keytab = "/etc/krb5.keytab";
+
+ if (unenroll) {
+ ret = unenroll_host(server, hostname, keytab, quiet);
+ } else {
+ ret = check_perms(keytab);
+ if (ret == 0)
+ ret = join(server, hostname, bindpw, basedn, keytab, force, quiet);
+ }
+
+ exit(ret);
+}