freeipa/ipa-client/ipa-join.c
Rob Crittenden e889b82599 Add support defaultNamingContext and add --basedn to migrate-ds
There are two sides to this, the server and client side.

On the server side we attempt to add a defaultNamingContext on already
installed servers. This will fail on older 389-ds instances but the
failure is not fatal. New installations on versions of 389-ds that
support this attribute will have it already defined.

On the client side we need to look for both defaultNamingContext and
namingContexts. We still need to check that the defaultNamingContext
is an IPA server (info=IPAV2).

The migration change also takes advantage of this and adds a new
option which allows one to provide a basedn to use instead of trying
to detect it.

https://fedorahosted.org/freeipa/ticket/1919
https://fedorahosted.org/freeipa/ticket/2314
2012-02-29 15:28:13 +01:00

1152 lines
33 KiB
C

/* 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 %s failed: errno = %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 = 1;
curlXportParmsP->no_ssl_verifyhost = 1;
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 (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 %s on rootdse failed with error %d"),
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;
char *filter = NULL;
int rval = 0;
char *oidresult = NULL;
struct berval valrequest;
struct berval *valresult = NULL;
int rc, ret;
char *ldap_base = NULL;
char *search_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:
free(filter);
free(search_base);
free(ldap_base);
if (ld != NULL) {
ldap_unbind_ext(ld, NULL, NULL);
}
done:
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 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) {
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;
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 == 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 \"%s\": %s.\n"),
principal, error_message(krberr));
return krberr;
}
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));
return krberr;
}
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));
return krberr;
}
krb5_cc_close(krbctx, ccache);
ccache = NULL;
putenv("KRB5CCNAME=MEMORY:ipa-join");
/* 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 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((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 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, 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;
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") },
{ "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, quiet);
}
exit(ret);
}