From d0587cbdd5bc5e07a6e8519deb07adaace643740 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Mon, 14 Sep 2009 17:04:08 -0400 Subject: Enrollment for a host in an IPA domain This will create a host service principal and may create a host entry (for admins). A keytab will be generated, by default in /etc/krb5.keytab If no kerberos credentails are available then enrollment over LDAPS is used if a password is provided. This change requires that openldap be used as our C LDAP client. It is much easier to do SSL using openldap than mozldap (no certdb required). Otherwise we'd have to write a slew of extra code to create a temporary cert database, import the CA cert, ... --- ipa-client/Makefile.am | 15 + ipa-client/config.c | 155 +++++++ ipa-client/configure.ac | 24 ++ ipa-client/ipa-getkeytab.c | 56 ++- ipa-client/ipa-install/ipa-client-install | 1 + ipa-client/ipa-join.c | 648 ++++++++++++++++++++++++++++++ 6 files changed, 889 insertions(+), 10 deletions(-) create mode 100644 ipa-client/config.c create mode 100644 ipa-client/ipa-join.c (limited to 'ipa-client') diff --git a/ipa-client/Makefile.am b/ipa-client/Makefile.am index 796a923fa..9a8b5f690 100644 --- a/ipa-client/Makefile.am +++ b/ipa-client/Makefile.am @@ -22,6 +22,7 @@ INCLUDES = \ sbin_PROGRAMS = \ ipa-getkeytab \ + ipa-join \ $(NULL) ipa_getkeytab_SOURCES = \ @@ -36,6 +37,20 @@ ipa_getkeytab_LDADD = \ $(POPT_LIBS) \ $(NULL) +ipa_join_SOURCES = \ + config.c \ + ipa-join.c \ + $(NULL) + +ipa_join_LDADD = \ + $(KRB5_LIBS) \ + $(OPENLDAP_LIBS) \ + $(SASL_LIBS) \ + $(CURL_LIBS) \ + $(XMLRPC_LIBS) \ + $(POPT_LIBS) \ + $(NULL) + SUBDIRS = \ firefox \ ipaclient \ diff --git a/ipa-client/config.c b/ipa-client/config.c new file mode 100644 index 000000000..81cb793db --- /dev/null +++ b/ipa-client/config.c @@ -0,0 +1,155 @@ +/* Authors: Rob Crittenden + * + * 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; version 2 only + * + * 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 + */ + +/* Simple and INI-style file reader. + * + * usage is: + * char * data = read_config_file("/path/to/something.conf") + * char * entry = get_config_entry(data, "section", "mykey") + * + * caller must free data and entry. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +char * +read_config_file(const char *filename) +{ + int fd; + struct stat st; + char *data, *dest; + size_t left; + + fd = open(filename, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "cannot open configuration file %s\n", filename); + return NULL; + } + + /* stat() the file so we know the size and can pre-allocate the right + * amount of memory. */ + if (fstat(fd, &st) == -1) { + fprintf(stderr, "cannot stat() configuration file %s\n", filename); + return NULL; + } + left = st.st_size; + data = malloc(st.st_size + 1); + dest = data; + while (left != 0) { + ssize_t res; + + res = read(fd, dest, left); + if (res == 0) + break; + if (res < 0) { + fprintf(stderr, "read error\n"); + close(fd); + free(dest); + return NULL; + } + dest += res; + left -= res; + } + close(fd); + *dest = 0; + return data; +} + +char * +get_config_entry(char * in_data, const char *section, const char *key) +{ + char *ptr, *p, *tmp; + char *line; + int in_section = 0; + char * data = strdup(in_data); + + for (line = strtok_r(data, "\n", &ptr); line != NULL; + line = strtok_r(NULL, "\n", &ptr)) { + /* Skip initial whitespace. */ + while (isspace((unsigned char)*line) && (*line != '\0')) + line++; + + /* If it's a comment, bail. */ + if (*line == '#') { + continue; + } + + /* If it's the beginning of a section, process it and clear the key + * and value values. */ + if (*line == '[') { + line++; + p = strchr(line, ']'); + if (p) { + tmp = strndup(line, p - line); + if (in_section) { + /* We exited the matching section without a match */ + free(data); + return NULL; + } + if (strcmp(section, tmp) == 0) { + free(tmp); + in_section = 1; + continue; + } + } + } /* [ */ + + p = strchr(line, '='); + if (p != NULL && in_section) { + /* Trim any trailing whitespace off the key name. */ + while (p != line && isspace((unsigned char)p[-1])) + p--; + + /* Save the key. */ + tmp = strndup(line, p - line); + if (strcmp(key, tmp) != 0) { + free(tmp); + } else { + free(tmp); + + /* Skip over any whitespace after the equal sign. */ + line = strchr(line, '='); + line++; + while (isspace((unsigned char)*line) && (*line != '\0')) + line++; + + /* Trim off any trailing whitespace. */ + p = strchr(line, '\0'); + while (p != line && isspace((unsigned char)p[-1])) + p--; + + /* Save the value. */ + tmp = strndup(line, p - line); + + free(data); + return tmp; + } + } + } + return NULL; +} diff --git a/ipa-client/configure.ac b/ipa-client/configure.ac index cce4e1865..c9d9e435e 100644 --- a/ipa-client/configure.ac +++ b/ipa-client/configure.ac @@ -156,6 +156,30 @@ if test "x$PYTHON" = "x" ; then AC_MSG_ERROR([Python not found]) fi +dnl --------------------------------------------------------------------------- +dnl - Check for CURL +dnl --------------------------------------------------------------------------- + +CURL_LIBS= +AC_CHECK_HEADER(curl/curl.h) +AC_CHECK_LIB(curl, curl_easy_init, [CURL_LIBS="-lcurl"]) +if test "x$CURL_LIBS" = "x" ; then + AC_MSG_ERROR([curl not found]) +fi +AC_SUBST(CURL_LIBS) + +dnl --------------------------------------------------------------------------- +dnl - Check for XMLRPC-C +dnl --------------------------------------------------------------------------- + +XMLRPC_LIBS= +AC_CHECK_HEADER(xmlrpc-c/base.h) +AC_CHECK_LIB(xmlrpc_client, xmlrpc_client_init2, [XMLRPC_LIBS="-lxmlrpc -lxmlrpc_client -lxmlrpc_util"]) +if test "x$XMLRPC_LIBS" = "x" ; then + AC_MSG_ERROR([xmlrpc-c not found]) +fi +AC_SUBST(XMLRPC_LIBS) + dnl --------------------------------------------------------------------------- dnl - Set the data install directory since we don't use pkgdatadir dnl --------------------------------------------------------------------------- diff --git a/ipa-client/ipa-getkeytab.c b/ipa-client/ipa-getkeytab.c index fbeb547a8..1bbb7759e 100644 --- a/ipa-client/ipa-getkeytab.c +++ b/ipa-client/ipa-getkeytab.c @@ -479,6 +479,8 @@ static int ldap_set_keytab(krb5_context krbctx, const char *servername, const char *principal_name, krb5_principal princ, + const char *binddn, + const char *bindpw, struct keys_container *keys) { int version; @@ -513,7 +515,20 @@ static int ldap_set_keytab(krb5_context krbctx, } /* TODO: support referrals ? */ - ld = ldap_init(servername, 389); + if (binddn) { + int ssl = LDAP_OPT_X_TLS_HARD;; + if (ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, "/etc/ipa/ca.crt") != LDAP_OPT_SUCCESS) { + goto error_out; + } + + ld = ldap_init(servername, 636); + if (ldap_set_option(ld, LDAP_OPT_X_TLS, &ssl) != LDAP_OPT_SUCCESS) { + goto error_out; + } + } else { + ld = ldap_init(servername, 389); + } + if(ld == NULL) { fprintf(stderr, "Unable to initialize ldap library!\n"); goto error_out; @@ -526,14 +541,22 @@ static int ldap_set_keytab(krb5_context krbctx, goto error_out; } - ret = ldap_sasl_interactive_bind_s(ld, - NULL, "GSSAPI", - NULL, NULL, - LDAP_SASL_QUIET, - ldap_sasl_interact, princ); - if (ret != LDAP_SUCCESS) { - fprintf(stderr, "SASL Bind failed!\n"); - goto error_out; + if (binddn) { + ret = ldap_bind_s(ld, binddn, bindpw, LDAP_AUTH_SIMPLE); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "Simple bind failed\n"); + goto error_out; + } + } else { + ret = ldap_sasl_interactive_bind_s(ld, + NULL, "GSSAPI", + NULL, NULL, + LDAP_SASL_QUIET, + ldap_sasl_interact, princ); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "SASL Bind failed!\n"); + goto error_out; + } } /* find base dn */ @@ -686,6 +709,8 @@ int main(int argc, char *argv[]) static const char *principal = NULL; static const char *keytab = NULL; static const char *enctypes_string = NULL; + static const char *binddn = NULL; + static const char *bindpw = NULL; int quiet = 0; int askpass = 0; int permitted_enctypes = 0; @@ -697,6 +722,8 @@ int main(int argc, char *argv[]) { "enctypes", 'e', POPT_ARG_STRING, &enctypes_string, 0, "Encryption types to request", "Comma separated encryption types list" }, { "permitted-enctypes", 0, POPT_ARG_NONE, &permitted_enctypes, 0, "Show the list of permitted encryption types and exit", "Permitted Encryption Types"}, { "password", 'P', POPT_ARG_NONE, &askpass, 0, "Asks for a non-random password to use for the principal" }, + { "binddn", 'D', POPT_ARG_STRING, &binddn, 0, "LDAP DN", "DN to bind as if not using kerberos" }, + { "bindpw", 'w', POPT_ARG_STRING, &bindpw, 0, "LDAP password", "password to use if not using kerberos" }, { NULL, 0, POPT_ARG_NONE, NULL, 0, NULL, NULL } }; poptContext pc; @@ -751,6 +778,13 @@ int main(int argc, char *argv[]) exit(2); } + if (NULL!=binddn && NULL==bindpw) { + fprintf(stderr, "Bind password required when using a bind DN.\n"); + if (!quiet) + poptPrintUsage(pc, stderr, 0); + exit(10); + } + if (askpass) { password = ask_password(krbctx); if (!password) { @@ -773,6 +807,7 @@ int main(int argc, char *argv[]) exit(4); } + if (NULL == bindpw) { krberr = krb5_cc_default(krbctx, &ccache); if (krberr) { fprintf(stderr, "Kerberos Credential Cache not found\n" @@ -786,6 +821,7 @@ int main(int argc, char *argv[]) "Do you have a valid Credential Cache?\n"); exit(6); } + } krberr = krb5_kt_resolve(krbctx, ktname, &kt); if (krberr) { @@ -800,7 +836,7 @@ int main(int argc, char *argv[]) exit(8); } - kvno = ldap_set_keytab(krbctx, server, principal, uprinc, &keys); + kvno = ldap_set_keytab(krbctx, server, principal, uprinc, binddn, bindpw, &keys); if (!kvno) { exit(9); } diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install index 7701086c0..1966c18c3 100644 --- a/ipa-client/ipa-install/ipa-client-install +++ b/ipa-client/ipa-install/ipa-client-install @@ -225,6 +225,7 @@ def main(): defopts = [{'name':'basedn', 'type':'option', 'value':cli_basedn}, {'name':'realm', 'type':'option', 'value':cli_realm}, {'name':'domain', 'type':'option', 'value':cli_domain}, + {'name':'server', 'type':'option', 'value':cli_server}, {'name':'xmlrpc_uri', 'type':'option', 'value':'https://%s/ipa/xml' % cli_server}] opts.append({'name':'global', 'type':'section', 'value':defopts}) diff --git a/ipa-client/ipa-join.c b/ipa-client/ipa-join.c new file mode 100644 index 000000000..d08d3b578 --- /dev/null +++ b/ipa-client/ipa-join.c @@ -0,0 +1,648 @@ +/* Authors: Rob Crittenden + * + * 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; version 2 only + * + * 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 + */ + +#define _GNU_SOURCE +#define LDAP_DEPRECATED 1 + +#include +#include +#include +#include +#include +#include +#include +#include +/* Doesn't work w/mozldap */ +#include +#include +#include +#include +#include + +#include "xmlrpc-c/base.h" +#include "xmlrpc-c/client.h" + +#define NAME "ipa-join" +#define VERSION "1.0" + +#define JOIN_OID "2.16.840.1.113730.3.8.3.53" + +#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"); +} + +/* Get the IPA realm from the configuration file. + * The caller is responsible for freeing this value + */ +static char * +getIPArealm(char * data) { + return get_config_entry(data, "global", "realm"); +} + +/* 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 %s failed: errno = %d\n", keytab, errno); + break; + } + return 1; + } + + return 0; +} + +/* + * 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(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; + + XMLRPC_ASSERT(xmlrpc_value_type(paramArrayP) == XMLRPC_TYPE_ARRAY); + + curlXportParmsP = malloc(sizeof(*curlXportParmsP)); + + /* Have curl do SSL certificate validation */ + curlXportParmsP->no_ssl_verifypeer = 1; + curlXportParmsP->no_ssl_verifyhost = 1; + curlXportParmsP->cainfo = "/etc/ipa/ca.crt"; + + clientparms.transport = "curl"; + clientparms.transportparmsP = (struct xmlrpc_xportparms *) + curlXportParmsP; + clientparms.transportparm_size = XMLRPC_CXPSIZE(cainfo); + xmlrpc_client_create(envP, XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION, + &clientparms, XMLRPC_CPSIZE(transportparm_size), + &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; + + if (ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, CAFILE) != LDAP_OPT_SUCCESS) + goto fail; + + ld = (LDAP *)ldap_init(hostname, 636); + if (ldap_set_option(ld, LDAP_OPT_X_TLS, &ssl) != LDAP_OPT_SUCCESS) { + fprintf(stderr, "Unable to enable SSL in LDAP\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; + } + + ret = ldap_bind_s(ld, binddn, bindpw, LDAP_AUTH_SIMPLE); + if (ret != LDAP_SUCCESS) { + if (debug) + fprintf(stderr, "Bind failed\n"); + goto fail; + } + + return ld; + +fail: + ldap_unbind_ext(ld, NULL, NULL); + return NULL; +} + +static int +get_root_dn(const char *ipaserver, char **ldap_base) +{ + LDAP *ld = NULL; + char *root_attrs[] = {"namingContexts", NULL}; + LDAPMessage *entry, *res = NULL; + struct berval **ncvals; + int ret, rval; + + ld = connect_ldap(ipaserver, NULL, NULL); + if (!ld) { + rval = 1; + 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 %s on rootdse failed with error %d", + root_attrs[0], ret); + goto done; + } + + /* for now just use the first result we get */ + entry = ldap_first_entry(ld, res); + ncvals = ldap_get_values_len(ld, entry, root_attrs[0]); + if (!ncvals) { + fprintf(stderr, "No values for %s", root_attrs[0]); + goto done; + } + + *ldap_base = strdup(ncvals[0]->bv_val); + + ldap_value_free_len(ncvals); + +done: + if (res) ldap_msgfree(res); + ldap_unbind_ext(ld, NULL, NULL); + + 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, const char *hostname, const char ** binddn, const char *bindpw, const char ** princ, int quiet) +{ + LDAP *ld; + char *filter = NULL; + int rval = 0; + char *oidresult; + struct berval valrequest; + struct berval *valresult = NULL; + int rc, ret; + LDAPMessage *result, *e; + char *ldap_base = NULL; + char *search_base = NULL; + char * attrs[] = {"krbPrincipalName", NULL}; + struct berval **ncvals; + int has_principal = 0; + + *binddn = NULL; + + get_root_dn(ipaserver, &ldap_base); + + ld = connect_ldap(ipaserver, NULL, NULL); + if (!ld) { + rval = 1; + goto done; + } + /* Search for the entry. */ + asprintf(&filter, "(fqdn=%s)", hostname); + asprintf(&search_base, "cn=computers,cn=accounts,%s", ldap_base); + if (debug) { + fprintf(stderr, "Searching with %s in %s\n", filter, search_base); + } + if ((ret = ldap_search_ext_s(ld, ldap_base, LDAP_SCOPE_SUB, + filter, attrs, 0, NULL, NULL, LDAP_NO_LIMIT, + LDAP_NO_LIMIT, &result)) != LDAP_SUCCESS) { + fprintf(stderr, "ldap_search_ext_s: %s\n", ldap_err2string(ret)); + rval = 1; + goto ldap_done; + } + e = ldap_first_entry(ld, result); + if (!e) { + fprintf(stderr, "Unable to find host '%s'\n", hostname); + rval = 1; + goto ldap_done; + } + if ((*binddn = ldap_get_dn(ld, e)) == NULL) { + fprintf(stderr, "Unable to get binddn for host '%s'\n", hostname); + rval = 1; + goto ldap_done; + } + ncvals = ldap_get_values_len(ld, e, attrs[0]); + if (ncvals != NULL) { + /* This host is probably already registered. The krbprincipalname + * is not set on password protected entries, but lets try to bind + * anyway. + */ + has_principal = 1; + if (debug) + fprintf(stderr, "Host already has principal, trying bind anyway\n"); + } + + ldap_value_free_len(ncvals); + ldap_msgfree(result); + ldap_unbind_ext(ld, NULL, NULL); + + /* Now rebind as the host */ + ld = connect_ldap(ipaserver, *binddn, bindpw); + if (!ld) { + if (has_principal) + fprintf(stderr, "Host is already joined.\n"); + else + fprintf(stderr, "Incorrect password.\n"); + rval = 1; + goto done; + } + + 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) { + fprintf(stderr, "principal not found in host entry\n"); + if (debug) ldap_perror(ld, "ldap_extended_operation_s"); + rval = 12; + goto ldap_done; + } + + /* Get the value from the result returned by the server. */ + *princ = strdup(valresult->bv_val); + +ldap_done: + + free(filter); + free(search_base); + free(ldap_base); + ldap_unbind_ext(ld, NULL, NULL); + +done: + if (valresult) ber_bvfree(valresult); + return rval; +} + +static int +join_krb5(const char *ipaserver, const char *hostname, const char **hostdn, const char **princ, int quiet) { + xmlrpc_env env; + 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 *hostdnP = NULL; + const char *krblastpwdchange = NULL; + char * url = NULL; + int rval = 0; + + /* Start up our XML-RPC client library. */ + xmlrpc_client_init(XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION); + + uname(&uinfo); + + xmlrpc_env_init(&env); + +#if 1 + asprintf(&url, "https://%s:443/ipa/xml", ipaserver); +#else + asprintf(&url, "http://%s:8888/", ipaserver); +#endif + serverInfoP = xmlrpc_server_info_new(&env, url); + + paramArrayP = xmlrpc_array_new(&env); + + if (hostname == NULL) + paramP = xmlrpc_string_new(&env, uinfo.nodename); + else + paramP = xmlrpc_string_new(&env, hostname); +#ifdef REALM + if (!quiet) + printf("Joining %s to IPA realm %s\n", uinfo.nodename, iparealm); +#endif + xmlrpc_array_append_item(&env, paramArrayP, paramP); + 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); + + callRPC(&env, serverInfoP, "join", paramArrayP, &resultP); + if (handle_fault(&env)) { + rval = 1; + 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, &*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 { + fprintf(stderr, "principal not found in XML-RPC response\n"); + rval = 12; + goto cleanup; + } + xmlrpc_struct_find_value(&env, structP, "krblastpwdchange", &krblastpwdchangeP); + if (krblastpwdchangeP) { + 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); + fprintf(stderr, "Host is already joined.\n"); + rval = 13; + goto cleanup; + } + +cleanup: + if (paramArrayP) xmlrpc_DECREF(paramArrayP); + if (resultP) xmlrpc_DECREF(resultP); + +cleanup_xmlrpc: + free(url); +// free((char *)princ); +// free((char *)hostdn); + free((char *)krblastpwdchange); + xmlrpc_env_clean(&env); + xmlrpc_client_cleanup(); + + return rval; +} + +static int +join(const char *hostname, const char *bindpw, const char *keytab, int quiet) +{ + int rval; + pid_t childpid = 0; + int status = 0; + char *ipaserver = NULL; + char *iparealm = NULL; + char * conf_data = NULL; + const char * princ = NULL; + const char * hostdn = NULL; + struct utsname uinfo; + + krb5_context krbctx = NULL; + krb5_ccache ccache = NULL; + krb5_principal uprinc = NULL; + krb5_error_code krberr; + + 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); + } +#if 1 + if ((iparealm = getIPArealm(conf_data)) == NULL) { + fprintf(stderr, "Unable to determine IPA realm from %s\n", IPA_CONFIG); + exit(1); + } +#endif + free(conf_data); + + if (NULL == hostname) { + uname(&uinfo); + hostname = strdup(uinfo.nodename); + } + + if (bindpw) + rval = join_ldap(ipaserver, hostname, &hostdn, bindpw, &princ, 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, hostname, &hostdn, &princ, 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: + free((char *)princ); + 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; +} + +int +main(int argc, char **argv) { + static const char *hostname = NULL; + static const char *keytab = NULL; + static const char *bindpw = NULL; + int quiet = 0; + struct poptOption options[] = { + { "debug", 'd', POPT_ARG_NONE, &debug, 0, "Print the raw XML-RPC output", "XML-RPC debugging Output"}, + { "quiet", 'q', POPT_ARG_NONE, &quiet, 0, "Print as little as possible", "Output only on errors"}, + { "hostname", 'h', POPT_ARG_STRING, &hostname, 0, "Use this hostname instead of the node name", "Host Name" }, + { "keytab", 'k', POPT_ARG_STRING, &keytab, 0, "File were to store the keytab information", "Keytab File Name" }, + { "bindpw", 'w', POPT_ARG_STRING, &bindpw, 0, "LDAP password", "password to use if not using kerberos" }, + { NULL, 0, POPT_ARG_NONE, NULL, 0, NULL, NULL } + }; + poptContext pc; + int ret; + + pc = poptGetContext("ipa-join", argc, (const char **)argv, options, 0); + ret = poptGetNextOpt(pc); + if (ret != -1) { + if (!quiet) { + poptPrintUsage(pc, stderr, 0); + } + exit(1); + } + poptFreeContext(pc); + if (debug) + setenv("XMLRPC_TRACE_XML", "1", 1); + + if (!keytab) + keytab = "/etc/krb5.keytab"; + + ret = check_perms(keytab); + if (ret == 0) + ret = join(hostname, bindpw, keytab, quiet); + + exit(ret); +} -- cgit