summaryrefslogtreecommitdiffstats
path: root/daemons/ipa-kpasswd
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2009-01-29 16:26:07 -0500
committerRob Crittenden <rcritten@redhat.com>2009-02-03 15:27:14 -0500
commite30cd6ba42c256d2016db45146d616f329455e86 (patch)
treed4c5291095c80c92bc4803fe7f20fc2838124ffa /daemons/ipa-kpasswd
parentc4ed025001895bfc65c613cabbbfcb27c19cc29f (diff)
downloadfreeipa-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.am58
-rw-r--r--daemons/ipa-kpasswd/README2
-rw-r--r--daemons/ipa-kpasswd/ipa_kpasswd.c1388
-rw-r--r--daemons/ipa-kpasswd/ipa_kpasswd.init83
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 $?