diff options
author | Simo Sorce <ssorce@redhat.com> | 2007-08-08 22:18:14 -0400 |
---|---|---|
committer | Simo Sorce <ssorce@redhat.com> | 2007-08-08 22:18:14 -0400 |
commit | a50720e7d4979483c52081b4278046a354528dbf (patch) | |
tree | 3ded400bb03d6f56d1c75e28e79578bbe5ddd1e4 /ipa-server | |
parent | 7ca7a4b9e42451b6b1ac464502ec99a5e254f0ab (diff) | |
download | freeipa-a50720e7d4979483c52081b4278046a354528dbf.tar.gz freeipa-a50720e7d4979483c52081b4278046a354528dbf.tar.xz freeipa-a50720e7d4979483c52081b4278046a354528dbf.zip |
Make the daemon init it's own tickets.
Make it blacklist clients until the previous operation is not over.
General bugfixing.
Diffstat (limited to 'ipa-server')
-rw-r--r-- | ipa-server/ipa-kpasswd/ipa_kpasswd.c | 451 |
1 files changed, 405 insertions, 46 deletions
diff --git a/ipa-server/ipa-kpasswd/ipa_kpasswd.c b/ipa-server/ipa-kpasswd/ipa_kpasswd.c index 35503ae95..4e324e2a5 100644 --- a/ipa-server/ipa-kpasswd/ipa_kpasswd.c +++ b/ipa-server/ipa-kpasswd/ipa_kpasswd.c @@ -14,72 +14,317 @@ #include <string.h> #include <errno.h> #include <netinet/in.h> +#include <time.h> #include <krb5.h> #include <ldap.h> #include <sasl/sasl.h> +#define TMP_TEMPLATE "/tmp/kpasswd.XXXXXX" #define KPASSWD_PORT 464 #define KPASSWD_TCP 1 #define KPASSWD_UDP 2 +struct blacklist { + struct blacklist *next; + char *address; + pid_t pid; +}; + +static struct blacklist *global_blacklist = NULL; + +int check_blacklist(char *address) +{ + struct blacklist *bl; + + if (!global_blacklist) { + return 0; + } + + for (bl = global_blacklist; bl; bl = bl->next) { + if (strcmp(address, bl->address) == 0) { + return 1; + } + } + + return 0; +} + +int add_blacklist(pid_t pid, char *address) +{ + struct blacklist *bl, *gbl; + + bl = malloc(sizeof(struct blacklist)); + if (!bl) return -1; + + bl->next = NULL; + bl->pid = pid; + bl->address = strdup(address); + if (!bl->address) { + free(bl); + return -1; + } + + if (!global_blacklist) { + global_blacklist = bl; + return 0; + } + + gbl = global_blacklist; + while (gbl->next) { + gbl = gbl->next; + } + gbl->next = bl; + return 0; +} + +int remove_blacklist(pid_t pid) +{ + struct blacklist *bl, *pbl; + + if (!global_blacklist) { + return -1; + } + + pbl = NULL; + bl = global_blacklist; + while (bl) { + if (pid == bl->pid) { + if (pbl == NULL) { + global_blacklist = bl->next; + } else { + pbl->next = bl->next; + } + free(bl->address); + free(bl); + return 0; + } + pbl = bl; + bl = bl->next; + } + return -1; +} + int debug = 1; char *srv_pri_name = "kadmin/changepw"; char *keytab_name = "FILE:/var/kerberos/krb5kdc/kpasswd.keytab"; -char *realm_name = "BLUEBOX.REDHAT.COM"; -char *ldap_uri = "ldap://rc1.bluebox.redhat.com:389"; + +static int get_krb5_ticket(char *tmpfile) +{ + char *ccname; + char *realm_name = NULL; + krb5_context context = NULL; + krb5_keytab keytab = NULL; + krb5_ccache ccache = NULL; + krb5_principal kprincpw; + krb5_creds my_creds; + krb5_get_init_creds_opt options; + int krberr, ret; + + krberr = krb5_init_context(&context); + if (krberr) { + fprintf(stderr, "Failed to init kerberos context\n"); + return -1; + } + + krberr = krb5_get_default_realm(context, &realm_name); + if (krberr) { + fprintf(stderr, "Failed to get default realm name: %s\n", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + krberr = krb5_build_principal(context, &kprincpw, + strlen(realm_name), realm_name, + "kadmin", "changepw", NULL); + if (krberr) { + fprintf(stderr, "Unable to build principal: %s\n", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + krberr = krb5_kt_resolve(context, keytab_name, &keytab); + if (krberr) { + fprintf(stderr, "Failed to read keytab file: %s\n", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + ret = asprintf(&ccname, "FILE:%s", tmpfile); + if (ret == -1) { + fprintf(stderr, "Out of memory!\n"); + goto done; + } + + ret = setenv("KRB5CCNAME", ccname, 1); + if (ret == -1) { + fprintf(stderr, "Unable to set env. variable KRB5CCNAME!\n"); + goto done; + } + + krberr = krb5_cc_resolve(context, ccname, &ccache); + if (krberr) { + fprintf(stderr, "Failed to set cache name: %s\n", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + memset(&my_creds, 0, sizeof(my_creds)); + memset(&options, 0, sizeof(options)); + + krb5_get_init_creds_opt_set_address_list(&options, NULL); + krb5_get_init_creds_opt_set_forwardable(&options, 0); + krb5_get_init_creds_opt_set_proxiable(&options, 0); + /* set a very short lifetime, we don't keep the ticket around */ + krb5_get_init_creds_opt_set_tkt_life(&options, 300); + + krberr = krb5_get_init_creds_keytab(context, &my_creds, kprincpw, + keytab, 0, NULL, + &options); + + if (krberr) { + fprintf(stderr, "Failed to init credentials: %s\n", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + krb5_cc_initialize(context, ccache, kprincpw); + if (krberr) { + fprintf(stderr, "Failed to init ccache: %s\n", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + krberr = krb5_cc_store_cred(context, ccache, &my_creds); + if (krberr) { + fprintf(stderr, "Failed to store creds: %s\n", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + ret = 0; + +done: + /* TODO: mem cleanup */ + if (keytab) krb5_kt_close(context, keytab); + if (context) krb5_free_context(context); + return ret; +} int ldap_sasl_interact(LDAP *ld, unsigned flags, void *priv_data, void *sit) { - sasl_interact_t **in = sit; - int i, ret = LDAP_OTHER; + sasl_interact_t *in = NULL; + int ret = LDAP_OTHER; + char *realm_name = (char *)priv_data; if (!ld) return LDAP_PARAM_ERROR; - for (i = 0; in[i] && in[i]->id != SASL_CB_LIST_END; i++) { - switch(in[i]->id) { + for (in = sit; in && in->id != SASL_CB_LIST_END; in++) { + switch(in->id) { case SASL_CB_USER: - in[i]->result = srv_pri_name; - in[i]->len = strlen(srv_pri_name); + in->result = srv_pri_name; + in->len = strlen(srv_pri_name); ret = LDAP_SUCCESS; break; case SASL_CB_GETREALM: - in[i]->result = realm_name; - in[i]->len = strlen(realm_name); + in->result = realm_name; + in->len = strlen(realm_name); ret = LDAP_SUCCESS; break; default: if (debug > 0) { fprintf(stderr, "Unhandled SASL int. option %d\n", - in[i]->id); + in->id); } - in[i]->result = NULL; - in[i]->len = 0; + in->result = NULL; + in->len = 0; ret = LDAP_OTHER; } } return ret; } -int ldap_pwd_change(char *client_name, krb5_data pwd) +int ldap_pwd_change(char *client_name, char *realm_name, krb5_data pwd) { + char *tmpfile = NULL; int id, version; LDAP *ld = NULL; BerElement *ctrl = NULL; struct berval control; struct berval newpw; + char hostname[1024]; + char *ldap_uri = NULL; + struct berval **ncvals; + char *ldap_base = NULL; + char *filter; + char *attrs[] = {"krbprincipalname", NULL}; + char *root_attrs[] = {"namingContexts", NULL}; char *userdn = NULL; + char *retoid = NULL; + struct berval *retdata = NULL; + struct timeval tv; + LDAPMessage *entry, *res = NULL; int ret; + tmpfile = strdup(TMP_TEMPLATE); + if (!tmpfile) { + fprintf(stderr, "Out of memory!\n"); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + + ret = mkstemp(tmpfile); + if (ret == -1) { + fprintf(stderr, + "Failed to create tmp file with errno: %d\n", errno); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + /* close mimmediately, we don't need to keep the file open, + * just that it exist and has a unique name */ + close(ret); + + /* In the long term we may want to do this in the main daemon + * and just renew when needed. + * Right now do it at every password change for robustness */ + ret = get_krb5_ticket(tmpfile); + if (ret) { + fprintf(stderr, "Unable to kinit!\n"); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + newpw.bv_len = pwd.length; newpw.bv_val = pwd.data; + /* retrieve server name and build uri */ + ret = gethostname(hostname, 1023); + if (ret == -1) { + fprintf(stderr, "Unable to get the hostname!\n"); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + + ret = asprintf(&ldap_uri, "ldap://%s:389", hostname); + if (ret == -1) { + fprintf(stderr, "Out of memory!\n"); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + /* connect to ldap server */ /* TODO: support referrals ? */ ret = ldap_initialize(&ld, ldap_uri); if(ret != LDAP_SUCCESS) { fprintf(stderr, "Unable to connect to ldap server"); - ret = -1; + ret = KRB5_KPASSWD_HARDERROR; goto done; } @@ -87,7 +332,7 @@ int ldap_pwd_change(char *client_name, krb5_data pwd) ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version); if (ret != LDAP_OPT_SUCCESS) { fprintf(stderr, "Unable to set ldap protocol version"); - ret = -1; + ret = KRB5_KPASSWD_HARDERROR; goto done; } @@ -95,20 +340,82 @@ int ldap_pwd_change(char *client_name, krb5_data pwd) NULL, "GSSAPI", NULL, NULL, LDAP_SASL_AUTOMATIC, - ldap_sasl_interact, NULL); + ldap_sasl_interact, realm_name); if (ret != LDAP_SUCCESS) { fprintf(stderr, "Unable to bind to ldap server"); - ret = -1; + ret = KRB5_KPASSWD_HARDERROR; goto done; } + /* find base dn */ + tv.tv_sec = 10; + tv.tv_usec = 0; + + ret = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE, + "objectclass=*", root_attrs, 0, + NULL, NULL, &tv, 0, &res); + + if (ret != LDAP_SUCCESS) { + fprintf(stderr, + "Search for %s on rootdse failed with error %d\n", + root_attrs[0], ret); + ret = KRB5_KPASSWD_HARDERROR; + 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\n", root_attrs[0]); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + + ldap_base = strdup(ncvals[0]->bv_val); + + ldap_value_free_len(ncvals); + ldap_msgfree(res); + /* find user dn */ + ret = asprintf(&filter, "krbPrincipalName=%s", client_name); + if (ret == -1) { + fprintf(stderr, "Out of memory!\n"); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + + tv.tv_sec = 10; + tv.tv_usec = 0; + + ret = ldap_search_ext_s(ld, ldap_base, LDAP_SCOPE_SUBTREE, + filter, attrs, 1, NULL, NULL, &tv, 0, &res); + + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "Search for %s failed with error %d\n", + filter, ret); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + free(filter); + + /* for now just use the first result we get */ + entry = ldap_first_entry(ld, res); + userdn = ldap_get_dn(ld, entry); + + ldap_msgfree(res); + + if (!userdn) { + fprintf(stderr, "No userdn, can't change password!\n"); + ret = -1; + goto done; + } /* build password change control */ ctrl = ber_alloc_t(LBER_USE_DER); if (!ctrl) { fprintf(stderr, "Out of memory!\n"); - ret = -1; + ret = KRB5_KPASSWD_HARDERROR; goto done; } ber_printf(ctrl, "{tstON}", @@ -122,12 +429,27 @@ int ldap_pwd_change(char *client_name, krb5_data pwd) goto done; } - /* perform poassword change */ - ret = ldap_extended_operation(ld, LDAP_EXOP_MODIFY_PASSWD, - &control, NULL, NULL, &id); + /* perform password change */ + ret = ldap_extended_operation_s(ld, LDAP_EXOP_MODIFY_PASSWD, &control, + NULL, NULL, &retoid, &retdata); + + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "password change failed!\n"); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + + /* TODO: interpret retdata so that we can give back meaningful errors */ + done: + if (userdn) free(userdn); if (ctrl) ber_free(ctrl, 1); - if (ld) + if (ld) ldap_unbind(ld); + if (ldap_uri) free(ldap_uri); + if (tmpfile) { + unlink(tmpfile); + free(tmpfile); + } return ret; } @@ -149,7 +471,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen, socklen_t addrlen; size_t reqlen; size_t verno; - char *client_name; + char *client_name, *realm_name; char *result_string; int result_err; uint8_t *reply; @@ -161,6 +483,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen, krep.length = 0; krep.data = NULL; kprincpw = NULL; + context = NULL; ticket = NULL; lkaddr = NULL; @@ -210,6 +533,14 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen, goto done; } + krberr = krb5_get_default_realm(context, &realm_name); + if (krberr) { + result_string = "Failed to get default realm name"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s\n", result_string); + goto done; + } + krberr = krb5_auth_con_init(context, &auth_context); if (krberr) { result_string = "Unable to init auth context"; @@ -317,11 +648,13 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen, client_name, kdec.length, kdec.data); } - err = ldap_pwd_change(client_name, kdec); - - /* ok we are done, and the password change was successful! */ - result_err = KRB5_KPASSWD_SUCCESS; - result_string = ""; + /* Actually try to change the password */ + result_err = ldap_pwd_change(client_name, realm_name, kdec); + if (result_err != KRB5_KPASSWD_SUCCESS) { + result_string = "Generic error occurred while changing password"; + } else { + result_string = ""; + } /* make sure password is cleared off before we free the memory */ memset(kdec.data, 0, kdec.length); @@ -424,19 +757,21 @@ done: if (ticket) krb5_free_ticket(context, ticket); if (kdec.length) free(kdec.data); if (lkaddr) krb5_free_addresses(context, lkaddr); - krb5_free_context(context); + if (context) krb5_free_context(context); } pid_t handle_conn(int fd, int type) { int mfd; pid_t pid; + char address[INET6_ADDRSTRLEN+1]; uint8_t request[1500]; ssize_t reqlen; uint8_t *reply; ssize_t replen; struct sockaddr_in from; socklen_t fromlen; + ssize_t sendret; fromlen = sizeof(from); @@ -462,16 +797,29 @@ pid_t handle_conn(int fd, int type) return -1; } + if (!inet_ntop(from.sin_family, &from.sin_addr, + address, sizeof(address))) { + address[0] = '\0'; + } + if (debug > 0) { - uint32_t host = ntohl(from.sin_addr.s_addr); uint16_t port = ntohs(from.sin_port); - fprintf(stderr, - "Connection from %d.%d.%d.%d:%d\n", - (host & 0xff000000) >> 24, - (host & 0x00ff0000) >> 16, - (host & 0x0000ff00) >> 8, - host & 0x000000ff, - port); + + fprintf(stderr, "Connection from %s:%d\n", address, port); + } + + /* Check blacklist for requests frm the same IP until operations + * are finished on the active client. + * the password change may be slow and pam_krb5 sends up to 3 UDP + * requests waiting 1 sec. each time. + * We do not want to start 3 password changes at the same time */ + + if (check_blacklist(address)) { + if (debug > 0) { + fprintf(stderr, "[%s] blacklisted\n", address); + } + if (type == KPASSWD_TCP) close(mfd); + return 0; } #if 1 @@ -485,23 +833,30 @@ pid_t handle_conn(int fd, int type) } if (pid != 0) { /* parent */ if (type == KPASSWD_TCP) close(mfd); + add_blacklist(pid, address); return pid; } #endif + /* children */ handle_krb_packets(request, reqlen, &from, &reply, &replen); if (replen) { /* we have something to reply */ if (type == KPASSWD_TCP) { - sendto(mfd, reply, replen, 0, NULL, 0); + sendret = sendto(mfd, reply, replen, 0, NULL, 0); } else { - sendto(mfd, reply, replen, 0, (struct sockaddr *)&from, fromlen); + sendret = sendto(mfd, reply, replen, 0, (struct sockaddr *)&from, fromlen); + } + if (sendret == -1) { + fprintf(stderr, "Error sending reply (%d)\n", errno); } } close(mfd); exit(0); } +/* TODO: make this IPv6 aware */ + int main(int argc, char *argv[]) { struct sockaddr_in addr; @@ -589,14 +944,18 @@ int main(int argc, char *argv[]) /* now that sockets are set up, enter the select loop */ while (1) { - int cstatus; + struct timeval tv; + int cstatus, cid; fd_set rfd; + tv.tv_sec = 3; + tv.tv_usec = 0; + FD_ZERO(&rfd); FD_SET(udp_s, &rfd); FD_SET(tcp_s, &rfd); - ret = select(udp_s+1, &rfd, NULL, NULL, NULL); + ret = select(udp_s+1, &rfd, NULL, NULL, &tv); switch(ret) { case 0: @@ -618,12 +977,12 @@ int main(int argc, char *argv[]) handle_conn(udp_s, KPASSWD_UDP); break; } - /* what else?? */ - fprintf(stderr, "Select returned but no fd ready\n"); - exit(6); } /* check for children exiting */ - waitpid(-1, &cstatus, WNOHANG); + cid = waitpid(-1, &cstatus, WNOHANG); + if (cid != -1 && cid != 0) { + remove_blacklist(cid); + } } } |