diff options
author | Rob Crittenden <rcritten@redhat.com> | 2009-01-29 16:26:07 -0500 |
---|---|---|
committer | Rob Crittenden <rcritten@redhat.com> | 2009-02-03 15:27:14 -0500 |
commit | e30cd6ba42c256d2016db45146d616f329455e86 (patch) | |
tree | d4c5291095c80c92bc4803fe7f20fc2838124ffa /daemons/ipa-kpasswd | |
parent | c4ed025001895bfc65c613cabbbfcb27c19cc29f (diff) | |
download | freeipa-e30cd6ba42c256d2016db45146d616f329455e86.tar.gz freeipa-e30cd6ba42c256d2016db45146d616f329455e86.tar.xz freeipa-e30cd6ba42c256d2016db45146d616f329455e86.zip |
Mass tree reorganization for IPAv2. To view previous history of files use:
% git log --follow -- <file>
renamed: ipa-server/autogen.sh -> autogen.sh
renamed: ipa-server/ipa-kpasswd/Makefile.am -> daemons/ipa-kpasswd/Makefile.am
renamed: ipa-server/ipa-kpasswd/README -> daemons/ipa-kpasswd/README
renamed: ipa-server/ipa-kpasswd/ipa_kpasswd.c -> daemons/ipa-kpasswd/ipa_kpasswd.c
renamed: ipa-server/ipa-kpasswd/ipa_kpasswd.init -> daemons/ipa-kpasswd/ipa_kpasswd.init
renamed: ipa-server/ipa-slapi-plugins/Makefile.am -> daemons/ipa-slapi-plugins/Makefile.am
renamed: ipa-server/ipa-slapi-plugins/README -> daemons/ipa-slapi-plugins/README
renamed: ipa-server/ipa-slapi-plugins/dna/Makefile.am -> daemons/ipa-slapi-plugins/dna/Makefile.am
renamed: ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif -> daemons/ipa-slapi-plugins/dna/dna-conf.ldif
renamed: ipa-server/ipa-slapi-plugins/dna/dna.c -> daemons/ipa-slapi-plugins/dna/dna.c
renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile.am -> daemons/ipa-slapi-plugins/ipa-memberof/Makefile.am
renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c -> daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c
renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h -> daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof.h
renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c -> daemons/ipa-slapi-plugins/ipa-memberof/ipa-memberof_config.c
renamed: ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif -> daemons/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif
renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am -> daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am
renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/README -> daemons/ipa-slapi-plugins/ipa-pwd-extop/README
renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c -> daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
renamed: ipa-server/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif -> daemons/ipa-slapi-plugins/ipa-pwd-extop/pwd-extop-conf.ldif
renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/Makefile.am -> daemons/ipa-slapi-plugins/ipa-winsync/Makefile.am
renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/README -> daemons/ipa-slapi-plugins/ipa-winsync/README
renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif
renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync-config.c
renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.c
renamed: ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h -> daemons/ipa-slapi-plugins/ipa-winsync/ipa-winsync.h
renamed: ipa-server/xmlrpc-server/ipa-rewrite.conf -> install/conf/ipa-rewrite.conf
renamed: ipa-server/xmlrpc-server/ipa.conf -> install/conf/ipa.conf
renamed: ipa-server/xmlrpc-server/ssbrowser.html -> install/html/ssbrowser.html
renamed: ipa-server/xmlrpc-server/unauthorized.html -> install/html/unauthorized.html
renamed: ipa-server/ipa-install/share/60ipaconfig.ldif -> install/share/60ipaconfig.ldif
renamed: ipa-server/ipa-install/share/60kerberos.ldif -> install/share/60kerberos.ldif
renamed: ipa-server/ipa-install/share/60radius.ldif -> install/share/60radius.ldif
renamed: ipa-server/ipa-install/share/60samba.ldif -> install/share/60samba.ldif
renamed: ipa-server/ipa-install/share/Makefile.am -> install/share/Makefile.am
renamed: ipa-server/ipa-install/share/bind.named.conf.template -> install/share/bind.named.conf.template
renamed: ipa-server/ipa-install/share/bind.zone.db.template -> install/share/bind.zone.db.template
renamed: ipa-server/ipa-install/share/bootstrap-template.ldif -> install/share/bootstrap-template.ldif
renamed: ipa-server/ipa-install/share/certmap.conf.template -> install/share/certmap.conf.template
renamed: ipa-server/ipa-install/share/default-aci.ldif -> install/share/default-aci.ldif
renamed: ipa-server/ipa-install/share/default-keytypes.ldif -> install/share/default-keytypes.ldif
renamed: ipa-server/ipa-install/share/dna-posix.ldif -> install/share/dna-posix.ldif
renamed: ipa-server/ipa-install/share/encrypted_attribute.ldif -> install/share/encrypted_attribute.ldif
renamed: ipa-server/ipa-install/share/fedora-ds.init.patch -> install/share/fedora-ds.init.patch
renamed: ipa-server/ipa-install/share/indices.ldif -> install/share/indices.ldif
renamed: ipa-server/ipa-install/share/kdc.conf.template -> install/share/kdc.conf.template
renamed: ipa-server/ipa-install/share/kerberos.ldif -> install/share/kerberos.ldif
renamed: ipa-server/ipa-install/share/krb.con.template -> install/share/krb.con.template
renamed: ipa-server/ipa-install/share/krb5.conf.template -> install/share/krb5.conf.template
renamed: ipa-server/ipa-install/share/krb5.ini.template -> install/share/krb5.ini.template
renamed: ipa-server/ipa-install/share/krbrealm.con.template -> install/share/krbrealm.con.template
renamed: ipa-server/ipa-install/share/master-entry.ldif -> install/share/master-entry.ldif
renamed: ipa-server/ipa-install/share/memberof-task.ldif -> install/share/memberof-task.ldif
renamed: ipa-server/ipa-install/share/ntp.conf.server.template -> install/share/ntp.conf.server.template
renamed: ipa-server/ipa-install/share/ntpd.sysconfig.template -> install/share/ntpd.sysconfig.template
renamed: ipa-server/ipa-install/share/preferences.html.template -> install/share/preferences.html.template
renamed: ipa-server/ipa-install/share/referint-conf.ldif -> install/share/referint-conf.ldif
renamed: ipa-server/ipa-install/share/schema_compat.uldif -> install/share/schema_compat.uldif
renamed: ipa-server/ipa-install/share/unique-attributes.ldif -> install/share/unique-attributes.ldif
renamed: ipa-server/ipa-install/Makefile.am -> install/tools/Makefile.am
renamed: ipa-server/ipa-install/README -> install/tools/README
renamed: ipa-server/ipa-compat-manage -> install/tools/ipa-compat-manage
renamed: ipa-server/ipa-fix-CVE-2008-3274 -> install/tools/ipa-fix-CVE-2008-3274
renamed: ipa-server/ipa-ldap-updater -> install/tools/ipa-ldap-updater
renamed: ipa-server/ipa-install/ipa-replica-install -> install/tools/ipa-replica-install
renamed: ipa-server/ipa-install/ipa-replica-manage -> install/tools/ipa-replica-manage
renamed: ipa-server/ipa-install/ipa-replica-prepare -> install/tools/ipa-replica-prepare
renamed: ipa-server/ipa-install/ipa-server-certinstall -> install/tools/ipa-server-certinstall
renamed: ipa-server/ipa-install/ipa-server-install -> install/tools/ipa-server-install
renamed: ipa-server/ipa-upgradeconfig -> install/tools/ipa-upgradeconfig
renamed: ipa-server/ipa-install/ipactl -> install/tools/ipactl
renamed: ipa-server/man/Makefile.am -> install/tools/man/Makefile.am
renamed: ipa-server/man/ipa-compat-manage.1 -> install/tools/man/ipa-compat-manage.1
renamed: ipa-server/man/ipa-ldap-updater.1 -> install/tools/man/ipa-ldap-updater.1
renamed: ipa-server/man/ipa-replica-install.1 -> install/tools/man/ipa-replica-install.1
renamed: ipa-server/man/ipa-replica-manage.1 -> install/tools/man/ipa-replica-manage.1
renamed: ipa-server/man/ipa-replica-prepare.1 -> install/tools/man/ipa-replica-prepare.1
renamed: ipa-server/man/ipa-server-certinstall.1 -> install/tools/man/ipa-server-certinstall.1
renamed: ipa-server/man/ipa-server-install.1 -> install/tools/man/ipa-server-install.1
renamed: ipa-server/man/ipa_kpasswd.8 -> install/tools/man/ipa_kpasswd.8
renamed: ipa-server/man/ipa_webgui.8 -> install/tools/man/ipa_webgui.8
renamed: ipa-server/man/ipactl.8 -> install/tools/man/ipactl.8
renamed: ipa-server/ipa-install/updates/Makefile.am -> install/updates/Makefile.am
renamed: ipa-server/ipa-install/updates/RFC2307bis.update -> install/updates/RFC2307bis.update
renamed: ipa-server/ipa-install/updates/RFC4876.update -> install/updates/RFC4876.update
renamed: ipa-server/ipa-install/updates/indices.update -> install/updates/indices.update
renamed: ipa-server/ipa-install/updates/nss_ldap.update -> install/updates/nss_ldap.update
renamed: ipa-server/ipa-install/updates/replication.update -> install/updates/replication.update
renamed: ipa-server/ipa-install/updates/winsync_index.update -> install/updates/winsync_index.update
renamed: ipa-server/ipaserver/Makefile.am -> ipaserver/install/Makefile.am
renamed: ipa-server/ipaserver/__init__.py -> ipaserver/install/__init__.py
renamed: ipa-server/ipaserver/bindinstance.py -> ipaserver/install/bindinstance.py
renamed: ipa-server/ipaserver/certs.py -> ipaserver/install/certs.py
renamed: ipa-server/ipaserver/dsinstance.py -> ipaserver/install/dsinstance.py
renamed: ipa-server/ipaserver/httpinstance.py -> ipaserver/install/httpinstance.py
renamed: ipa-server/ipaserver/installutils.py -> ipaserver/install/installutils.py
renamed: ipa-server/ipaserver/ipaldap.py -> ipaserver/install/ipaldap.py
renamed: ipa-server/ipaserver/krbinstance.py -> ipaserver/install/krbinstance.py
renamed: ipa-server/ipaserver/ldapupdate.py -> ipaserver/install/ldapupdate.py
renamed: ipa-server/ipaserver/ntpinstance.py -> ipaserver/install/ntpinstance.py
renamed: ipa-server/ipaserver/replication.py -> ipaserver/install/replication.py
renamed: ipa-server/ipaserver/service.py -> ipaserver/install/service.py
renamed: ipa-server/selinux/Makefile -> selinux/Makefile
renamed: ipa-server/selinux/ipa-server-selinux.spec.in -> selinux/ipa-server-selinux.spec.in
renamed: ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.fc -> selinux/ipa_kpasswd/ipa_kpasswd.fc
renamed: ipa-server/selinux/ipa_kpasswd/ipa_kpasswd.te -> selinux/ipa_kpasswd/ipa_kpasswd.te
renamed: ipa-server/selinux/ipa_webgui/ipa_webgui.fc -> selinux/ipa_webgui/ipa_webgui.fc
renamed: ipa-server/selinux/ipa_webgui/ipa_webgui.te -> selinux/ipa_webgui/ipa_webgui.te
renamed: ipa-server/version.m4.in -> version.m4.in
Diffstat (limited to 'daemons/ipa-kpasswd')
-rw-r--r-- | daemons/ipa-kpasswd/Makefile.am | 58 | ||||
-rw-r--r-- | daemons/ipa-kpasswd/README | 2 | ||||
-rw-r--r-- | daemons/ipa-kpasswd/ipa_kpasswd.c | 1388 | ||||
-rw-r--r-- | daemons/ipa-kpasswd/ipa_kpasswd.init | 83 |
4 files changed, 1531 insertions, 0 deletions
diff --git a/daemons/ipa-kpasswd/Makefile.am b/daemons/ipa-kpasswd/Makefile.am new file mode 100644 index 000000000..5f95fdef3 --- /dev/null +++ b/daemons/ipa-kpasswd/Makefile.am @@ -0,0 +1,58 @@ +NULL = + +INCLUDES = \ + -I. \ + -I$(srcdir) \ + -DPREFIX=\""$(prefix)"\" \ + -DBINDIR=\""$(bindir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DLIBEXECDIR=\""$(libexecdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + $(LDAP_CFLAGS) \ + $(KRB5_CFLAGS) \ + $(WARN_CFLAGS) \ + $(NULL) + +sbin_PROGRAMS = \ + ipa_kpasswd \ + $(NULL) + +ipa_kpasswd_SOURCES = \ + ipa_kpasswd.c \ + $(NULL) + +ipa_kpasswd_LDADD = \ + $(LDAP_LIBS) \ + $(KRB5_LIBS) \ + $(NULL) + +install-exec-local: + mkdir -p $(DESTDIR)$(localstatedir)/cache/ipa/kpasswd + chmod 700 $(DESTDIR)$(localstatedir)/cache/ipa/kpasswd + +uninstall-local: + -rmdir $(DESTDIR)$(localstatedir)/cache/ipa/kpasswd + -rmdir $(DESTDIR)$(localstatedir)/cache/ipa + +EXTRA_DIST = \ + README \ + ipa_kpasswd.init \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in + +initdir=$(sysconfdir)/rc.d/init.d + +install-data-hook: ipa_kpasswd.init + + if test '!' -d $(DESTDIR)$(initdir); then \ + $(mkinstalldirs) $(DESTDIR)$(initdir); \ + chmod 755 $(DESTDIR)$(initdir); \ + fi + + $(INSTALL_SCRIPT) $(srcdir)/ipa_kpasswd.init $(DESTDIR)$(initdir)/ipa_kpasswd + +uninstall-hook: + rm -f $(DESTDIR)$(initdir)/ipa_kpasswd diff --git a/daemons/ipa-kpasswd/README b/daemons/ipa-kpasswd/README new file mode 100644 index 000000000..c0a2767a4 --- /dev/null +++ b/daemons/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/daemons/ipa-kpasswd/ipa_kpasswd.c b/daemons/ipa-kpasswd/ipa_kpasswd.c new file mode 100644 index 000000000..f2d3490f9 --- /dev/null +++ b/daemons/ipa-kpasswd/ipa_kpasswd.c @@ -0,0 +1,1388 @@ + +/* Kpasswd-LDAP proxy */ + +/* Authors: Simo Sorce <ssorce@redhat.com> + * + * Copyright (C) 2007, 2008 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; version 2 only + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define _GNU_SOURCE + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <sys/poll.h> +#include <unistd.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <syslog.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <time.h> +#include <krb5.h> +#ifdef WITH_MOZLDAP +#include <mozldap/ldap.h> +#else +#define LDAP_DEPRECATED 1 +#include <ldap.h> +#endif +#include <sasl/sasl.h> +#include <ifaddrs.h> + +#define DEFAULT_KEYTAB "FILE:/var/kerberos/krb5kdc/kpasswd.keytab" +#define TMP_TEMPLATE "/var/cache/ipa/kpasswd/krb5_cc.XXXXXX" +#define KPASSWD_PORT 464 + +#ifdef WITH_MOZLDAP +/* From OpenLDAP's ldap.h */ +#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID ((ber_tag_t) 0x80U) +#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ((ber_tag_t) 0x82U) +#endif + +/* blacklist entries are released only BLCAKLIST_TIMEOUT seconds + * after the children performing the noperation has finished. + * this is to avoid races */ + +#define BLACKLIST_TIMEOUT 5 + +struct blacklist { + struct blacklist *next; + char *address; + pid_t pid; + time_t expire; +}; + +static struct blacklist *global_blacklist = NULL; + +struct socklist { + int fd; + int socktype; + int dest_addr_len; + struct sockaddr_storage dest_addr; + struct socklist *next; +}; + +int check_blacklist(char *address) +{ + struct blacklist *bl, *prev_bl; + time_t now = time(NULL); + + if (!global_blacklist) { + return 0; + } + + prev_bl = NULL; + bl = global_blacklist; + while (bl) { + if (bl->expire && (bl->expire < now)) { + if (prev_bl) { + prev_bl->next = bl->next; + free(bl->address); + free(bl); + bl = prev_bl->next; + } else { + global_blacklist = bl->next; + free(bl->address); + free(bl); + bl = global_blacklist; + } + continue; + } + + if (strcmp(address, bl->address) == 0) { + return 1; + } + + prev_bl = bl; + bl = bl->next; + } + + 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->expire = 0; + 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; + + if (!global_blacklist) { + return -1; + } + + bl = global_blacklist; + while (bl) { + if (pid == bl->pid) { + bl->expire = time(NULL) + BLACKLIST_TIMEOUT; + return 0; + } + bl = bl->next; + } + return -1; +} + +int debug = 0; +char *srv_pri_name = "kadmin/changepw"; +char *keytab_name = NULL; + +static int get_krb5_ticket(char *tmp_file) +{ + 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) { + syslog(LOG_ERR, "Failed to init kerberos context"); + return -1; + } + + krberr = krb5_get_default_realm(context, &realm_name); + if (krberr) { + syslog(LOG_ERR, "Failed to get default realm name: %s", + 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) { + syslog(LOG_ERR, "Unable to build principal: %s", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + krberr = krb5_kt_resolve(context, keytab_name, &keytab); + if (krberr) { + syslog(LOG_ERR, "Failed to read keytab file: %s", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + ret = asprintf(&ccname, "FILE:%s", tmp_file); + if (ret == -1) { + syslog(LOG_ERR, "Out of memory!"); + goto done; + } + + ret = setenv("KRB5CCNAME", ccname, 1); + if (ret == -1) { + syslog(LOG_ERR, "Unable to set env. variable KRB5CCNAME!"); + goto done; + } + + krberr = krb5_cc_resolve(context, ccname, &ccache); + if (krberr) { + syslog(LOG_ERR, "Failed to set cache name: %s", + 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) { + syslog(LOG_ERR, "Failed to init credentials: %s", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + krb5_cc_initialize(context, ccache, kprincpw); + if (krberr) { + syslog(LOG_ERR, "Failed to init ccache: %s", + krb5_get_error_message(context, krberr)); + ret = -1; + goto done; + } + + krberr = krb5_cc_store_cred(context, ccache, &my_creds); + if (krberr) { + syslog(LOG_ERR, "Failed to store creds: %s", + 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) { + syslog(LOG_ERR, + "Unhandled SASL int. option %ld", + in->id); + } + in->result = NULL; + in->len = 0; + ret = LDAP_OTHER; + } + } + return ret; +} + +/* from DS ldaprot.h */ +#define LDAP_TAG_PWP_WARNING 0xA0 /* context specific + constructed + 0 */ +#define LDAP_TAG_PWP_SECSLEFT 0x80L /* context specific + primitive */ +#define LDAP_TAG_PWP_GRCLOGINS 0x81L /* context specific + primitive + 1 */ +#define LDAP_TAG_PWP_ERROR 0x81L /* context specific + primitive + 1 */ + +int ldap_pwd_change(char *client_name, char *realm_name, krb5_data pwd, char **errstr) +{ + char *tmp_file = NULL; + int version; + LDAP *ld = NULL; + BerElement *ctrl = NULL; + BerElement *sctrl = NULL; + struct berval *control = NULL; + struct berval newpw; + char hostname[1024]; + 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; + LDAPControl **srvctrl = NULL; + char *exterr0 = NULL; + char *exterr1 = NULL; + char *exterr2 = NULL; + char *err = NULL; + int msgid; + int ret, rc; + int fd; + int kpwd_err = KRB5_KPASSWD_HARDERROR; + + tmp_file = strdup(TMP_TEMPLATE); + if (!tmp_file) { + syslog(LOG_ERR, "Out of memory!"); + goto done; + } + + fd = mkstemp(tmp_file); + if (fd == -1) { + syslog(LOG_ERR, + "Failed to create tmp file with errno: %d", errno); + goto done; + } + /* close mimmediately, we don't need to keep the file open, + * just that it exist and has a unique name */ + close(fd); + + /* 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(tmp_file); + if (ret) { + syslog(LOG_ERR, "Unable to kinit!"); + 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) { + syslog(LOG_ERR, "Unable to get the hostname!"); + goto done; + } + + /* connect to ldap server */ + /* TODO: support referrals ? */ + ld = ldap_init(hostname, 389); + if(ld == NULL) { + syslog(LOG_ERR, "Unable to connect to ldap server"); + goto done; + } + + version = LDAP_VERSION3; + ret = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version); + if (ret != LDAP_SUCCESS) { + syslog(LOG_ERR, "Unable to set ldap protocol version"); + 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) { + syslog(LOG_ERR, "Unable to bind to ldap server"); + goto done; + } + + /* find base dn */ + /* TODO: address the case where we have multiple naming contexts */ + 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) { + syslog(LOG_ERR, + "Search for %s on rootdse failed with error %d", + root_attrs[0], ret); + 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) { + syslog(LOG_ERR, "No values for %s", root_attrs[0]); + 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) { + syslog(LOG_ERR, "Out of memory!"); + 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) { + syslog(LOG_ERR, "Search for %s failed with error %d", + filter, ret); + if (ret == LDAP_CONSTRAINT_VIOLATION) { + *errstr = strdup("Password Change Failed"); + kpwd_err = KRB5_KPASSWD_SOFTERROR; + } + 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); + res = NULL; + + if (!userdn) { + syslog(LOG_ERR, "No userdn, can't change password!"); + goto done; + } + + /* build password change control */ + ctrl = ber_alloc_t(LBER_USE_DER); + if (!ctrl) { + syslog(LOG_ERR, "Out of memory!"); + goto done; + } + + ber_printf(ctrl, "{tstO}", + LDAP_TAG_EXOP_MODIFY_PASSWD_ID, userdn, + LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, &newpw); + + ret = ber_flatten(ctrl, &control); + if (ret < 0) { + syslog(LOG_ERR, "ber flattening failed!"); + goto done; + } + + /* perform password change */ + ret = ldap_extended_operation(ld, + LDAP_EXOP_MODIFY_PASSWD, + control, NULL, NULL, + &msgid); + if (ret != LDAP_SUCCESS) { + syslog(LOG_ERR, "ldap_extended_operation() failed. (%d)", ret); + goto done; + } + + tv.tv_sec = 10; + tv.tv_usec = 0; + + ret = ldap_result(ld, msgid, 1, &tv, &res); + if (ret == -1) { + ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &rc); + syslog(LOG_ERR, "ldap_result() failed. (%d)", rc); + goto done; + } + + ret = ldap_parse_extended_result(ld, res, &retoid, &retdata, 0); + if(ret != LDAP_SUCCESS) { + syslog(LOG_ERR, "ldap_parse_extended_result() failed."); + ldap_msgfree(res); + goto done; + } + if (retoid || retdata) { + syslog(LOG_ERR, "ldap_parse_extended_result() returned data, but we don't handle it yet."); + } + + ret = ldap_parse_result(ld, res, &rc, NULL, &err, NULL, &srvctrl, 0); + if(ret != LDAP_SUCCESS) { + syslog(LOG_ERR, "ldap_parse_result() failed."); + goto done; + } + if (rc != LDAP_SUCCESS) { + if (rc == LDAP_CONSTRAINT_VIOLATION) { + kpwd_err = KRB5_KPASSWD_SOFTERROR; + } + ret = LDAP_OPERATIONS_ERROR; + } + if (err) { + syslog(LOG_ERR, "ldap_parse_result(): [%s]", err); + ldap_memfree(err); + } + + if (srvctrl) { + + LDAPControl *pprc = NULL; + int i; + + for (i = 0; srvctrl[i]; i++) { + if (0 == strcmp(srvctrl[i]->ldctl_oid, LDAP_CONTROL_PASSWORDPOLICYRESPONSE)) { + pprc = srvctrl[i]; + } + } + if (pprc) { + sctrl = ber_init(&pprc->ldctl_value); + } + + if (sctrl) { + /* + * PasswordPolicyResponseValue ::= SEQUENCE { + * warning [0] CHOICE OPTIONAL { + * timeBeforeExpiration [0] INTEGER (0 .. maxInt), + * graceLoginsRemaining [1] INTEGER (0 .. maxInt) } + * error [1] ENUMERATED OPTIONAL { + * passwordExpired (0), + * accountLocked (1), + * changeAfterReset (2), + * passwordModNotAllowed (3), + * mustSupplyOldPassword (4), + * invalidPasswordSyntax (5), + * passwordTooShort (6), + * passwordTooYoung (7), + * passwordInHistory (8) } } + */ + + ber_tag_t rtag, btag; + ber_int_t bint; + rtag = ber_scanf(sctrl, "{t", &btag); + if (btag == LDAP_TAG_PWP_WARNING) { + rtag = ber_scanf(sctrl, "{ti}", &btag, &bint); + if (btag == LDAP_TAG_PWP_SECSLEFT) { + ret = asprintf(&exterr2, " (%d seconds left before password expires)", bint); + } else { + ret = asprintf(&exterr2, " (%d grace logins remaining)", bint); + } + if (ret == -1) { + syslog(LOG_ERR, "OOM while creating error message ..."); + exterr2 = NULL; + } + rtag = ber_scanf(sctrl, "t", &btag); + } + if (btag == LDAP_TAG_PWP_ERROR) { + rtag = ber_scanf(sctrl, "e", &bint); + switch(bint) { + case 0: + ret = asprintf(&exterr1, " Err%d: Password Expired.", bint); + break; + case 1: + ret = asprintf(&exterr1, " Err%d: Account locked.", bint); + break; + case 2: + ret = asprintf(&exterr1, " Err%d: Password changed after reset.", bint); + break; + case 3: + ret = asprintf(&exterr1, " Err%d: Password change not allowed.", bint); + break; + case 4: + ret = asprintf(&exterr1, " Err%d: [Shouldn't happen].", bint); + break; + case 5: + ret = asprintf(&exterr1, " Err%d: Password too simple.", bint); + break; + case 6: + ret = asprintf(&exterr1, " Err%d: Password too short.", bint); + break; + case 7: + ret = asprintf(&exterr1, " Err%d: Too soon to change password.", bint); + break; + case 8: + ret = asprintf(&exterr1, " Err%d: Password reuse not permitted.", bint); + break; + default: + ret = asprintf(&exterr1, " Err%d: Unknown Errorcode.", bint); + break; + } + if (ret == -1) { + syslog(LOG_ERR, "OOM while creating error message ..."); + exterr1 = NULL; + } + } + } + } + + if (ret == LDAP_SUCCESS) { + kpwd_err = KRB5_KPASSWD_SUCCESS; + exterr0 = "Password change succeeded"; + } else { + exterr0 = "Password change failed"; + } + ret = asprintf(errstr, "%s%s%s", exterr0, exterr1?exterr1:"", exterr2?exterr2:""); + if (ret == -1) { + syslog(LOG_ERR, "OOM while creating error message ..."); + *errstr = NULL; + } + +done: + if (ctrl) ber_free(ctrl, 1); + if (sctrl) ber_free(sctrl, 1); + if (srvctrl) ldap_controls_free(srvctrl); + if (res) ldap_msgfree(res); + if (control) ber_bvfree(control); + free(exterr1); + free(exterr2); + free(userdn); + if (ld) ldap_unbind_ext(ld, NULL, NULL); + if (tmp_file) { + unlink(tmp_file); + free(tmp_file); + } + return kpwd_err; +} + +void handle_krb_packets(uint8_t *buf, ssize_t buflen, + struct socklist *sd, + struct sockaddr_storage *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; + 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; + + result_string = NULL; + auth_context = NULL; + krep.length = 0; + krep.data = NULL; + kdec.length = 0; + kdec.data = NULL; + kprincpw = NULL; + context = NULL; + ticket = NULL; + + switch(((struct sockaddr *)from)->sa_family) { + case AF_INET: + lkaddr.addrtype = ADDRTYPE_INET; + lkaddr.length = sizeof(((struct sockaddr_in *)&sd->dest_addr)->sin_addr); + lkaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)&sd->dest_addr)->sin_addr); + + rkaddr.addrtype = ADDRTYPE_INET; + rkaddr.length = sizeof(((struct sockaddr_in *)from)->sin_addr); + rkaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)from)->sin_addr); + break; + case AF_INET6: + if (IN6_IS_ADDR_V4MAPPED (&((struct sockaddr_in6 *)from)->sin6_addr)) { + lkaddr.addrtype = ADDRTYPE_INET; + lkaddr.length = 4; + lkaddr.contents = 12 + (krb5_octet *) &(((struct sockaddr_in6 *)&sd->dest_addr)->sin6_addr); + + rkaddr.addrtype = ADDRTYPE_INET; + rkaddr.length = 4; + rkaddr.contents = 12 + (krb5_octet *) &(((struct sockaddr_in6 *)from)->sin6_addr); + } else { + lkaddr.addrtype = ADDRTYPE_INET6; + lkaddr.length = sizeof(((struct sockaddr_in6 *)&sd->dest_addr)->sin6_addr); + lkaddr.contents = (krb5_octet *) &(((struct sockaddr_in6 *)&sd->dest_addr)->sin6_addr); + + rkaddr.addrtype = ADDRTYPE_INET6; + rkaddr.length = sizeof(((struct sockaddr_in6 *)from)->sin6_addr); + rkaddr.contents = (krb5_octet *) &(((struct sockaddr_in6 *)from)->sin6_addr); + } + break; + default: + result_string = strdup("Invalid remopte IP address"); + result_err = KRB5_KPASSWD_MALFORMED; + syslog(LOG_ERR, "%s", result_string); + goto done; + } + + if (buflen < 4) { + result_string = strdup("Request truncated"); + result_err = KRB5_KPASSWD_MALFORMED; + syslog(LOG_ERR, "%s", result_string); + goto done; + } + + reqlen = (buf[0] << 8) + buf[1]; + + if (reqlen != buflen) { + result_string = strdup("Unmatching request length"); + result_err = KRB5_KPASSWD_MALFORMED; + syslog(LOG_ERR, "%s", result_string); + goto done; + } + + verno = (buf[2] << 8) + buf[3]; + + if (verno != 1) { + result_string = strdup("Unsupported version"); + result_err = KRB5_KPASSWD_BAD_VERSION; + syslog(LOG_ERR, "%s", result_string); + goto done; + } + + kreq.length = (buf[4] << 8) + buf[5]; + if (kreq.length > (buflen - 6)) { + result_string = strdup("Request truncated"); + result_err = KRB5_KPASSWD_MALFORMED; + syslog(LOG_ERR, "%s", result_string); + goto done; + } + kreq.data = (char *)&buf[6]; + + krberr = krb5_init_context(&context); + if (krberr) { + result_string = strdup("Failed to init kerberos context"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s", result_string); + goto done; + } + + krberr = krb5_get_default_realm(context, &realm_name); + if (krberr) { + result_string = strdup("Failed to get default realm name"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s", result_string); + goto done; + } + + krberr = krb5_auth_con_init(context, &auth_context); + if (krberr) { + result_string = strdup("Unable to init auth context"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s: %s", 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 = strdup("Unable to init auth context"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s: %s", 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 = strdup("Unable to build principal"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s: %s", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + krberr = krb5_kt_resolve(context, keytab_name, &keytab); + if (krberr) { + result_string = strdup("Unable to retrieve keytab"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s: %s", 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 = strdup("Unable to read request"); + result_err = KRB5_KPASSWD_AUTHERROR; + syslog(LOG_ERR, "%s: %s", 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 = strdup("Failed to to build reply"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s: %s", 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 = strdup("Ticket must be derived from a password"); + result_err = KRB5_KPASSWD_AUTHERROR; + syslog(LOG_ERR, "%s", result_string); + goto kpreply; + } + + krberr = krb5_unparse_name(context, ticket->enc_part2->client, + &client_name); + if (krberr) { + result_string = strdup("Unable to parse client name"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s", result_string); + goto kpreply; + } + + krberr = krb5_auth_con_setaddrs(context, auth_context, NULL, &rkaddr); + if (krberr) { + result_string = strdup("Failed to set client address"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s: %s", 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 = strdup("Failed to decrypt password"); + result_err = KRB5_KPASSWD_HARDERROR; + syslog(LOG_ERR, "%s: %s", result_string, + krb5_get_error_message(context, krberr)); + goto kpreply; + } + + if (debug > 100) { + syslog(LOG_ERR, "Client %s trying to set password [%*s]", + client_name, kdec.length, kdec.data); + } + + /* Actually try to change the password */ + result_err = ldap_pwd_change(client_name, realm_name, kdec, &result_string); + if (result_string == NULL) { + result_string = strdup("Server Error while performing LDAP password change"); + } + syslog(LOG_ERR, "%s", result_string); + + /* make sure password is cleared off before we free the memory */ + memset(kdec.data, 0, kdec.length); + free(kdec.data); + kdec.length = 0; + +kpreply: + + /* set-up the the clear text reply */ + kdec.length = 2 + strlen(result_string); + kdec.data = malloc(kdec.length); + if (!kdec.data) { + syslog(LOG_ERR, "Out of memory!"); + kdec.length = 0; + goto done; + } + + kdec.data[0] = (result_err >> 8) & 0xff; + kdec.data[1] = result_err & 0xff; + memcpy(&kdec.data[2], result_string, strlen(result_string)); + + krberr = krb5_auth_con_setaddrs(context, auth_context, &lkaddr, NULL); + if (krberr) { + result_string = strdup("Failed to set local address"); + syslog(LOG_ERR, "%s: %s", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + + krberr = krb5_mk_priv(context, auth_context, &kdec, &kenc, &replay); + if (krberr) { + result_string = strdup("Failed to encrypt reply message"); + syslog(LOG_ERR, "%s: %s", 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 = strdup("Failed to set time of day"); + syslog(LOG_ERR, "%s: %s", 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 = strdup("Failed to build error message"); + syslog(LOG_ERR, "%s: %s", result_string, + krb5_get_error_message(context, krberr)); + goto done; + } + } + + replylen = 6 + krep.length + kenc.length; + reply = malloc(replylen); + if (!reply) { + syslog(LOG_ERR, "Out of memory!"); + 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: + free(result_string); + 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 (context) krb5_free_context(context); +} + +pid_t handle_conn(struct socklist *sd) +{ + int mfd, tcp; + pid_t pid; + char addrto6[INET6_ADDRSTRLEN+1]; + char address[INET6_ADDRSTRLEN+1]; + uint8_t request[1500]; + ssize_t reqlen; + uint8_t *reply; + ssize_t replen; + struct sockaddr_storage from; + socklen_t fromlen; + ssize_t sendret; + int ret; + + fromlen = sizeof(from); + mfd = 0; + tcp = 0; + reqlen = 0; + + /* receive request */ + if (sd->socktype == SOCK_STREAM) { + tcp = 1; + mfd = accept(sd->fd, (struct sockaddr *)&from, &fromlen); + if (mfd == -1) { + syslog(LOG_ERR, "Accept failed with error (%d) %s", + errno, strerror(errno)); + return -1; + } + } else { + /* read first to empty the buffer on udp connections */ + reqlen = recvfrom(sd->fd, request, sizeof(request), 0, + (struct sockaddr *)&from, &fromlen); + if (reqlen <= 0) { + syslog(LOG_ERR, "Error receiving request (%d) %s", + errno, strerror(errno)); + return -1; + } + + } + + ret = getnameinfo((struct sockaddr *)&from, fromlen, + addrto6, INET6_ADDRSTRLEN+1, + NULL, 0, NI_NUMERICHOST); + if (ret) { + syslog(LOG_ERR, "Error retrieving host address\n"); + return -1; + } + + if (debug > 0) { + syslog(LOG_ERR, "Connection from %s", addrto6); + } + + if (strchr(addrto6, ':') == NULL) { + char *prefix6 = "::ffff:"; + /* this is an IPv4 formatted addr + * convert to IPv6 mapped addr */ + memcpy(address, prefix6, 7); + memcpy(&address[7], addrto6, INET6_ADDRSTRLEN-7); + } else { + /* regular IPv6 address, copy as is */ + memcpy(address, addrto6, INET6_ADDRSTRLEN); + } + /* make sure we have termination */ + address[INET6_ADDRSTRLEN] = '\0'; + + /* Check blacklist for requests from 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) { + syslog(LOG_ERR, "[%s] blacklisted", address); + } + if (tcp) close(mfd); + return 0; + } + + /* now read data if it was a TCP connection */ + if (tcp) { + reqlen = recvfrom(mfd, request, sizeof(request), 0, + (struct sockaddr *)&from, &fromlen); + if (reqlen <= 0) { + syslog(LOG_ERR, "Error receiving request (%d) %s", + errno, strerror(errno)); + close(mfd); + return -1; + } + } +#if 1 + /* handle kerberos and ldap operations in childrens */ + pid = fork(); + if (pid == -1) { + syslog(LOG_ERR, "Fork failed with error (%d) %s", + errno, strerror(errno)); + if (tcp) close(mfd); + return 0; + } + if (pid != 0) { /* parent */ + if (tcp) close(mfd); + add_blacklist(pid, address); + return pid; + } +#endif + + /* children */ + if (debug > 0) syslog(LOG_ERR, "Servicing %s", address); + + /* TCP packets prepend the lenght as a 32bit network order field, + * this information seem to be just redundant, so let's simply + * skip it */ + if (tcp) { + handle_krb_packets(request+4, reqlen-4, sd, &from, &reply, &replen); + } else { + handle_krb_packets(request, reqlen, sd, &from, &reply, &replen); + } + + if (replen) { /* we have something to reply */ + if (tcp) { + sendret = sendto(mfd, reply, replen, 0, NULL, 0); + } else { + sendret = sendto(sd->fd, reply, replen, 0, (struct sockaddr *)&from, fromlen); + } + if (sendret == -1) { + syslog(LOG_ERR, "Error sending reply (%d)", errno); + } + } + if (tcp) close(mfd); + exit(0); +} + +static int create_socket(struct addrinfo *ai, struct socklist **_sds, + struct pollfd **_pfds, int *_nfds) +{ + struct socklist *csd, *tsd; + struct pollfd *pfds; + int nfds; + int ret; + int tru = 1; + + pfds = *_pfds; + nfds = *_nfds; + + csd = calloc(1, sizeof(struct socklist)); + if (csd == NULL) { + syslog(LOG_ERR, "Out of memory, can't create socklist\n"); + return 1; + } + csd->socktype = ai->ai_socktype; + csd->dest_addr_len = ai->ai_addrlen; + memcpy(&csd->dest_addr, ai->ai_addr, ai->ai_addrlen); + + csd->fd = socket(csd->dest_addr.ss_family, csd->socktype, 0); + if (csd->fd == -1) { + syslog(LOG_ERR, "Unable to create socket (%s)", + strerror(errno)); + goto errout; + } + ret = setsockopt(csd->fd, SOL_SOCKET, SO_REUSEADDR, + (void *)&tru, sizeof(tru)); + + ret = bind(csd->fd, (struct sockaddr *)&csd->dest_addr, csd->dest_addr_len); + if (ret) { + if (errno != EADDRINUSE) { + syslog(LOG_ERR, "Unable to bind to socket"); + close(csd->fd); + goto errout; + } + /* if EADDRINUSE it means we are on a machine + * with a dual ipv4/ipv6 stack that does not + * allow to bind on both at the same time as the + * ipv6 bind already allows connections on ipv4 + * Just ignore */ + close(csd->fd); + free(csd); + return 0; + } + + if (csd->socktype == SOCK_STREAM) { + ret = listen(csd->fd, SOMAXCONN); + if (ret) { + syslog(LOG_ERR, "Unable to listen to TCP socket (%s)", + strerror(errno)); + close(csd->fd); + goto errout; + } + } + + pfds = realloc(pfds, sizeof(struct pollfd) * (nfds +1)); + if (pfds == NULL) { + syslog(LOG_ERR, "Out of memory, can't alloc pollfd array\n"); + close(csd->fd); + goto errout; + } + pfds[nfds].events = POLLIN; + pfds[nfds].fd = csd->fd; + nfds++; + + if (*_sds) { + for (tsd = *_sds; tsd->next; tsd = tsd->next) /* skip */ ; + tsd->next = csd; + } else { + *_sds = csd; + } + + *_pfds = pfds; + *_nfds = nfds; + + return 0; + +errout: + free(csd); + return 1; +} + +int main(int argc, char *argv[]) +{ + pid_t pid; + struct ifaddrs *ifa, *tifa; + struct addrinfo *ai, *tai; + struct addrinfo hints; + char host[NI_MAXHOST]; + struct socklist *sds, *csd; + struct pollfd *pfds; + int nfds; + int ret; + char *env; + + /* log to syslog */ + openlog("kpasswd", LOG_PID, LOG_DAEMON); + + /* do not keep any fs busy */ + ret = chdir("/"); + if (ret == -1) { + syslog(LOG_ERR, "Unable to change dir to '/'"); + exit(-1); + } + + /* daemonize */ + pid = fork(); + if (pid == -1) { + syslog(LOG_ERR, "Error fork() failed!"); + exit(-1); + } + if (pid != 0) { /* parent */ + exit(0); + } + + /* new session */ + setsid(); + + /* close std* descriptors */ + close(0); + close(1); + close(2); + + /* fork again to make sure we completely detach from parent process */ + pid = fork(); + if (pid == -1) { + syslog(LOG_ERR, "Error fork() failed!"); + exit(-1); + } + if (pid != 0) { /* parent */ + exit(0); + } + + /* source env vars */ + env = getenv("KRB5_KTNAME"); + if (!env) { + env = DEFAULT_KEYTAB; + } + keytab_name = strdup(env); + if (!keytab_name) { + syslog(LOG_ERR, "Out of memory!"); + } + + env = getenv("IPA_KPASSWD_DEBUG"); + if (env) { + debug = strtol(env, NULL, 0); + } + + ret = getifaddrs(&ifa); + if (ret) { + syslog(LOG_ERR, "getifaddrs failed: %s", gai_strerror(ret)); + exit(1); + } + + /* Write out the pid file after the sigterm handler */ + const char *pid_file = "/var/run/ipa_kpasswd.pid"; + FILE *f = fopen(pid_file, "w"); + int fail = 1; + if (f) { + int n_bytes = fprintf(f, "%ld\n", (long) getpid()); + if (fclose(f) == 0 && 0 < n_bytes) + fail = 0; + } + if (fail) { + syslog(LOG_ERR, "Couldn't create pid file %s: %s", + pid_file, strerror(errno)); + exit(1); + } + + nfds = 0; + pfds = NULL; + sds = NULL; + + for (tifa = ifa; tifa; tifa = tifa->ifa_next) { + + if (NULL == tifa->ifa_addr) + /* uhmm no address ?? skip it */ + continue; + + if (tifa->ifa_addr->sa_family != AF_INET && + tifa->ifa_addr->sa_family != AF_INET6) { + /* not interesting for us */ + continue; + } + + ret = getnameinfo(tifa->ifa_addr, sizeof(struct sockaddr_storage), + host, sizeof(host), NULL, 0, NI_NUMERICHOST); + if (ret) { + syslog(LOG_ERR, "Error converting address (%s)", + gai_strerror(ret)); + continue; + } else { + syslog(LOG_INFO, "Setting up socket for [%s]", host); + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_UNSPEC; + + /* this should return 2 entries, one for UDP and one for TCP */ + ret = getaddrinfo(host, "kpasswd", &hints, &ai); + if (ret) { + syslog(LOG_ERR, "Error getting address info (%s) for [%s]", + gai_strerror(ret), host); + continue; + } + + for (tai = ai; tai; tai = tai->ai_next) { + char *socktype = (tai->ai_socktype==SOCK_STREAM)?"TCP":"UDP"; + ret = create_socket(tai, &sds, &pfds, &nfds); + if (ret) { + syslog(LOG_ERR, + "Failed to set up %s socket for [%s]", + socktype, host); + } + } + } + + if (nfds == 0) { + syslog(LOG_ERR, "Failed to setup any socket. Aborting"); + exit(1); + } + + /* now that sockets are set up, enter the poll loop */ + + while (1) { + int cstatus, cid, i; + + ret = poll(pfds, nfds, 3000); + + switch(ret) { + case 0: + break; + case -1: + if (errno != EINTR) { + syslog(LOG_ERR, + "Unexpected error in poll (%d) %s", + errno, strerror(errno)); + exit(5); + } + break; + default: + for (i = 0; i < nfds; i++) { + if (pfds[i].revents & POLLIN) { + for (csd = sds; csd; csd = csd->next) { + if (csd->fd == pfds[i].fd) { + handle_conn(csd); + } + } + } + } + } + + /* check for children exiting */ + cid = waitpid(-1, &cstatus, WNOHANG); + if (cid != -1 && cid != 0) { + if (debug > 0) + syslog(LOG_ERR, "pid %d completed operations!\n", cid); + remove_blacklist(cid); + } + } +} diff --git a/daemons/ipa-kpasswd/ipa_kpasswd.init b/daemons/ipa-kpasswd/ipa_kpasswd.init new file mode 100644 index 000000000..d7244bed6 --- /dev/null +++ b/daemons/ipa-kpasswd/ipa_kpasswd.init @@ -0,0 +1,83 @@ +#!/bin/sh +# +# ipa_kpasswd This starts and stops ipa_kpasswd +# +# chkconfig: - 36 64 +# description: ipa_kpasswd IPA Kpasswd daemon +# processname: /usr/sbin/ipa_kpasswd +# configdir: /etc/sysconfig/ipa-kpasswd +# + +# Source function library. +if [ -f /etc/rc.d/init.d/functions ] ; then +. /etc/rc.d/init.d/functions +fi +# Source networking configuration. +if [ -f /etc/sysconfig/network ] ; then +. /etc/sysconfig/network +fi + +# Check that networking is up. +if [ "${NETWORKING}" = "no" ] +then + echo "Networking is down" + exit 0 +fi + +# Source networking configuration. +if [ -f /etc/sysconfig/ipa-kpasswd ] ; then +. /etc/sysconfig/ipa-kpasswd +fi + +NAME="ipa_kpasswd" +PROG="/usr/sbin/ipa_kpasswd" + +start() { + echo -n $"Starting $NAME: " + daemon $NAME + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/ipa_kpasswd || \ + RETVAL=1 + return $RETVAL +} + +stop() { + echo -n $"Shutting down $NAME: " + killproc $NAME + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/ipa_kpasswd + return $RETVAL +} + +restart() { + stop + start +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status $PROG + ;; + restart) + restart + ;; + condrestart) + [ -f /var/lock/subsys/ipa_kpasswd ] && restart || : + ;; + reload) + exit 3 + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart}" + exit 2 +esac + +exit $? |