summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--server/examples/sssdproxylocal6
-rw-r--r--server/examples/sssdproxytest8
-rw-r--r--server/providers/data_provider.c1
-rw-r--r--server/providers/data_provider.h47
-rw-r--r--server/providers/dp_auth_util.c (renamed from server/responder/pam/pamsrv_util.c)24
-rw-r--r--server/providers/dp_backend.h1
-rw-r--r--server/responder/pam/pam_LOCAL_domain.c153
-rw-r--r--server/responder/pam/pam_LOCAL_domain.h9
-rw-r--r--server/responder/pam/pamsrv.c3
-rw-r--r--server/responder/pam/pamsrv.h53
-rw-r--r--server/responder/pam/pamsrv_cache.c275
-rw-r--r--server/responder/pam/pamsrv_cmd.c123
-rw-r--r--server/responder/pam/pamsrv_dp.c63
-rw-r--r--server/server.mk19
14 files changed, 576 insertions, 209 deletions
diff --git a/server/examples/sssdproxylocal b/server/examples/sssdproxylocal
index 1bc47f89..063dbff3 100644
--- a/server/examples/sssdproxylocal
+++ b/server/examples/sssdproxylocal
@@ -1,9 +1,5 @@
#%PAM-1.0
-auth sufficient pam_unix.so
-auth requisite pam_succeed_if.so uid >= 500 quiet
-auth required pam_deny.so
+auth required pam_unix.so
account required pam_unix.so
-account sufficient pam_succeed_if.so uid < 500 quiet
-account required pam_permit.so
diff --git a/server/examples/sssdproxytest b/server/examples/sssdproxytest
index 9c5cb4ad..14217969 100644
--- a/server/examples/sssdproxytest
+++ b/server/examples/sssdproxytest
@@ -1,9 +1,5 @@
#%PAM-1.0
-auth sufficient pam_ldap.so debug
-auth requisite pam_succeed_if.so uid >= 1000 quiet
-auth required pam_deny.so
+auth irequired pam_ldap.so
-account required pam_ldap.so debug
-account sufficient pam_succeed_if.so uid < 1000 quiet
-account required pam_permit.so
+account required pam_ldap.so
diff --git a/server/providers/data_provider.c b/server/providers/data_provider.c
index 4614250c..e8f190ea 100644
--- a/server/providers/data_provider.c
+++ b/server/providers/data_provider.c
@@ -41,7 +41,6 @@
#include "dp_interfaces.h"
#include "monitor/monitor_sbus.h"
#include "monitor/monitor_interfaces.h"
-#include "responder/pam/pamsrv.h"
#define DP_CONF_ENTRY "config/services/dp"
diff --git a/server/providers/data_provider.h b/server/providers/data_provider.h
index 4b68a0bd..2c828fab 100644
--- a/server/providers/data_provider.h
+++ b/server/providers/data_provider.h
@@ -34,6 +34,7 @@
#include "sbus/sssd_dbus.h"
#include "sbus/sbus_client.h"
#include "providers/dp_interfaces.h"
+#include "../sss_client/sss_cli.h"
#define DATA_PROVIDER_VERSION 0x0001
#define DATA_PROVIDER_SERVICE_NAME "dp"
@@ -80,4 +81,50 @@
#define BE_REQ_GROUP 2
#define BE_REQ_INITGROUPS 3
+/* AUTH related common data and functions */
+
+#define DEBUG_PAM_DATA(level, pd) do { \
+ if (level <= debug_level) pam_print_data(level, pd); \
+} while(0);
+
+
+struct response_data {
+ int32_t type;
+ int32_t len;
+ uint8_t *data;
+ struct response_data *next;
+};
+
+struct pam_data {
+ int cmd;
+ uint32_t authtok_type;
+ uint32_t authtok_size;
+ uint32_t newauthtok_type;
+ uint32_t newauthtok_size;
+ char *domain;
+ char *user;
+ char *service;
+ char *tty;
+ char *ruser;
+ char *rhost;
+ uint8_t *authtok;
+ uint8_t *newauthtok;
+
+ int pam_status;
+ int response_delay;
+ struct response_data *resp_list;
+
+ bool offline_auth;
+};
+
+void pam_print_data(int l, struct pam_data *pd);
+
+int pam_add_response(struct pam_data *pd, enum response_type type,
+ int len, const uint8_t *data);
+
+bool dp_pack_pam_request(DBusMessage *msg, struct pam_data *pd);
+bool dp_unpack_pam_request(DBusMessage *msg, struct pam_data *pd, DBusError *dbus_error);
+bool dp_pack_pam_response(DBusMessage *msg, struct pam_data *pd);
+bool dp_unpack_pam_response(DBusMessage *msg, struct pam_data *pd, DBusError *dbus_error);
+
#endif /* __DATA_PROVIDER_ */
diff --git a/server/responder/pam/pamsrv_util.c b/server/providers/dp_auth_util.c
index ab9b733e..99e57e2e 100644
--- a/server/responder/pam/pamsrv_util.c
+++ b/server/providers/dp_auth_util.c
@@ -1,5 +1,25 @@
-#include "util/util.h"
-#include "responder/pam/pamsrv.h"
+/*
+ SSSD
+
+ Data Provider, auth utils
+
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
+
+ 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; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "data_provider.h"
void pam_print_data(int l, struct pam_data *pd)
{
diff --git a/server/providers/dp_backend.h b/server/providers/dp_backend.h
index da71e753..27f79eb7 100644
--- a/server/providers/dp_backend.h
+++ b/server/providers/dp_backend.h
@@ -24,7 +24,6 @@
#include "providers/data_provider.h"
#include "db/sysdb.h"
-#include "responder/pam/pamsrv.h"
struct be_ctx;
struct be_id_ops;
diff --git a/server/responder/pam/pam_LOCAL_domain.c b/server/responder/pam/pam_LOCAL_domain.c
index 7ee84eb6..df2803e5 100644
--- a/server/responder/pam/pam_LOCAL_domain.c
+++ b/server/responder/pam/pam_LOCAL_domain.c
@@ -1,11 +1,32 @@
+/*
+ SSSD
+
+ PAM e credentials
+
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
+
+ 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; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
#include <time.h>
#include <security/pam_modules.h>
-#include <ldb.h>
#include "util/util.h"
-#include "responder/pam/pamsrv.h"
#include "db/sysdb.h"
#include "util/nss_sha512crypt.h"
+#include "providers/data_provider.h"
+#include "responder/pam/pamsrv.h"
#define NULL_CHECK_OR_JUMP(var, msg, ret, err, label) do { \
@@ -26,15 +47,14 @@
struct LOCAL_request {
- struct cli_ctx *cctx;
- struct pam_data *pd;
- pam_dp_callback_t callback;
struct sysdb_ctx *dbctx;
- struct sss_domain_info *domain_info;
struct sysdb_attrs *mod_attrs;
struct sysdb_req *sysdb_req;
+
struct ldb_result *res;
int error;
+
+ struct pam_auth_req *preq;
};
static int authtok2str(const void *mem_ctx, uint8_t *src, const int src_size, char **dest)
@@ -45,7 +65,7 @@ static int authtok2str(const void *mem_ctx, uint8_t *src, const int src_size, ch
}
*dest = talloc_size(mem_ctx, src_size + 1);
- if (dest == NULL) {
+ if (*dest == NULL) {
return ENOMEM;
}
memcpy(*dest, src, src_size);
@@ -56,12 +76,14 @@ static int authtok2str(const void *mem_ctx, uint8_t *src, const int src_size, ch
static void prepare_reply(struct LOCAL_request *lreq)
{
- if (lreq->error != EOK && lreq->pd->pam_status == PAM_SUCCESS)
- lreq->pd->pam_status = PAM_SYSTEM_ERR;
+ struct pam_data *pd;
+
+ pd = lreq->preq->pd;
- lreq->callback(lreq->pd);
+ if (lreq->error != EOK && pd->pam_status == PAM_SUCCESS)
+ pd->pam_status = PAM_SYSTEM_ERR;
- talloc_free(lreq);
+ lreq->preq->callback(lreq->preq);
}
static void set_user_attr_callback(void *pvt, int ldb_status, struct ldb_result *res)
@@ -93,8 +115,8 @@ static void set_user_attr_req(struct sysdb_req *req, void *pvt)
lreq->sysdb_req = req;
- ret = sysdb_set_user_attr(req, lreq->dbctx, lreq->domain_info,
- lreq->pd->user, lreq->mod_attrs,
+ ret = sysdb_set_user_attr(req, lreq->dbctx, lreq->preq->domain,
+ lreq->preq->pd->user, lreq->mod_attrs,
set_user_attr_callback, lreq);
if (ret != EOK)
sysdb_transaction_done(lreq->sysdb_req, ret);
@@ -139,10 +161,12 @@ static void do_failed_login(struct LOCAL_request *lreq)
{
int ret;
int failedLoginAttempts;
+ struct pam_data *pd;
- lreq->pd->pam_status = PAM_AUTH_ERR;
+ pd = lreq->preq->pd;
+ pd->pam_status = PAM_AUTH_ERR;
/* TODO: maybe add more inteligent delay calculation */
- lreq->pd->response_delay = 3;
+ pd->response_delay = 3;
lreq->mod_attrs = sysdb_new_attrs(lreq);
NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"),
@@ -175,14 +199,17 @@ done:
static void do_pam_acct_mgmt(struct LOCAL_request *lreq)
{
- const char *disabled=NULL;
+ const char *disabled;
+ struct pam_data *pd;
+
+ pd = lreq->preq->pd;
disabled = ldb_msg_find_attr_as_string(lreq->res->msgs[0],
SYSDB_DISABLED, NULL);
- if (disabled != NULL &&
- strncasecmp(disabled, "false",5)!=0 &&
- strncasecmp(disabled, "no",2)!=0 ) {
- lreq->pd->pam_status = PAM_PERM_DENIED;
+ if ((disabled != NULL) &&
+ (strncasecmp(disabled, "false",5) != 0) &&
+ (strncasecmp(disabled, "no",2) != 0) ) {
+ pd->pam_status = PAM_PERM_DENIED;
}
prepare_reply(lreq);
@@ -194,12 +221,14 @@ static void do_pam_chauthtok(struct LOCAL_request *lreq)
char *newauthtok;
char *salt;
char *new_hash;
+ struct pam_data *pd;
+
+ pd = lreq->preq->pd;
- ret = authtok2str(lreq, lreq->pd->newauthtok, lreq->pd->newauthtok_size,
- &newauthtok);
+ ret = authtok2str(lreq, pd->newauthtok, pd->newauthtok_size, &newauthtok);
NEQ_CHECK_OR_JUMP(ret, EOK, ("authtok2str failed.\n"),
lreq->error, ret, done);
- memset(lreq->pd->newauthtok, 0, lreq->pd->newauthtok_size);
+ memset(pd->newauthtok, 0, pd->newauthtok_size);
salt = gen_salt();
NULL_CHECK_OR_JUMP(salt, ("Salt generation failed.\n"),
@@ -210,7 +239,7 @@ static void do_pam_chauthtok(struct LOCAL_request *lreq)
NULL_CHECK_OR_JUMP(new_hash, ("Hash generation failed.\n"),
lreq->error, EFAULT, done);
DEBUG(4, ("New hash [%s]\n", new_hash));
- memset(newauthtok, 0, lreq->pd->newauthtok_size);
+ memset(newauthtok, 0, pd->newauthtok_size);
lreq->mod_attrs = sysdb_new_attrs(lreq);
NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"),
@@ -234,8 +263,8 @@ done:
prepare_reply(lreq);
}
-static void pam_handler_callback(void *pvt, int ldb_status,
- struct ldb_result *res)
+static void local_handler_callback(void *pvt, int ldb_status,
+ struct ldb_result *res)
{
struct LOCAL_request *lreq;
const char *username = NULL;
@@ -243,9 +272,11 @@ static void pam_handler_callback(void *pvt, int ldb_status,
char *newauthtok = NULL;
char *new_hash = NULL;
char *authtok = NULL;
+ struct pam_data *pd;
int ret;
lreq = talloc_get_type(pvt, struct LOCAL_request);
+ pd = lreq->preq->pd;
DEBUG(4, ("pam_handler_callback called with ldb_status [%d].\n",
ldb_status));
@@ -256,8 +287,8 @@ static void pam_handler_callback(void *pvt, int ldb_status,
if (res->count < 1) {
DEBUG(4, ("No user found with filter ["SYSDB_PWNAM_FILTER"]\n",
- lreq->pd->user));
- lreq->pd->pam_status = PAM_USER_UNKNOWN;
+ pd->user));
+ pd->pam_status = PAM_USER_UNKNOWN;
goto done;
} else if (res->count > 1) {
DEBUG(4, ("More than one object found with filter ["SYSDB_PWNAM_FILTER"]\n"));
@@ -266,27 +297,26 @@ static void pam_handler_callback(void *pvt, int ldb_status,
}
username = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL);
- if (strcmp(username, lreq->pd->user) != 0) {
- DEBUG(1, ("Expected username [%s] get [%s].\n", lreq->pd->user, username));
+ if (strcmp(username, pd->user) != 0) {
+ DEBUG(1, ("Expected username [%s] get [%s].\n", pd->user, username));
lreq->error = EINVAL;
goto done;
}
lreq->res = res;
- switch (lreq->pd->cmd) {
+ switch (pd->cmd) {
case SSS_PAM_AUTHENTICATE:
case SSS_PAM_CHAUTHTOK:
- if (lreq->pd->cmd == SSS_PAM_CHAUTHTOK && lreq->cctx->priv == 1) {
+ if (pd->cmd == SSS_PAM_CHAUTHTOK && lreq->preq->cctx->priv == 1) {
/* TODO: maybe this is a candiate for an explicit audit message. */
DEBUG(4, ("allowing root to reset a password.\n"));
break;
}
- ret = authtok2str(lreq, lreq->pd->authtok,
- lreq->pd->authtok_size, &authtok);
+ ret = authtok2str(lreq, pd->authtok, pd->authtok_size, &authtok);
NEQ_CHECK_OR_JUMP(ret, EOK, ("authtok2str failed.\n"),
lreq->error, ret, done);
- memset(lreq->pd->authtok, 0, lreq->pd->authtok_size);
+ memset(pd->authtok, 0, pd->authtok_size);
password = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_PWD, NULL);
NULL_CHECK_OR_JUMP(password, ("No password stored.\n"),
@@ -294,7 +324,7 @@ static void pam_handler_callback(void *pvt, int ldb_status,
DEBUG(4, ("user: [%s], password hash: [%s]\n", username, password));
new_hash = nss_sha512_crypt(authtok, password);
- memset(authtok, 0, lreq->pd->authtok_size);
+ memset(authtok, 0, pd->authtok_size);
NULL_CHECK_OR_JUMP(new_hash, ("nss_sha512_crypt failed.\n"),
lreq->error, EFAULT, done);
@@ -309,7 +339,7 @@ static void pam_handler_callback(void *pvt, int ldb_status,
break;
}
- switch (lreq->pd->cmd) {
+ switch (pd->cmd) {
case SSS_PAM_AUTHENTICATE:
do_successful_login(lreq);
return;
@@ -334,23 +364,22 @@ static void pam_handler_callback(void *pvt, int ldb_status,
}
done:
- if (lreq->pd->authtok != NULL)
- memset(lreq->pd->authtok, 0, lreq->pd->authtok_size);
+ if (pd->authtok != NULL)
+ memset(pd->authtok, 0, pd->authtok_size);
if (authtok != NULL)
- memset(authtok, 0, lreq->pd->authtok_size);
- if (lreq->pd->newauthtok != NULL)
- memset(lreq->pd->newauthtok, 0, lreq->pd->newauthtok_size);
+ memset(authtok, 0, pd->authtok_size);
+ if (pd->newauthtok != NULL)
+ memset(pd->newauthtok, 0, pd->newauthtok_size);
if (newauthtok != NULL)
- memset(newauthtok, 0, lreq->pd->newauthtok_size);
+ memset(newauthtok, 0, pd->newauthtok_size);
prepare_reply(lreq);
}
-int LOCAL_pam_handler(struct cli_ctx *cctx, pam_dp_callback_t callback,
- struct sss_domain_info *dom, struct pam_data *pd)
+int LOCAL_pam_handler(struct pam_auth_req *preq)
{
int ret;
- struct LOCAL_request *lreq=NULL;
+ struct LOCAL_request *lreq;
static const char *attrs[] = {SYSDB_NAME,
SYSDB_PWD,
@@ -364,37 +393,27 @@ int LOCAL_pam_handler(struct cli_ctx *cctx, pam_dp_callback_t callback,
"lastFailedLogin",
NULL};
- lreq = talloc_zero(cctx, struct LOCAL_request);
+ DEBUG(4, ("LOCAL pam handler.\n"));
+
+ lreq = talloc_zero(preq, struct LOCAL_request);
if (!lreq) {
return ENOMEM;
}
- lreq->cctx = cctx;
- lreq->pd = pd;
- lreq->callback = callback;
- lreq->pd->pam_status = PAM_SUCCESS;
- lreq->error = EOK;
-
-
- DEBUG(4, ("LOCAL pam handler.\n"));
- lreq->domain_info = dom;
- NULL_CHECK_OR_JUMP(lreq->domain_info, ("Domain info not found.\n"),
- ret, EINVAL, done);
+ lreq->dbctx = preq->cctx->rctx->sysdb;
+ lreq->preq = preq;
- lreq->dbctx = lreq->cctx->rctx->sysdb;
+ preq->pd->pam_status = PAM_SUCCESS;
ret = sysdb_get_user_attr(lreq, lreq->dbctx,
- lreq->domain_info, lreq->pd->user, attrs,
- pam_handler_callback, lreq);
+ preq->domain, preq->pd->user, attrs,
+ local_handler_callback, preq);
- if(ret != EOK) {
+ if (ret != EOK) {
DEBUG(1, ("sysdb_get_user_attr failed.\n"));
- goto done;
+ talloc_free(lreq);
+ return ret;
}
return EOK;
-
-done:
- talloc_free(lreq);
- return ret;
}
diff --git a/server/responder/pam/pam_LOCAL_domain.h b/server/responder/pam/pam_LOCAL_domain.h
deleted file mode 100644
index bc2064db..00000000
--- a/server/responder/pam/pam_LOCAL_domain.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#ifndef __PAM_LOCAL_DOMAIN_H__
-#define __PAM_LOCAL_DOMAIN_H__
-
-#include "responder/pam/pamsrv.h"
-
-int LOCAL_pam_handler(struct cli_ctx *cctx, pam_dp_callback_t callback,
- struct sss_domain_info *dom, struct pam_data *pd);
-
-#endif /* __PAM_LOCAL_DOMAIN_H__ */
diff --git a/server/responder/pam/pamsrv.c b/server/responder/pam/pamsrv.c
index 2f74a4ac..1adbb14c 100644
--- a/server/responder/pam/pamsrv.c
+++ b/server/responder/pam/pamsrv.c
@@ -3,7 +3,8 @@
PAM Responder
- Copyright (C) Simo Sorce <ssorce@redhat.com> 2008
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
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
diff --git a/server/responder/pam/pamsrv.h b/server/responder/pam/pamsrv.h
index 077b495d..c751ceed 100644
--- a/server/responder/pam/pamsrv.h
+++ b/server/responder/pam/pamsrv.h
@@ -1,59 +1,34 @@
#ifndef __PAMSRV_H__
#define __PAMSRV_H__
-
+#include <security/pam_appl.h>
#include "util/util.h"
#include "sbus/sssd_dbus.h"
#include "responder/common/responder.h"
#define PAM_DP_TIMEOUT 5000
-#define DEBUG_PAM_DATA(level, pd) do { \
- if (level <= debug_level) pam_print_data(level, pd); \
-} while(0);
+struct pam_auth_req;
-struct response_data {
- int32_t type;
- int32_t len;
- uint8_t *data;
- struct response_data *next;
-};
+typedef void (pam_dp_callback_t)(struct pam_auth_req *preq);
-struct pam_data {
- int cmd;
- uint32_t authtok_type;
- uint32_t authtok_size;
- uint32_t newauthtok_type;
- uint32_t newauthtok_size;
- char *domain;
- char *user;
- char *service;
- char *tty;
- char *ruser;
- char *rhost;
- uint8_t *authtok;
- uint8_t *newauthtok;
-
- int pam_status;
- int response_delay;
- struct response_data *resp_list;
+struct pam_auth_req {
struct cli_ctx *cctx;
-};
+ struct sss_domain_info *domain;
-int pam_add_response(struct pam_data *pd, enum response_type type,
- int len, const uint8_t *data);
-void pam_print_data(int l, struct pam_data *pd);
+ struct pam_data *pd;
-typedef void (*pam_dp_callback_t)(struct pam_data *pd);
+ pam_dp_callback_t *callback;
+};
struct sbus_method *register_pam_dp_methods(void);
struct sss_cmd_table *register_sss_cmds(void);
-int pam_dp_send_req(struct cli_ctx *cctx, pam_dp_callback_t callback,
- int timeout, struct pam_data *pd);
+int pam_dp_send_req(struct pam_auth_req *preq, int timeout);
+
+int pam_cache_credentials(struct pam_auth_req *preq);
+int pam_cache_auth(struct pam_auth_req *preq);
+
+int LOCAL_pam_handler(struct pam_auth_req *preq);
-bool dp_pack_pam_request(DBusMessage *msg, struct pam_data *pd);
-bool dp_unpack_pam_request(DBusMessage *msg, struct pam_data *pd, DBusError *dbus_error);
-bool dp_pack_pam_response(DBusMessage *msg, struct pam_data *pd);
-bool dp_unpack_pam_response(DBusMessage *msg, struct pam_data *pd, DBusError *dbus_error);
#endif /* __PAMSRV_H__ */
diff --git a/server/responder/pam/pamsrv_cache.c b/server/responder/pam/pamsrv_cache.c
new file mode 100644
index 00000000..10f41996
--- /dev/null
+++ b/server/responder/pam/pamsrv_cache.c
@@ -0,0 +1,275 @@
+/*
+ SSSD
+
+ PAM cache credentials
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
+
+ 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; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <security/pam_modules.h>
+#include <time.h>
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "util/nss_sha512crypt.h"
+#include "providers/data_provider.h"
+#include "responder/pam/pamsrv.h"
+
+static int authtok2str(const void *mem_ctx,
+ uint8_t *src, const int src_size,
+ char **_dest)
+{
+ char *dest;
+
+ if ((src == NULL && src_size != 0) ||
+ (src != NULL && *src != '\0' && src_size == 0)) {
+ return EINVAL;
+ }
+
+ dest = talloc_size(mem_ctx, src_size + 1);
+ if (dest == NULL) {
+ return ENOMEM;
+ }
+
+ memcpy(dest, src, src_size);
+ dest[src_size]='\0';
+
+ *_dest = dest;
+ return EOK;
+}
+
+struct set_attrs_ctx {
+ struct pam_auth_req *preq;
+ struct sysdb_attrs *attrs;
+ struct sysdb_req *sysreq;
+};
+
+static void pc_set_user_attr_callback(void *pvt,
+ int ldb_status,
+ struct ldb_result *res)
+{
+ struct set_attrs_ctx *ctx;
+ int error;
+
+ ctx = talloc_get_type(pvt, struct set_attrs_ctx);
+ error = sysdb_error_to_errno(ldb_status);
+
+ sysdb_transaction_done(ctx->sysreq, error);
+
+ if (ldb_status != LDB_SUCCESS) {
+ DEBUG(2, ("Failed to cache credentials for user [%s] (%d)!\n",
+ ctx->preq->pd->user, error, strerror(error)));
+ }
+
+ ctx->preq->callback(ctx->preq);
+}
+
+static void pc_set_user_attr_req(struct sysdb_req *req, void *pvt)
+{
+ struct set_attrs_ctx *ctx;
+ int ret;
+
+ DEBUG(4, ("entering pc_set_user_attr_req\n"));
+
+ ctx = talloc_get_type(pvt, struct set_attrs_ctx);
+
+ ctx->sysreq = req;
+
+ ret = sysdb_set_user_attr(req, ctx->preq->cctx->rctx->sysdb,
+ ctx->preq->domain,
+ ctx->preq->pd->user,
+ ctx->attrs,
+ pc_set_user_attr_callback, ctx);
+ if (ret != EOK) {
+ sysdb_transaction_done(ctx->sysreq, ret);
+ }
+
+ if (ret != EOK) {
+ DEBUG(2, ("Failed to cache credentials for user [%s] (%d)!\n",
+ ctx->preq->pd->user, ret, strerror(ret)));
+ ctx->preq->callback(ctx->preq);
+ }
+}
+
+int pam_cache_credentials(struct pam_auth_req *preq)
+{
+ struct set_attrs_ctx *ctx;
+ struct pam_data *pd;
+ char *password = NULL;
+ char *comphash = NULL;
+ char *salt;
+ int i, ret;
+
+ pd = preq->pd;
+
+ ret = authtok2str(preq, pd->authtok, pd->authtok_size, &password);
+ if (ret) {
+ DEBUG(4, ("Invalid auth token.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ salt = gen_salt();
+ if (!salt) {
+ DEBUG(4, ("Failed to generate random salt.\n"));
+ ret = EFAULT;
+ goto done;
+ }
+
+ comphash = nss_sha512_crypt(password, salt);
+ if (!comphash) {
+ DEBUG(4, ("Failed to create password hash.\n"));
+ ret = EFAULT;
+ goto done;
+ }
+
+ ctx = talloc_zero(preq, struct set_attrs_ctx);
+ if (!ctx) {
+ ret = ENOMEM;
+ goto done;
+ }
+ ctx->preq = preq;
+
+ ctx->attrs = sysdb_new_attrs(ctx);
+ if (!ctx->attrs) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sysdb_attrs_add_string(ctx->attrs, SYSDB_PWD, comphash);
+ if (ret) goto done;
+
+ /* FIXME: should we use a different attribute for chache passwords ?? */
+ ret = sysdb_attrs_add_long(ctx->attrs, "lastPasswordChange",
+ (long)time(NULL));
+ if (ret) goto done;
+
+ ret = sysdb_transaction(ctx, preq->cctx->rctx->sysdb,
+ pc_set_user_attr_req, ctx);
+
+done:
+ if (password) for (i = 0; password[i]; i++) password[i] = 0;
+ if (ret != EOK) {
+ DEBUG(2, ("Failed to cache credentials for user [%s] (%d)!\n",
+ pd->user, ret, strerror(ret)));
+ }
+ return ret;
+}
+
+static void pam_cache_auth_return(struct pam_auth_req *preq, int error)
+{
+ preq->pd->pam_status = error;
+ preq->callback(preq);
+}
+
+static void pam_cache_auth_callback(void *pvt, int ldb_status,
+ struct ldb_result *res)
+{
+ struct pam_auth_req *preq;
+ struct pam_data *pd;
+ const char *userhash;
+ const char *comphash;
+ char *password = NULL;
+ int i, ret;
+
+ preq = talloc_get_type(pvt, struct pam_auth_req);
+ pd = preq->pd;
+
+ if (ldb_status != LDB_SUCCESS) {
+ DEBUG(4, ("User info retireval failed! (%d [%s])\n",
+ ldb_status, sysdb_error_to_errno(ldb_status)));
+
+ ret = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ if (res->count == 0) {
+ DEBUG(4, ("User [%s@%s] not found.\n",
+ pd->user, preq->domain->name));
+ ret = PAM_USER_UNKNOWN;
+ goto done;
+ }
+
+ if (res->count != 1) {
+ DEBUG(4, ("Too manyt results for user [%s@%s].\n",
+ pd->user, preq->domain->name));
+ ret = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ /* TODO: verify user account (failed logins, disabled, expired ...) */
+
+ ret = authtok2str(preq, pd->authtok, pd->authtok_size, &password);
+ if (ret) {
+ DEBUG(4, ("Invalid auth token.\n"));
+ ret = PAM_AUTH_ERR;
+ goto done;
+ }
+
+ userhash = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_PWD, NULL);
+ if (userhash == NULL || *userhash == '\0') {
+ DEBUG(4, ("Cached credentials not available.\n"));
+ ret = PAM_AUTHINFO_UNAVAIL;
+ goto done;
+ }
+
+ comphash = nss_sha512_crypt(password, userhash);
+ if (!comphash) {
+ DEBUG(4, ("Failed to create password hash.\n"));
+ ret = PAM_SYSTEM_ERR;
+ goto done;
+ }
+
+ if (strcmp(userhash, comphash) == 0) {
+ /* TODO: probable good point for audit logging */
+ DEBUG(4, ("Hashes do match!\n"));
+ ret = PAM_SUCCESS;
+ goto done;
+ }
+
+ DEBUG(4, ("Authentication failed.\n"));
+ ret = PAM_AUTH_ERR;
+
+done:
+ if (password) for (i = 0; password[i]; i++) password[i] = 0;
+ pam_cache_auth_return(preq, ret);
+}
+
+int pam_cache_auth(struct pam_auth_req *preq)
+{
+ int ret;
+
+ static const char *attrs[] = {SYSDB_NAME,
+ SYSDB_PWD,
+ SYSDB_DISABLED,
+ SYSDB_LAST_LOGIN,
+ "lastPasswordChange",
+ "accountExpires",
+ "failedLoginAttempts",
+ "lastFailedLogin",
+ NULL};
+
+ ret = sysdb_get_user_attr(preq, preq->cctx->rctx->sysdb,
+ preq->domain, preq->pd->user, attrs,
+ pam_cache_auth_callback, preq);
+
+ if (ret != EOK) {
+ DEBUG(2, ("sysdb_get_user_attr failed.\n"));
+ }
+
+ return ret;
+}
+
diff --git a/server/responder/pam/pamsrv_cmd.c b/server/responder/pam/pamsrv_cmd.c
index db5f064f..2190b566 100644
--- a/server/responder/pam/pamsrv_cmd.c
+++ b/server/responder/pam/pamsrv_cmd.c
@@ -1,10 +1,32 @@
+/*
+ SSSD
+
+ PAM Responder
+
+ Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
+ Copyright (C) Sumit Bose <sbose@redhat.com> 2009
+
+ 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; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
#include <errno.h>
#include <talloc.h>
#include "util/util.h"
#include "confdb/confdb.h"
#include "responder/common/responder_packet.h"
-#include "responder/pam/pam_LOCAL_domain.h"
+#include "providers/data_provider.h"
#include "responder/pam/pamsrv.h"
static int pam_parse_in_data(struct sss_names_ctx *snctx,
@@ -86,19 +108,20 @@ static int pam_parse_in_data(struct sss_names_ctx *snctx,
return EOK;
}
-static void pam_reply(struct pam_data *pd);
+static void pam_reply(struct pam_auth_req *preq);
static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te,
struct timeval tv, void *pvt)
{
- struct pam_data *pd;
+ struct pam_auth_req *preq;
+
DEBUG(4, ("pam_reply_delay get called.\n"));
- pd = talloc_get_type(pvt, struct pam_data);
+ preq = talloc_get_type(pvt, struct pam_auth_req);
- pam_reply(pd);
+ pam_reply(preq);
}
-static void pam_reply(struct pam_data *pd)
+static void pam_reply(struct pam_auth_req *preq)
{
struct cli_ctx *cctx;
uint8_t *body;
@@ -111,13 +134,48 @@ static void pam_reply(struct pam_data *pd)
int p;
struct timeval tv;
struct tevent_timer *te;
+ struct pam_data *pd;
+
+ pd = preq->pd;
DEBUG(4, ("pam_reply get called.\n"));
+ if ((pd->cmd == SSS_PAM_AUTHENTICATE) &&
+ (preq->domain->cache_credentials == true) &&
+ (pd->offline_auth == false)) {
+
+ if (pd->pam_status == PAM_SUCCESS) {
+ pd->offline_auth = true;
+ preq->callback = pam_reply;
+ ret = pam_cache_credentials(preq);
+ if (ret == EOK) {
+ return;
+ }
+ else {
+ DEBUG(0, ("Failed to cache credentials"));
+ /* this error is not fatal, continue */
+ }
+ }
+
+ if (pd->pam_status == PAM_AUTHINFO_UNAVAIL) {
+ /* do auth with offline credentials */
+ pd->offline_auth = true;
+ preq->callback = pam_reply;
+ ret = pam_cache_auth(preq);
+ if (ret == EOK) {
+ return;
+ }
+ else {
+ DEBUG(1, ("Failed to setup offline auth"));
+ /* this error is not fatal, continue */
+ }
+ }
+ }
+
if (pd->response_delay > 0) {
ret = gettimeofday(&tv, NULL);
if (ret != EOK) {
- DEBUG(0, ("gettimeofday failed [%d][%s].\n",
+ DEBUG(1, ("gettimeofday failed [%d][%s].\n",
errno, strerror(errno)));
err = ret;
goto done;
@@ -126,9 +184,9 @@ static void pam_reply(struct pam_data *pd)
tv.tv_usec = 0;
pd->response_delay = 0;
- te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, pd);
+ te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq);
if (te == NULL) {
- DEBUG(0, ("Failed to add event pam_reply_delay.\n"));
+ DEBUG(1, ("Failed to add event pam_reply_delay.\n"));
err = ENOMEM;
goto done;
}
@@ -136,7 +194,7 @@ static void pam_reply(struct pam_data *pd)
return;
}
- cctx = pd->cctx;
+ cctx = preq->cctx;
ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in),
&cctx->creq->out);
@@ -191,8 +249,7 @@ static void pam_reply(struct pam_data *pd)
}
done:
- talloc_free(pd);
- sss_cmd_done(cctx, NULL);
+ sss_cmd_done(cctx, preq);
}
static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd)
@@ -201,50 +258,60 @@ static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd)
uint8_t *body;
size_t blen;
int ret;
+ struct pam_auth_req *preq;
struct pam_data *pd;
- pd = talloc(cctx, struct pam_data);
- if (pd == NULL) return ENOMEM;
+ preq = talloc_zero(cctx, struct pam_auth_req);
+ if (!preq) {
+ return ENOMEM;
+ }
+ preq->cctx = cctx;
+
+ preq->pd = talloc_zero(preq, struct pam_data);
+ if (!preq->pd) {
+ talloc_free(preq);
+ return ENOMEM;
+ }
+ pd = preq->pd;
sss_packet_get_body(cctx->creq->in, &body, &blen);
if (blen >= sizeof(uint32_t) &&
((uint32_t *)(&body[blen - sizeof(uint32_t)]))[0] != END_OF_PAM_REQUEST) {
DEBUG(1, ("Received data not terminated.\n"));
- talloc_free(pd);
+ talloc_free(preq);
return EINVAL;
}
pd->cmd = pam_cmd;
- pd->cctx = cctx;
- ret=pam_parse_in_data(cctx->rctx->names, pd, body, blen);
- if( ret != 0 ) {
- talloc_free(pd);
+ ret = pam_parse_in_data(cctx->rctx->names, pd, body, blen);
+ if (ret != 0) {
+ talloc_free(preq);
return EINVAL;
}
- pd->response_delay = 0;
- pd->resp_list = NULL;
-
if (pd->domain) {
for (dom = cctx->rctx->domains; dom; dom = dom->next) {
if (strcasecmp(dom->name, pd->domain) == 0) break;
}
if (!dom) {
- talloc_free(pd);
+ talloc_free(preq);
return EINVAL;
}
+ preq->domain = dom;
}
else {
DEBUG(4, ("Domain not provided, using default.\n"));
- dom = cctx->rctx->domains;
- pd->domain = dom->name;
+ preq->domain = cctx->rctx->domains;
+ pd->domain = preq->domain->name;
}
- if (!dom->provider) {
- return LOCAL_pam_handler(cctx, pam_reply, dom, pd);
+ if (!preq->domain->provider) {
+ preq->callback = pam_reply;
+ return LOCAL_pam_handler(preq);
};
- ret = pam_dp_send_req(cctx, pam_reply, PAM_DP_TIMEOUT, pd);
+ preq->callback = pam_reply;
+ ret = pam_dp_send_req(preq, PAM_DP_TIMEOUT);
DEBUG(4, ("pam_dp_send_req returned %d\n", ret));
return ret;
diff --git a/server/responder/pam/pamsrv_dp.c b/server/responder/pam/pamsrv_dp.c
index 3555c20e..f352b270 100644
--- a/server/responder/pam/pamsrv_dp.c
+++ b/server/responder/pam/pamsrv_dp.c
@@ -32,21 +32,15 @@
#include "providers/dp_sbus.h"
#include "responder/pam/pamsrv.h"
-struct pam_reply_ctx {
- struct cli_ctx *cctx;
- struct pam_data *pd;
- pam_dp_callback_t callback;
-};
-
-static void pam_process_dp_reply(DBusPendingCall *pending, void *ptr)
+static void pam_dp_process_reply(DBusPendingCall *pending, void *ptr)
{
DBusError dbus_error;
DBusMessage* msg;
int ret;
int type;
- struct pam_reply_ctx *rctx;
+ struct pam_auth_req *preq;
- rctx = talloc_get_type(ptr, struct pam_reply_ctx);
+ preq = talloc_get_type(ptr, struct pam_auth_req);
dbus_error_init(&dbus_error);
@@ -54,7 +48,7 @@ static void pam_process_dp_reply(DBusPendingCall *pending, void *ptr)
msg = dbus_pending_call_steal_reply(pending);
if (msg == NULL) {
DEBUG(0, ("Severe error. A reply callback was called but no reply was received and no timeout occurred\n"));
- rctx->pd->pam_status = PAM_SYSTEM_ERR;
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
goto done;
}
@@ -62,57 +56,44 @@ static void pam_process_dp_reply(DBusPendingCall *pending, void *ptr)
type = dbus_message_get_type(msg);
switch (type) {
case DBUS_MESSAGE_TYPE_METHOD_RETURN:
- ret = dp_unpack_pam_response(msg, rctx->pd, &dbus_error);
+ ret = dp_unpack_pam_response(msg, preq->pd, &dbus_error);
if (!ret) {
DEBUG(0, ("Failed to parse reply.\n"));
- rctx->pd->pam_status = PAM_SYSTEM_ERR;
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
goto done;
}
- DEBUG(4, ("received: [%d][%s]\n", rctx->pd->pam_status, rctx->pd->domain));
+ DEBUG(4, ("received: [%d][%s]\n", preq->pd->pam_status, preq->pd->domain));
break;
case DBUS_MESSAGE_TYPE_ERROR:
DEBUG(0, ("Reply error.\n"));
- rctx->pd->pam_status = PAM_SYSTEM_ERR;
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
break;
default:
DEBUG(0, ("Default... what now?.\n"));
- rctx->pd->pam_status = PAM_SYSTEM_ERR;
+ preq->pd->pam_status = PAM_SYSTEM_ERR;
}
done:
dbus_pending_call_unref(pending);
dbus_message_unref(msg);
- rctx->callback(rctx->pd);
-
- talloc_free(rctx);
+ preq->callback(preq);
}
-int pam_dp_send_req(struct cli_ctx *cctx,
- pam_dp_callback_t callback,
- int timeout, struct pam_data *pd)
+int pam_dp_send_req(struct pam_auth_req *preq, int timeout)
{
+ struct pam_data *pd = preq->pd;
DBusMessage *msg;
DBusPendingCall *pending_reply;
DBusConnection *conn;
dbus_bool_t ret;
- struct pam_reply_ctx *rctx;
- rctx = talloc(cctx, struct pam_reply_ctx);
- if (rctx == NULL) {
- DEBUG(0,("Out of memory?!\n"));
- return ENOMEM;
- }
- rctx->cctx = cctx;
- rctx->callback = callback;
- rctx->pd = pd;
-
- if (pd->domain==NULL ||
- pd->user==NULL ||
- pd->service==NULL ||
- pd->tty==NULL ||
- pd->ruser==NULL ||
- pd->rhost==NULL ) {
+ if ((pd->domain == NULL) ||
+ (pd->user == NULL) ||
+ (pd->service == NULL) ||
+ (pd->tty == NULL) ||
+ (pd->ruser == NULL) ||
+ (pd->rhost == NULL) ) {
return EINVAL;
}
@@ -120,12 +101,12 @@ int pam_dp_send_req(struct cli_ctx *cctx,
* in some pathological cases it may happen that nss starts up before
* dp connection code is actually able to establish a connection.
*/
- if (!cctx->rctx->dp_ctx) {
+ if (!preq->cctx->rctx->dp_ctx) {
DEBUG(1, ("The Data Provider connection is not available yet!"
" This maybe a bug, it shouldn't happen!\n"));
return EIO;
}
- conn = sbus_get_connection(cctx->rctx->dp_ctx->scon_ctx);
+ conn = sbus_get_connection(preq->cctx->rctx->dp_ctx->scon_ctx);
msg = dbus_message_new_method_call(NULL,
DP_CLI_PATH,
@@ -158,8 +139,8 @@ int pam_dp_send_req(struct cli_ctx *cctx,
return EIO;
}
- dbus_pending_call_set_notify(pending_reply, pam_process_dp_reply, rctx,
- NULL);
+ dbus_pending_call_set_notify(pending_reply,
+ pam_dp_process_reply, preq, NULL);
dbus_message_unref(msg);
return EOK;
diff --git a/server/server.mk b/server/server.mk
index 9785f636..07848939 100644
--- a/server/server.mk
+++ b/server/server.mk
@@ -7,6 +7,7 @@ UTIL_OBJ = \
util/usertools.o \
monitor/monitor_sbus.o \
providers/dp_sbus.o \
+ providers/dp_auth_util.o \
sbus/sssd_dbus_common.o \
sbus/sssd_dbus_connection.o \
sbus/sssd_dbus_server.o \
@@ -30,7 +31,7 @@ DP_OBJ = \
providers/data_provider.o
DP_BE_OBJ = \
- providers/data_provider_be.o \
+ providers/data_provider_be.o
PROXY_BE_OBJ = \
providers/proxy.o
@@ -62,16 +63,16 @@ SYSDB_TEST_OBJ = \
INFP_TEST_OBJ = \
tests/infopipe-tests.o
-CRYPT_OBJ = util/nss_sha512crypt.o
+CRYPT_OBJ = \
+ util/nss_sha512crypt.o
PAMSRV_OBJ = \
responder/pam/pamsrv.o \
responder/pam/pamsrv_cmd.o \
+ responder/pam/pamsrv_cache.o \
responder/pam/pam_LOCAL_domain.o \
responder/pam/pamsrv_dp.o
-PAMSRV_UTIL_OBJ = responder/pam/pamsrv_util.o
-
$(LDAP_BE_OBJ): CFLAGS += $(LDAP_CFLAGS)
$(CRYPT_OBJ): CFLAGS += $(NSS_CFLAGS)
@@ -103,14 +104,14 @@ sbin/sssd: $(SERVER_OBJ) $(UTIL_OBJ)
sbin/sssd_nss: $(NSSSRV_OBJ) $(UTIL_OBJ) $(RESPONDER_UTIL_OBJ)
$(CC) -o sbin/sssd_nss $(NSSSRV_OBJ) $(UTIL_OBJ) $(RESPONDER_UTIL_OBJ) $(LDFLAGS) $(LIBS)
-sbin/sssd_pam: $(PAMSRV_OBJ) $(UTIL_OBJ) $(RESPONDER_UTIL_OBJ) $(PAMSRV_UTIL_OBJ) $(CRYPT_OBJ)
- $(CC) -o sbin/sssd_pam $(PAMSRV_OBJ) $(UTIL_OBJ) $(PAMSRV_UTIL_OBJ) $(RESPONDER_UTIL_OBJ) $(CRYPT_OBJ) $(LDFLAGS) $(LIBS) $(NSS_LIBS)
+sbin/sssd_pam: $(PAMSRV_OBJ) $(UTIL_OBJ) $(RESPONDER_UTIL_OBJ) $(CRYPT_OBJ)
+ $(CC) -o sbin/sssd_pam $(PAMSRV_OBJ) $(UTIL_OBJ) $(RESPONDER_UTIL_OBJ) $(CRYPT_OBJ) $(LDFLAGS) $(LIBS) $(NSS_LIBS)
-sbin/sssd_dp: $(DP_OBJ) $(UTIL_OBJ) $(PAMSRV_UTIL_OBJ)
- $(CC) -o sbin/sssd_dp $(DP_OBJ) $(UTIL_OBJ) $(PAMSRV_UTIL_OBJ) $(LDFLAGS) $(LIBS)
+sbin/sssd_dp: $(DP_OBJ) $(UTIL_OBJ)
+ $(CC) -o sbin/sssd_dp $(DP_OBJ) $(UTIL_OBJ) $(LDFLAGS) $(LIBS)
sbin/sssd_be: $(DP_BE_OBJ) $(UTIL_OBJ)
- $(CC) -Wl,-E -o sbin/sssd_be $(DP_BE_OBJ) $(UTIL_OBJ) $(PAMSRV_UTIL_OBJ) $(LDFLAGS) $(LIBS) $(PAM_LIBS)
+ $(CC) -Wl,-E -o sbin/sssd_be $(DP_BE_OBJ) $(UTIL_OBJ) $(LDFLAGS) $(LIBS) $(PAM_LIBS)
sbin/sssd_info: $(INFOPIPE_OBJ) $(UTIL_OBJ)
$(CC) -o sbin/sssd_info $(INFOPIPE_OBJ) $(UTIL_OBJ) $(LDFLAGS) $(LIBS)