/* * lib/kadm/adm_conn.c * * Copyright 1995 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * */ /* * Routines to contact an administrative protocol server. */ #define NEED_SOCKETS #define NEED_LOWLEVEL_IO #include "k5-int.h" #include "adm.h" #include "adm_proto.h" #if HAVE_PWD_H #include #endif /* HAVE_PWD_H */ /* Default ticket life is 10 minutes */ #define KADM_DEFAULT_LIFETIME (10*60) /* * Strings */ static char *kadm_cache_name_fmt = "FILE:/tmp/tkt_kadm_%d"; /* * Prototypes for local functions */ static krb5_error_code kadm_get_ccache PROTOTYPE((krb5_context, char *, char *, krb5_ccache *, krb5_principal *)); static krb5_error_code kadm_get_creds PROTOTYPE((krb5_context, krb5_ccache , krb5_principal, krb5_creds *, char *, char *, krb5_timestamp)); static krb5_error_code kadm_contact_server PROTOTYPE((krb5_context, krb5_data *, int *, krb5_address **, krb5_address **)); static krb5_error_code kadm_get_auth PROTOTYPE((krb5_context, krb5_auth_context *, krb5_address *, krb5_address *)); /* * kadm_get_ccache() - Initialze a credentials cache. * * Cleanup after success by calling krb5_cc_destroy() and krb5_free_principal() * Allocates new ccache and client. */ static krb5_error_code kadm_get_ccache(kcontext, user, ccname, ccache, client) krb5_context kcontext; char *user; char *ccname; krb5_ccache *ccache; krb5_principal *client; { krb5_error_code kret; char *name; int did_malloc = 0; char new_cache[MAXPATHLEN]; krb5_principal tprinc; /* Initialize. */ *client = (krb5_principal) NULL; tprinc = (krb5_principal) NULL; /* * If a name specified, then use that one, else get it from our * current uid. */ if (user) { name = user; } else { #if HAVE_PWD_H struct passwd *pw; pw = getpwuid(getuid()); if (pw) { name = (char *) malloc(strlen(pw->pw_name)+1); did_malloc = 1; strcpy(name, pw->pw_name); } else { kret = errno; goto cleanup; } #else /* HAVE_PWD_H */ kret = ENOENT; goto cleanup; #endif /* HAVE_PWD_H */ } /* Parse the name and form our principal */ if (kret = krb5_parse_name(kcontext, name, client)) goto cleanup; if (!ccname) { #ifdef _WINDOWS strcpy (new_cache, "FILE:"); GetTempFileName (0, "tkt", 0, new_cache+5); #else #ifdef _MACINTOSH (void) sprintf(new_cache, "STDIO:admcc"); #else (void) sprintf(new_cache, kadm_cache_name_fmt, getpid()); #endif /* _MACINTOSH */ #endif /* _WINDOWS */ } else sprintf(new_cache, "FILE:%s", ccname); /* * We only need to resolve the credentials cache if one hasn't * been supplied to us. */ if (!(*ccache) && (kret = krb5_cc_resolve(kcontext, new_cache, ccache))) goto cleanup; /* XXX assumes a file ccache */ if ((kret = krb5_cc_get_principal(kcontext, *ccache, &tprinc)) == KRB5_FCC_NOFILE) kret = krb5_cc_initialize(kcontext, *ccache, *client); cleanup: if (did_malloc) free(name); if (tprinc) krb5_free_principal(kcontext, tprinc); if (kret) { if (*client) krb5_free_principal(kcontext, *client); } return(kret); } /* * kadm_get_creds() - Get initial credentials. * * Cleanup after success by calling krb5_free_principal(). * Allocates new principal for creds->server. */ static krb5_error_code kadm_get_creds(kcontext, ccache, client, creds, prompt, oldpw, tlife) krb5_context kcontext; krb5_ccache ccache; krb5_principal client; krb5_creds *creds; char *prompt; char *oldpw; krb5_timestamp tlife; { char *client_name; krb5_error_code kret; krb5_address **my_addresses; int old_pwsize; krb5_creds tcreds; /* Initialize */ my_addresses = (krb5_address **) NULL; client_name = (char *) NULL; /* Get the string form for our principal */ if (kret = krb5_unparse_name(kcontext, client, &client_name)) return(kret); if (kret = krb5_os_localaddr(kcontext, &my_addresses)) goto cleanup; creds->client = client; /* * Build server principal name: * "changepw" is service * realm name is instance * realm name is realm name */ if (kret = krb5_build_principal_ext(kcontext, &creds->server, client->realm.length, client->realm.data, strlen(KRB5_ADM_SERVICE_INSTANCE), KRB5_ADM_SERVICE_INSTANCE, client->realm.length, client->realm.data, 0)) goto cleanup; /* Attempt to retrieve an appropriate entry from the credentials cache. */ if ((kret = krb5_cc_retrieve_cred(kcontext, ccache, KRB5_TC_MATCH_SRV_NAMEONLY, creds, &tcreds)) == KRB5_CC_NOTFOUND) { krb5_timestamp jetzt; if (prompt != (char *) NULL) { /* Read the password */ old_pwsize = KRB5_ADM_MAX_PASSWORD_LEN; if (kret = krb5_read_password(kcontext, prompt, (char *) NULL, oldpw, &old_pwsize)) goto cleanup; } if (kret = krb5_timeofday(kcontext, &jetzt)) goto cleanup; if (tlife > 0) creds->times.endtime = jetzt + tlife; else creds->times.endtime = jetzt + KADM_DEFAULT_LIFETIME; /* Get our initial ticket */ kret = krb5_get_in_tkt_with_password(kcontext, 0, my_addresses, NULL, NULL, oldpw, ccache, creds, 0); } else { krb5_principal sclient, sserver; if (!kret) { /* * We found the credentials cache entry - copy it out. * * We'd like to just blast tcreds on top of creds, but we cannot. * other logic uses the client data, and rather than going and * chasing all that logic down, might as well pretend that we just * filled in all the other muck. */ sclient = creds->client; sserver = creds->server; memcpy((char *) creds, (char *) &tcreds, sizeof(tcreds)); if (creds->client) krb5_free_principal(kcontext, creds->client); if (creds->server) krb5_free_principal(kcontext, creds->server); creds->client = sclient; creds->server = sserver; } } cleanup: if (kret) { if (creds->server) { krb5_free_principal(kcontext, creds->server); creds->server = 0; } } if (my_addresses) krb5_free_addresses(kcontext, my_addresses); if (client_name) krb5_xfree(client_name); return(kret); } /* * kadm_contact_server() - Establish a connection to the server. * * Cleanup after success by calling close() and free(). * Opens/connects socket *sockp. Allocates address storage for local/remote. */ static krb5_error_code kadm_contact_server(kcontext, realmp, sockp, local, remote) krb5_context kcontext; krb5_data *realmp; int *sockp; krb5_address **local; krb5_address **remote; { struct hostent *remote_host; struct servent *service; char **hostlist; int i, count; krb5_error_code kret; struct sockaddr_in in_local; struct sockaddr_in in_remote; int addr_len; const char *realm_admin_names[4]; char *realm_name; krb5_boolean found; /* Initialize */ hostlist = (char **) NULL; *sockp = -1; realm_name = (char *) NULL; /* * XXX - only know ADDRTYPE_INET. */ #ifdef KRB5_USE_INET *local = (krb5_address *) malloc(sizeof(krb5_address)); *remote = (krb5_address *) malloc(sizeof(krb5_address)); realm_name = (char *) malloc((size_t) realmp->length + 1); if ((*local == (krb5_address *) NULL) || (*remote == (krb5_address *) NULL) || (realm_name == (char *) NULL)) { kret = ENOMEM; goto cleanup; } (*local)->addrtype = (*remote)->addrtype = ADDRTYPE_INET; (*local)->length = (*remote)->length = sizeof(struct in_addr); (*local)->contents = (krb5_octet *) malloc(sizeof(struct in_addr)); (*remote)->contents = (krb5_octet *) malloc(sizeof(struct in_addr)); if (((*local)->contents == NULL) || ((*remote)->contents == NULL)) { kret = ENOMEM; goto cleanup; } /* * First attempt to find addresses from our config file, if we cannot * find an entry, then try getservbyname(). */ found = 0; #ifndef OLD_CONFIG_FILES strncpy(realm_name, realmp->data, (size_t) realmp->length); realm_name[realmp->length] = '\0'; realm_admin_names[0] = "realms"; realm_admin_names[1] = realm_name; realm_admin_names[2] = "admin_server"; realm_admin_names[3] = (char *) NULL; if (!(kret = profile_get_values(kcontext->profile, realm_admin_names, &hostlist))) { int hi; char *cport; char *cp; krb5_int32 pport; for (hi = 0; hostlist[hi]; hi++) { /* * This knows a little too much about the format of profile * entries. Shouldn't it just be some sort of tuple? * * The form is assumed to be: * admin_server = [:[]] */ cport = (char *) NULL; pport = (u_short) KRB5_ADM_DEFAULT_PORT; cp = strchr(hostlist[hi], ' '); if (cp) *cp = '\0'; cp = strchr(hostlist[hi], '\t'); if (cp) *cp = '\0'; cport = strchr(hostlist[hi], ':'); if (cport) { *cport = '\0'; cport++; pport = atoi (cport); if (pport == 0) { kret = KRB5_CONFIG_BADFORMAT; goto cleanup; } } /* * Now that we have a host name, get the host entry. */ remote_host = gethostbyname(hostlist[hi]); if (remote_host == (struct hostent *) NULL) { kret = KRB5_CONFIG_BADFORMAT; goto cleanup; } /* * Fill in our address values. */ in_remote.sin_family = remote_host->h_addrtype; (void) memcpy((char *) &in_remote.sin_addr, (char *) remote_host->h_addr, sizeof(in_remote.sin_addr)); in_remote.sin_port = htons((u_short) pport); /* Open a tcp socket */ *sockp = (int) socket(PF_INET, SOCK_STREAM, 0); if (*sockp < 0) { kret = SOCKET_ERRNO; goto cleanup; } else kret = 0; /* Attempt to connect to the remote address. */ if (connect(*sockp, (struct sockaddr *) &in_remote, sizeof(in_remote)) < 0) { /* Failed, go to next address */ kret = SOCKET_ERRNO; closesocket((SOCKET)*sockp); *sockp = -1; continue; } /* Find out local address */ addr_len = sizeof(in_local); if (getsockname((SOCKET) *sockp, (struct sockaddr *) &in_local, &addr_len) < 0) { /* Couldn't get our local address? */ kret = SOCKET_ERRNO; goto cleanup; } else { /* Connection established. */ memcpy((char *) (*remote)->contents, (char *) &in_remote.sin_addr, sizeof(struct in_addr)); memcpy((char *) (*local)->contents, (char *) &in_local.sin_addr, sizeof(struct in_addr)); found = 1; break; } } if (!found) { krb5_xfree(hostlist); hostlist = (char **) NULL; } } #endif /* OLD_CONFIG_FILES */ if (!found) { /* * Use the old way of finding our administrative server. * * This consists of looking up an entry in /etc/services and if * we don't find it, then we are just out of luck. Then, we use * that port number along with the address of the kdc. */ if ((service = getservbyname(KRB5_ADM_SERVICE_NAME, "tcp")) == NULL) { kret = ENOENT; goto cleanup; } in_remote.sin_port = service->s_port; if (kret = krb5_get_krbhst(kcontext, realmp, &hostlist)) goto cleanup; /* Now count the number of hosts in the realm */ count = 0; for (i=0; hostlist[i]; i++) count++; if (count == 0) { kret = ENOENT; /* something better? */ goto cleanup; } /* Now find an available host */ for (i=0; hostlist[i]; i++) { remote_host = gethostbyname(hostlist[i]); if (remote_host != (struct hostent *) NULL) { in_remote.sin_family = remote_host->h_addrtype; (void) memcpy((char *) &in_remote.sin_addr, (char *) remote_host->h_addr, sizeof(in_remote.sin_addr)); /* Open a tcp socket */ *sockp = (int) socket(PF_INET, SOCK_STREAM, 0); if (*sockp < 0) { kret = SOCKET_ERRNO; goto cleanup; } else kret = 0; if (connect(*sockp, (struct sockaddr *) &in_remote, sizeof(in_remote)) < 0) { kret = SOCKET_ERRNO; closesocket((SOCKET)*sockp); *sockp = -1; continue; } /* Find out local address */ addr_len = sizeof(in_local); if (getsockname((SOCKET)*sockp, (struct sockaddr *) &in_local, &addr_len) < 0) { kret = SOCKET_ERRNO; goto cleanup; } else { memcpy((char *) (*remote)->contents, (char *) &in_remote.sin_addr, sizeof(struct in_addr)); memcpy((char *) (*local)->contents, (char *) &in_local.sin_addr, sizeof(struct in_addr)); found = 1; break; } } } if (!found) kret = KRB5_SERVICE_UNKNOWN; } #else /* KRB5_USE_INET */ kret = ENOENT; #endif /* KRB5_USE_INET */ cleanup: if (kret) { if (*sockp >= 0) closesocket((SOCKET)*sockp); if (*local && (*local)->contents) free((*local)->contents); if (*remote && (*remote)->contents) free((*remote)->contents); if (*local) { memset((char *) (*local), 0, sizeof(krb5_address)); free(*local); *local = (krb5_address *) NULL; } if (*remote) { memset((char *) (*remote), 0, sizeof(krb5_address)); free(*remote); *remote = (krb5_address *) NULL; } } if (realm_name) free(realm_name); if (hostlist) krb5_xfree(hostlist); return(kret); } /* * kadm_get_auth() - Get authorization context. * * Cleanup after success by calling krb5_xfree(). * New krb5_auth_context allocated in *ctxp */ static krb5_error_code kadm_get_auth(kcontext, ctxp, local, remote) krb5_context kcontext; krb5_auth_context *ctxp; krb5_address *local; krb5_address *remote; { krb5_auth_con_init(kcontext, ctxp); krb5_auth_con_setflags(kcontext, *ctxp, KRB5_AUTH_CONTEXT_RET_SEQUENCE| KRB5_AUTH_CONTEXT_DO_SEQUENCE); krb5_auth_con_setaddrs(kcontext, *ctxp, local, remote); return(0); } /* * krb5_adm_connect() - Establish the connection to the service. * * If *ccachep is not null, then that ccache is used to establish the identity * of the caller. (Argument list is ugly, I know) * * Errors are not reported by this routine. * Cleanup after successful invocation must: * destroy/close ccache. * free auth_context * close socket. */ krb5_error_code INTERFACE krb5_adm_connect(kcontext, user, prompt, opassword, sockp, ctxp, ccachep, ccname, tlife) krb5_context kcontext; /* Context handle (In ) */ char *user; /* User specified (In ) */ char *prompt; /* Old password prompt (In ) */ char *opassword; /* Old Password (I/O) */ int *sockp; /* Socket for conn. (Out) */ krb5_auth_context *ctxp; /* Auth context (Out) */ krb5_ccache *ccachep; /* Credentials cache (I/O) */ char *ccname; /* Cred cache name (In ) */ krb5_timestamp tlife; /* Ticket lifetime (In ) */ { krb5_error_code kret; krb5_principal client; krb5_creds creds; krb5_data server_realm; krb5_data request_data, suppl_data; krb5_data response_data; krb5_address *local_addr; krb5_address *remote_addr; krb5_boolean ccache_supplied; char *server; /* Initialize */ memset((char *) &creds, 0, sizeof(krb5_creds)); server = (char *) NULL; *sockp = -1; local_addr = remote_addr = (krb5_address *) NULL; client = (krb5_principal) NULL; *ctxp = (krb5_auth_context) NULL; ccache_supplied = (*ccachep != (krb5_ccache) NULL); /* * Find the appropriate credentials cache and set up our identity. */ if (kret = kadm_get_ccache(kcontext, user, ccname, ccachep, &client)) goto cleanup; /* * Get initial credentials. */ if (kret = kadm_get_creds(kcontext, *ccachep, client, &creds, prompt, opassword, tlife)) goto cleanup; /* * Establish connection to server. */ if ((server_realm.data = (char *) malloc(client->realm.length+1)) == (char *) NULL) goto cleanup; server_realm.length = client->realm.length; memcpy(server_realm.data, client->realm.data, server_realm.length); server_realm.data[server_realm.length] = '\0'; if (kret = kadm_contact_server(kcontext, &server_realm, sockp, &local_addr, &remote_addr)) goto cleanup; /* * Obtain our authorization context */ if (kret = kadm_get_auth(kcontext, ctxp, local_addr, remote_addr)) goto cleanup; /* * Format, then send the KRB_AP_REQ */ suppl_data.data = NULL; suppl_data.length = 0; if (kret = krb5_mk_req_extended(kcontext, ctxp, AP_OPTS_MUTUAL_REQUIRED, &suppl_data, &creds, &request_data)) goto cleanup; if (kret = krb5_write_message(kcontext, sockp, &request_data)) goto cleanup; /* * Now read back the response. */ if (kret = krb5_read_message(kcontext, sockp, &response_data)) { goto cleanup; } else { krb5_ap_rep_enc_part *reply = NULL; kret = krb5_rd_rep(kcontext, *ctxp, &response_data, &reply); if (reply) krb5_free_ap_rep_enc_part(kcontext, reply); } cleanup: if (server) free(server); if (kret) { if (*ctxp) { krb5_xfree(*ctxp); *ctxp = (krb5_auth_context) NULL; } if (*sockp >= 0) { closesocket((SOCKET)*sockp); *sockp = -1; } if (local_addr && local_addr->contents) free(local_addr->contents); if (remote_addr && remote_addr->contents) free(remote_addr->contents); if (local_addr) free(local_addr); if (remote_addr) free(remote_addr); if (creds.server) krb5_free_principal(kcontext, creds.server); if (client) krb5_free_principal(kcontext, client); if (*ccachep && !ccache_supplied) { krb5_cc_destroy(kcontext, *ccachep); *ccachep = (krb5_ccache) NULL; } } return(kret); } /* * krb5_adm_disconnect() - Disconnect from the administrative service. * * If ccache is supplied, then it is destroyed. Otherwise, the ccache is * the caller's responsibility to close. */ void INTERFACE krb5_adm_disconnect(kcontext, socketp, auth_context, ccache) krb5_context kcontext; int *socketp; krb5_auth_context auth_context; krb5_ccache ccache; { if (ccache) krb5_cc_destroy(kcontext, ccache); if (auth_context) krb5_xfree(auth_context); if (*socketp >= 0) closesocket((SOCKET)*socketp); }