mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-28 18:01:23 -06:00
50318b60ee
Apparently we forgot to check OID consistency between the schema and the extensions, and we got duplicates. Technically the schema was done later but it is easier to change the extensions OIDs than to change the schema of current beta2/rc1 installations. The only side effect is that older ipa-getkeytab and ipa-join binaries will fail. So all the admin/client tools must be upgraded at the same time as well as all the masters (otherwise some will show/accept the new OID while others won't). Fixes: https://fedorahosted.org/freeipa/ticket/976
1088 lines
31 KiB
C
1088 lines
31 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 <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 VERSION "1.0"
|
|
|
|
#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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
memset(&clientparms, 0, sizeof(clientparms));
|
|
|
|
XMLRPC_ASSERT(xmlrpc_value_type(paramArrayP) == XMLRPC_TYPE_ARRAY);
|
|
|
|
curlXportParmsP = malloc(sizeof(*curlXportParmsP));
|
|
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";
|
|
|
|
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, 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 = 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;
|
|
}
|
|
|
|
/* 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]);
|
|
rval = 14;
|
|
goto done;
|
|
}
|
|
|
|
*ldap_base = strdup(ncvals[0]->bv_val);
|
|
|
|
ldap_value_free_len(ncvals);
|
|
|
|
done:
|
|
if (res) ldap_msgfree(res);
|
|
if (ld != NULL) {
|
|
ldap_unbind_ext(ld, NULL, NULL);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
static int
|
|
get_subject(const char *ipaserver, char *ldap_base, const char **subject)
|
|
{
|
|
LDAP *ld = NULL;
|
|
char *attrs[] = {"ipaCertificateSubjectBase", NULL};
|
|
char base[LINE_MAX];
|
|
LDAPMessage *entry, *res = NULL;
|
|
struct berval **ncvals;
|
|
int ret, rval = 0;
|
|
|
|
ld = connect_ldap(ipaserver, NULL, NULL);
|
|
if (!ld) {
|
|
rval = 14;
|
|
goto done;
|
|
}
|
|
|
|
strcpy(base, "cn=ipaconfig,cn=etc,");
|
|
strcat(base, ldap_base);
|
|
|
|
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:
|
|
if (res) ldap_msgfree(res);
|
|
if (ld != NULL) {
|
|
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, char *hostname, const char ** binddn, const char *bindpw, const char **princ, const char **subject, 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;
|
|
*princ = NULL;
|
|
*subject = NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
if (get_subject(ipaserver, ldap_base, subject) != 0) {
|
|
if (!quiet)
|
|
fprintf(stderr,
|
|
_("Unable to determine certificate subject of %s\n"),
|
|
ipaserver);
|
|
/* Not a critical failure */
|
|
}
|
|
|
|
ld = connect_ldap(ipaserver, NULL, NULL);
|
|
if (!ld) {
|
|
if (!quiet)
|
|
fprintf(stderr, _("Unable to make an LDAP connection to %s\n"),
|
|
ipaserver);
|
|
rval = 14;
|
|
goto done;
|
|
}
|
|
/* Search for the entry. */
|
|
ret = asprintf(&filter, "(fqdn=%s)", hostname);
|
|
if (ret == -1)
|
|
{
|
|
if (!quiet)
|
|
fprintf(stderr, _("Out of memory!\n"));
|
|
rval = 3;
|
|
goto done;
|
|
}
|
|
|
|
ret = asprintf(&search_base, "cn=computers,cn=accounts,%s", ldap_base);
|
|
if (ret == -1)
|
|
{
|
|
if (!quiet)
|
|
fprintf(stderr, _("Out of memory!\n"));
|
|
rval = 3;
|
|
goto done;
|
|
}
|
|
|
|
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) {
|
|
if (!quiet)
|
|
fprintf(stderr, _("ldap_search_ext_s: %s\n"),
|
|
ldap_err2string(ret));
|
|
rval = 14;
|
|
goto ldap_done;
|
|
}
|
|
e = ldap_first_entry(ld, result);
|
|
if (!e) {
|
|
if (!quiet)
|
|
fprintf(stderr, _("Unable to find host '%s'\n"), hostname);
|
|
rval = 14;
|
|
goto ldap_done;
|
|
}
|
|
if ((*binddn = ldap_get_dn(ld, e)) == NULL) {
|
|
if (!quiet)
|
|
fprintf(stderr,
|
|
_("Unable to get binddn for host '%s'\n"), hostname);
|
|
rval = 14;
|
|
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);
|
|
if (ld != NULL) {
|
|
ldap_unbind_ext(ld, NULL, NULL);
|
|
}
|
|
|
|
/* Now rebind as the host */
|
|
ld = connect_ldap(ipaserver, *binddn, bindpw);
|
|
if (!ld) {
|
|
if (has_principal) {
|
|
if (!quiet)
|
|
fprintf(stderr, _("Host is already joined.\n"));
|
|
rval = 13;
|
|
} else {
|
|
if (!quiet)
|
|
fprintf(stderr, _("Incorrect password.\n"));
|
|
rval = 15;
|
|
}
|
|
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) {
|
|
if (!quiet)
|
|
fprintf(stderr, _("principal not found in host entry\n"));
|
|
if (debug) {
|
|
fprintf(stderr, "ldap_extended_operation_s failed: %s",
|
|
ldap_err2string(rc));
|
|
}
|
|
rval = 18;
|
|
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);
|
|
return rval;
|
|
}
|
|
|
|
static int
|
|
join_krb5(const char *ipaserver, char *hostname, const 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;
|
|
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);
|
|
|
|
callRPC(&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, &*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(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;
|
|
|
|
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);
|
|
|
|
callRPC(&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:
|
|
|
|
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 *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;
|
|
const 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 (bindpw)
|
|
rval = join_ldap(ipaserver, host, &hostdn, bindpw, &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)
|
|
fprintf(stderr, _("Certificate subject base is: %s\n"), subject);
|
|
|
|
free((char *)princ);
|
|
free((char *)subject);
|
|
|
|
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;
|
|
int quiet = 0;
|
|
int unenroll = 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") },
|
|
{ "unenroll", 'u', POPT_ARG_NONE, &unenroll, 0,
|
|
_("Unenroll this host"), _("Unenroll this host from IPA server") },
|
|
{ "hostname", 'h', POPT_ARG_STRING, &hostname, 0,
|
|
_("Use this hostname instead of the node name"), _("Host Name") },
|
|
{ "server", 's', POPT_ARG_STRING, &server, 0,
|
|
_("IPA Server to use"), _("IPA Server 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") },
|
|
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, keytab, quiet);
|
|
}
|
|
|
|
exit(ret);
|
|
}
|