diff options
-rw-r--r-- | ipa-server/ipa-install/README | 1 | ||||
-rw-r--r-- | ipa-server/ipa-install/share/bind.zone.db.template | 6 | ||||
-rw-r--r-- | ipa-server/ipa-install/share/default-aci.ldif | 5 | ||||
-rw-r--r-- | ipa-server/ipa-kpasswd/Makefile | 5 | ||||
-rw-r--r-- | ipa-server/ipa-kpasswd/README | 2 | ||||
-rw-r--r-- | ipa-server/ipa-kpasswd/ipa_kpasswd.c | 988 | ||||
-rw-r--r-- | ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c | 5 | ||||
-rw-r--r-- | ipa-server/ipaserver/krbinstance.py | 30 |
8 files changed, 1027 insertions, 15 deletions
diff --git a/ipa-server/ipa-install/README b/ipa-server/ipa-install/README index 2e1dd0d4..3dd4664d 100644 --- a/ipa-server/ipa-install/README +++ b/ipa-server/ipa-install/README @@ -8,6 +8,7 @@ krb5-server-ldap cyrus-sasl-gssapi httpd mod_auth_kerb +ntpd Installation example: diff --git a/ipa-server/ipa-install/share/bind.zone.db.template b/ipa-server/ipa-install/share/bind.zone.db.template index e846c4f2..a438d29a 100644 --- a/ipa-server/ipa-install/share/bind.zone.db.template +++ b/ipa-server/ipa-install/share/bind.zone.db.template @@ -1,7 +1,7 @@ $$ORIGIN $DOMAIN. $$TTL 86400 @ IN SOA $DOMAIN. root.$DOMAIN. ( - 01 ; serial (d. adams) + 01 ; serial 3H ; refresh 15M ; retry 1W ; expiry @@ -21,6 +21,6 @@ _kerberos._tcp IN SRV 0 100 88 $HOST _kerberos._udp IN SRV 0 100 88 $HOST _kerberos-master._tcp IN SRV 0 100 88 $HOST _kerberos-master._udp IN SRV 0 100 88 $HOST -_kpasswd._tcp IN SRV 0 100 88 $HOST -_kpasswd._udp IN SRV 0 100 88 $HOST +_kpasswd._tcp IN SRV 0 100 464 $HOST +_kpasswd._udp IN SRV 0 100 464 $HOST diff --git a/ipa-server/ipa-install/share/default-aci.ldif b/ipa-server/ipa-install/share/default-aci.ldif index 7870461b..a32729a3 100644 --- a/ipa-server/ipa-install/share/default-aci.ldif +++ b/ipa-server/ipa-install/share/default-aci.ldif @@ -2,10 +2,11 @@ dn: $SUFFIX changetype: modify replace: aci -aci: (targetattr!="userPassword || krbPrincipalKey ||sambaLMPassword || sambaNTPassword")(version 3.0; acl "Enable anonymous access"; allow (read, search, compare)userdn="ldap:///anyone";) +aci: (targetattr!="userPassword || krbPrincipalKey ||sambaLMPassword || sambaNTPassword")(version 3.0; acl "Enable anonymous access"; allow (read, search, compare) userdn="ldap:///anyone";) aci: (targetattr="carLicense ||description ||displayName ||facsimileTelephoneNumber ||homePhone ||homePostalAddress ||initials ||jpegPhoto ||labeledURL ||mail ||mobile ||pager ||photo ||postOfficeBox ||postalAddress ||postalCode ||preferredDeliveryMethod ||preferredLanguage ||registeredAddress ||roomNumber | |secretary ||seeAlso ||st ||street ||telephoneNumber ||telexNumber ||title || userCertificate ||userPassword ||userSMIMECertificate ||x500UniqueIdentifier")(version 3.0; acl "Enable self write for common attributes"; allow (write) userdn="ldap:///self";) -aci: (targetattr="krbPrincipalKey")(version 3.0; acl "KDC System Account"; allow(read, search,compare)userdn="ldap:///uid=kdc,cn=kerberos,$SUFFIX";) +aci: (targetattr="krbPrincipalKey")(version 3.0; acl "KDC System Account"; allow (read, search, compare) userdn="ldap:///uid=kdc,cn=kerberos,$SUFFIX";) aci: (targetattr="*")(version 3.0; acl "Directory Administrators can manage all entries"; allow(all)groupdn="ldap:///cn=Directory Administrators,$SUFFIX";) +aci: (targetattr="userPassword || krbPrincipalKey ||sambaLMPassword || sambaNTPassword")(version 3.0; acl "Kpasswd access to passowrd hashes for passowrd changes"; allow (all) userdn="ldap:///krbprincipalname=kadmin/changepw@$REALM,cn=$REALM,cn=kerberos,$SUFFIX";) aci: (target="ldap:///uid=*,ou=users,ou=default,$SUFFIX")(targetattr="*")(version 3.0; acl "allowproxy-webservice"; allow (proxy) userdn="ldap:///uid=webservice,ou=special,$SUFFIX";) aci: (target="ldap:///uid=*,ou=users,ou=default,$SUFFIX")(targetattr="*")(version 3.0; acl "admins can write entries"; allow(add,delete,write)groupdn="ldap:///cn=admin,ou=groups,ou=default,$SUFFIX";) aci: (targetattr="userPrincipal")(version 3.0; acl "allow webservice to find users by kerberos principal name"; allow (read, search) userdn="ldap:///uid=webservice,ou=special,$SUFFIX";) diff --git a/ipa-server/ipa-kpasswd/Makefile b/ipa-server/ipa-kpasswd/Makefile new file mode 100644 index 00000000..efdaa16b --- /dev/null +++ b/ipa-server/ipa-kpasswd/Makefile @@ -0,0 +1,5 @@ +all: + gcc ipa_kpasswd.c -I/usr/include -lkrb5 -llber -lldap -g -o ipa_kpasswd + +install: + cp -f ipa_kpasswd /usr/local/sbin/ diff --git a/ipa-server/ipa-kpasswd/README b/ipa-server/ipa-kpasswd/README new file mode 100644 index 00000000..c0a2767a --- /dev/null +++ b/ipa-server/ipa-kpasswd/README @@ -0,0 +1,2 @@ +This is an implementation of the RFC3244 kpasswd protocol. +It is used to proxy password change operations to Directory Server. diff --git a/ipa-server/ipa-kpasswd/ipa_kpasswd.c b/ipa-server/ipa-kpasswd/ipa_kpasswd.c new file mode 100644 index 00000000..4e324e2a --- /dev/null +++ b/ipa-server/ipa-kpasswd/ipa_kpasswd.c @@ -0,0 +1,988 @@ + +/* Kpasswd-LDAP proxy */ + +/* (C) Simo Sorce */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <unistd.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#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"; + +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 = NULL; + int ret = LDAP_OTHER; + char *realm_name = (char *)priv_data; + + if (!ld) return LDAP_PARAM_ERROR; + + for (in = sit; in && in->id != SASL_CB_LIST_END; in++) { + switch(in->id) { + case SASL_CB_USER: + in->result = srv_pri_name; + in->len = strlen(srv_pri_name); + ret = LDAP_SUCCESS; + break; + case SASL_CB_GETREALM: + 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->id); + } + in->result = NULL; + in->len = 0; + ret = LDAP_OTHER; + } + } + return ret; +} + +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 = KRB5_KPASSWD_HARDERROR; + goto done; + } + + version = LDAP_VERSION3; + ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version); + if (ret != LDAP_OPT_SUCCESS) { + fprintf(stderr, "Unable to set ldap protocol version"); + ret = KRB5_KPASSWD_HARDERROR; + goto done; + } + + ret = ldap_sasl_interactive_bind_s(ld, + NULL, "GSSAPI", + NULL, NULL, + LDAP_SASL_AUTOMATIC, + ldap_sasl_interact, realm_name); + if (ret != LDAP_SUCCESS) { + fprintf(stderr, "Unable to bind to ldap server"); + 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 = KRB5_KPASSWD_HARDERROR; + goto done; + } + ber_printf(ctrl, "{tstON}", + LDAP_TAG_EXOP_MODIFY_PASSWD_ID, userdn, + LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, &newpw); + + ret = ber_flatten2(ctrl, &control, 0); + if (ret < 0) { + fprintf(stderr, "ber flattening failed!\n"); + ret = -1; + goto done; + } + + /* 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) ldap_unbind(ld); + if (ldap_uri) free(ldap_uri); + if (tmpfile) { + unlink(tmpfile); + free(tmpfile); + } + return ret; +} + +void handle_krb_packets(uint8_t *buf, ssize_t buflen, + struct sockaddr_in *from, + uint8_t **repbuf, ssize_t *replen) +{ + krb5_auth_context auth_context; + krb5_context context; + krb5_keytab keytab; + krb5_principal kprincpw; + krb5_ticket *ticket; + krb5_address **lkaddr, rkaddr; + krb5_data kreq, krep, kenc, kdec; + krb5_replay_data replay; + krb5_error krb5err; + int krberr, err; + struct sockaddr_in laddr, raddr; + socklen_t addrlen; + size_t reqlen; + size_t verno; + char *client_name, *realm_name; + char *result_string; + int result_err; + uint8_t *reply; + ssize_t replylen; + + *replen = 0; + + auth_context = NULL; + krep.length = 0; + krep.data = NULL; + kprincpw = NULL; + context = NULL; + ticket = NULL; + lkaddr = NULL; + + rkaddr.addrtype = ADDRTYPE_INET; + rkaddr.length = sizeof(from->sin_addr); + rkaddr.contents = (krb5_octet *) &from->sin_addr; + + if (buflen < 4) { + result_string = "Request truncated"; + result_err = KRB5_KPASSWD_MALFORMED; + fprintf(stderr, "%s\n", result_string); + goto done; + } + + reqlen = (buf[0] << 8) + buf[1]; + + if (reqlen != buflen) { + result_string = "Unmatching request length"; + result_err = KRB5_KPASSWD_MALFORMED; + fprintf(stderr, "%s\n", result_string); + goto done; + } + + verno = (buf[2] << 8) + buf[3]; + + if (verno != 1) { + result_string = "Unsupported version"; + result_err = KRB5_KPASSWD_BAD_VERSION; + fprintf(stderr, "%s\n", result_string); + goto done; + } + + kreq.length = (buf[4] << 8) + buf[5]; + if (kreq.length > (buflen - 6)) { + result_string = "Request truncated"; + result_err = KRB5_KPASSWD_MALFORMED; + fprintf(stderr, "%s\n", result_string); + goto done; + } + kreq.data = &buf[6]; + + krberr = krb5_init_context(&context); + if (krberr) { + result_string = "Failed to init kerberos context"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s\n", result_string); + 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"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + krberr = krb5_auth_con_setflags(context, auth_context, + KRB5_AUTH_CONTEXT_DO_SEQUENCE); + if (krberr) { + result_string = "Unable to init auth context"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + krberr = krb5_build_principal(context, &kprincpw, + strlen(realm_name), realm_name, + "kadmin", "changepw", NULL); + if (krberr) { + result_string = "Unable to build principal"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + krberr = krb5_kt_resolve(context, keytab_name, &keytab); + if (krberr) { + result_string = "Unable to retrieve keytab"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + krberr = krb5_rd_req(context, &auth_context, &kreq, + kprincpw, keytab, NULL, &ticket); + if (krberr) { + result_string = "Unable to read request"; + result_err = KRB5_KPASSWD_AUTHERROR; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + /* build the AP Reply before actually changing the password + * this minimize the risk of a fatal error occurring _after_ + * the password have been successfully changed */ + krberr = krb5_mk_rep(context, auth_context, &krep); + if (krberr) { + result_string = "Failed to to build reply"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + /* verify that this is an AS_REQ ticket */ + if (!(ticket->enc_part2->flags & TKT_FLG_INITIAL)) { + result_string = "Ticket must be derived from a password"; + result_err = KRB5_KPASSWD_AUTHERROR; + fprintf(stderr, "%s\n", result_string); + goto kpreply; + } + + krberr = krb5_unparse_name(context, ticket->enc_part2->client, + &client_name); + if (krberr) { + result_string = "Unable to parse client name"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s\n", result_string); + goto kpreply; + } + + krberr = krb5_auth_con_setaddrs(context, auth_context, NULL, &rkaddr); + if (krberr) { + result_string = "Failed to set client address"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto kpreply; + } + + /* decrypt the new password */ + kenc.length = reqlen - kreq.length - 6; + kenc.data = kreq.data + kreq.length; + + /* rd_priv needs the remote address while mk_priv (used later) + * requires the local address (from kadmin code) */ + krberr = krb5_rd_priv(context, auth_context, &kenc, &kdec, &replay); + if (krberr) { + result_string = "Failed to decrypt password"; + result_err = KRB5_KPASSWD_HARDERROR; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto kpreply; + } + + if (debug > 0) { + fprintf(stderr, "Client %s trying to set password [%*s]\n", + client_name, kdec.length, kdec.data); + } + + /* 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); + free(kdec.data); + +kpreply: + + /* set-up the the clear text reply */ + kdec.length = 2 + strlen(result_string); + kdec.data = malloc(kdec.length); + if (!kdec.data) { + fprintf(stderr, "Out of memory!\n"); + goto done; + } + + kdec.data[0] = (result_err >> 8) & 0xff; + kdec.data[1] = result_err & 0xff; + memcpy(&kdec.data[2], result_string, strlen(result_string)); + + /* we listen on ANYADDR, use this retrieve the right address */ + krberr = krb5_os_localaddr(context, &lkaddr); + if (krberr) { + result_string = "Failed to retrieve local address"; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + krberr = krb5_auth_con_setaddrs(context, auth_context, lkaddr[0], NULL); + if (krberr) { + result_string = "Failed to set local address"; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + krberr = krb5_mk_priv(context, auth_context, &kdec, &kenc, &replay); + if (krberr) { + result_string = "Failed to encrypt reply message"; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + /* encryption was unsuccessful, let's return a krb error */ + + /* the ap data is no more useful */ + free(krep.data); + krep.length = 0; + + /* build a krberror encrypted paylod */ + krb5err.error = KRB5_CHPW_FAIL; + krb5err.server = kprincpw; + krb5err.client = NULL; + krb5err.ctime = 0; + krb5err.cusec = 0; + krb5err.susec = 0; + krberr = krb5_timeofday(context, &krb5err.stime); + if (krberr) { + result_string = "Failed to set time of day"; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + krb5err.text.length = 0; + krb5err.e_data = kdec; + krberr = krb5_mk_error(context, &krb5err, &kenc); + if (krberr) { + result_string = "Failed to build error message"; + fprintf(stderr, "%s: %s\n", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + } + + replylen = 6 + krep.length + kenc.length; + reply = malloc(replylen); + if (!reply) { + fprintf(stderr, "Out of memory!\n"); + goto done; + } + *repbuf = reply; + + reply[0] = (replylen >> 8) & 0xff; + reply[1] = replylen & 0xff; + reply[2] = 0x00; + reply[3] = 0x01; + reply[4] = (krep.length >> 8) & 0xff; + reply[5] = krep.length & 0xff; + + if (krep.length) { + memcpy(&reply[6], krep.data, krep.length); + } + memcpy(&reply[6 + krep.length], kenc.data, kenc.length); + + *replen = replylen; + +done: + if (auth_context) krb5_auth_con_free(context, auth_context); + if (kprincpw) krb5_free_principal(context, kprincpw); + if (krep.length) free(krep.data); + if (ticket) krb5_free_ticket(context, ticket); + if (kdec.length) free(kdec.data); + if (lkaddr) krb5_free_addresses(context, lkaddr); + 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); + + /* receive request */ + if (type == KPASSWD_TCP) { + + mfd = accept(fd, (struct sockaddr *)&from, &fromlen); + if (mfd == -1) { + fprintf(stderr, "Accept failed with error (%d) %s\n", + errno, strerror(errno)); + return -1; + } + } else { + mfd = fd; + } + + reqlen = recvfrom(mfd, request, sizeof(request), 0, + (struct sockaddr *)&from, &fromlen); + if (reqlen <= 0) { + fprintf(stderr, "Error receiving request (%d) %s\n", + errno, strerror(errno)); + if (type == KPASSWD_TCP) close(mfd); + return -1; + } + + if (!inet_ntop(from.sin_family, &from.sin_addr, + address, sizeof(address))) { + address[0] = '\0'; + } + + if (debug > 0) { + uint16_t port = ntohs(from.sin_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 + /* handle kerberos and ldap operations in childrens */ + pid = fork(); + if (pid == -1) { + fprintf(stderr, "Fork failed with error (%d) %s\n", + errno, strerror(errno)); + if (type == KPASSWD_TCP) close(mfd); + return 0; + } + 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) { + sendret = sendto(mfd, reply, replen, 0, NULL, 0); + } else { + 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; + int tcp_s, udp_s; + int tru = 1; + int ret; + + tcp_s = socket(AF_INET, SOCK_STREAM, 0); + if (tcp_s == -1) { + fprintf(stderr, "Unable to create TCP socket\n"); + exit(1); + } + + udp_s = socket(AF_INET, SOCK_DGRAM, 0); + if (udp_s == -1) { + fprintf(stderr, "Unable to create UDP socket\n"); + close(tcp_s); + exit(1); + } + + /* make sockets immediately reusable */ + ret = setsockopt(tcp_s, SOL_SOCKET, SO_REUSEADDR, + (void *)&tru, sizeof(tru)); + if (ret == -1) { + fprintf(stderr, + "Unable to set SO_REUSEADDR for the TCP socket (%d) %s\n", + errno, strerror(errno)); + close(tcp_s); + close(udp_s); + exit(2); + } + + ret = setsockopt(udp_s, SOL_SOCKET, SO_REUSEADDR, + (void *)&tru, sizeof(tru)); + if (ret == -1) { + fprintf(stderr, + "Unable to set SO_REUSEADDR for the UDP socket (%d) %s\n", + errno, strerror(errno)); + close(tcp_s); + close(udp_s); + exit(2); + } + + /* bind sockets */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(KPASSWD_PORT); + + ret = bind(tcp_s, (struct sockaddr *)&addr, sizeof(addr)); + if (ret == -1) { + fprintf(stderr, + "Unable to bind the TCP kpasswd port (%d) %s\n", + errno, strerror(errno)); + close(tcp_s); + close(udp_s); + exit(3); + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(KPASSWD_PORT); + + ret = bind(udp_s, (struct sockaddr *)&addr, sizeof(addr)); + if (ret == -1) { + fprintf(stderr, + "Unable to bind the UDP kpasswd port (%d) %s\n", + errno, strerror(errno)); + close(tcp_s); + close(udp_s); + exit(3); + } + + ret = listen(tcp_s, 5); + if (ret == -1) { + fprintf(stderr, + "Unable to listen oin the TCP socket (%d) %s\n", + errno, strerror(errno)); + close(tcp_s); + close(udp_s); + exit(4); + } + + /* now that sockets are set up, enter the select loop */ + + while (1) { + 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, &tv); + + switch(ret) { + case 0: + break; + case -1: + if (errno != EINTR) { + fprintf(stderr, + "Unexpected error in select (%d) %s\n", + errno, strerror(errno)); + exit(5); + } + break; + default: + if (FD_ISSET(tcp_s, &rfd)) { + handle_conn(tcp_s, KPASSWD_TCP); + break; + } + if (FD_ISSET(udp_s, &rfd)) { + handle_conn(udp_s, KPASSWD_UDP); + break; + } + } + + /* check for children exiting */ + cid = waitpid(-1, &cstatus, WNOHANG); + if (cid != -1 && cid != 0) { + remove_blacklist(cid); + } + } +} diff --git a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c index f871ee4f..73f718aa 100644 --- a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c +++ b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c @@ -792,6 +792,11 @@ static int ipapwd_userpassword(Slapi_Entry *targetEntry, const char *newPasswd) } } + /* TODO !!! + * instead of replace we should use a delete/add so that we are + * completely sure nobody else modified the entry meanwhile and + * fail if that's the case */ + /* commit changes */ ret = ipapwd_apply_mods(dn, smods); diff --git a/ipa-server/ipaserver/krbinstance.py b/ipa-server/ipaserver/krbinstance.py index 84f8ebf2..df403471 100644 --- a/ipa-server/ipaserver/krbinstance.py +++ b/ipa-server/ipaserver/krbinstance.py @@ -87,6 +87,8 @@ class KrbInstance: self.__create_http_keytab() + self.__set_kadmin_changepw_preauth() + self.__create_sample_bind_zone() self.start() @@ -148,19 +150,19 @@ class KrbInstance: # TODO: NOT called yet, need to find out how to make sure the plugin is available first def __add_pwd_extop_module(self): - #add the password extop module - extop_txt = template_file(SHARE_DIR + "ipapwd_extop_plugin.ldif", self.sub_dict) - extop_fd = write_tmp_file(extop_txt) - ldap_mod(extop_fd, "cn=Directory Manager", self.admin_password) - extop_fd.close() - - #add an ACL to let the DS user read the master key - args = ["/usr/bin/setfacl", "-m", "u:"+self.ds_user+":r", "/var/kerberos/krb5kdc/.k5."+self.realm] - run(args) + #add the password extop module + extop_txt = template_file(SHARE_DIR + "ipapwd_extop_plugin.ldif", self.sub_dict) + extop_fd = write_tmp_file(extop_txt) + ldap_mod(extop_fd, "cn=Directory Manager", self.admin_password) + extop_fd.close() + + #add an ACL to let the DS user read the master key + args = ["/usr/bin/setfacl", "-m", "u:"+self.ds_user+":r", "/var/kerberos/krb5kdc/.k5."+self.realm] + run(args) def __create_sample_bind_zone(self): bind_txt = template_file(SHARE_DIR + "bind.zone.db.template", self.sub_dict) - [bind_fd, bind_name] = tempfile.mkstemp(".db","sample.zone.") + [bind_fd, bind_name] = tempfile.mkstemp(".db","sammple.zone.") os.write(bind_fd, bind_txt) os.close(bind_fd) print "Sample zone file for bind has been created in "+bind_name @@ -181,6 +183,14 @@ class KrbInstance: pent = pwd.getpwnam(self.ds_user) os.chown("/etc/sysconfig/fedora-ds", pent.pw_uid, pent.pw_gid) + def __set_kadmin_changepw_preauth(self): + (kwrite, kread, kerr) = os.popen3("/usr/kerberos/sbin/kadmin.local") + kwrite.write("modprinc +requires_preauth kadmin/changepw\n") + kwrite.flush() + kwrite.close() + kread.close() + kerr.close() + def __create_http_keytab(self): (kwrite, kread, kerr) = os.popen3("/usr/kerberos/sbin/kadmin.local") kwrite.write("addprinc -randkey HTTP/"+self.fqdn+"@"+self.realm+"\n") |