diff --git a/Makefile.am b/Makefile.am index 095c889..2546d59 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ AUTOMAKE_OPTIONS = dist-bzip2 -SUBDIRS = src data tests po +SUBDIRS = src data tests pam po EXTRA_DIST = TODO intltool-extract.in intltool-merge.in intltool-update.in diff --git a/configure.ac b/configure.ac index d7f8ebb..e9b48f7 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,25 @@ PKG_CHECK_MODULES(DAEMON, glib-2.0 dbus-glib-1 gmodule-2.0 polkit >= 0.8 polkit- AC_SUBST(DAEMON_LIBS) AC_SUBST(DAEMON_CFLAGS) +AC_ARG_ENABLE(pam, AC_HELP_STRING([--enable-pam],[Build the fprintd PAM module]), enable_pam="$enableval", enable_pam=yes) +has_pam=no +if test x$enable_pam = xyes; then + has_pam=yes + AC_CHECK_HEADER([security/pam_modules.h], [has_pam=yes] , [has_pam=no]) + if test x$has_pam = xyes; then + has_pam=no + AC_CHECK_LIB(pam, pam_start, [PAM_LIBS="-lpam" + has_pam=yes], + has_pam=no) + fi + AC_SUBST(PAM_LIBS) +fi +AM_CONDITIONAL(HAVE_PAM, test "x$has_pam" = "xyes") + +AC_MSG_CHECKING(for PAM headers and library) +AC_MSG_RESULT([$has_pam]) + + AC_CHECK_PROG([POLKIT_POLICY_FILE_VALIDATE], [polkit-policy-file-validate], [polkit-policy-file-validate]) @@ -36,15 +55,8 @@ AC_DEFINE_UNQUOTED(DBUS_SERVICES_DIR, "$DBUS_SERVICES_DIR", [Where services dir AC_DEFINE_UNQUOTED(SYSCONFDIR, "$sysconfdir", [Where the configuration file will be located]) -# Restore gnu89 inline semantics on gcc 4.3 and newer -saved_cflags="$CFLAGS" -CFLAGS="$CFLAGS -fgnu89-inline" -AC_COMPILE_IFELSE(AC_LANG_PROGRAM([]), inline_cflags="-fgnu89-inline", inline_cflags="") -CFLAGS="$saved_cflags" +GNOME_COMPILE_WARNINGS -AM_CFLAGS="-std=gnu99 $inline_cflags -Wall -Wundef -Wunused -Wstrict-prototypes -Werror-implicit-function-declaration -Wno-pointer-sign -Wshadow" -AC_SUBST(AM_CFLAGS) - -AC_CONFIG_FILES([Makefile] [src/Makefile] [data/Makefile] [tests/Makefile] [po/Makefile.in]) +AC_CONFIG_FILES([Makefile] [src/Makefile] [data/Makefile] [tests/Makefile] [pam/Makefile] [po/Makefile.in]) AC_OUTPUT diff --git a/pam/Makefile.am b/pam/Makefile.am new file mode 100644 index 0000000..710cfb9 --- /dev/null +++ b/pam/Makefile.am @@ -0,0 +1,13 @@ +if HAVE_PAM + +pammod_PROGRAMS = pam_fprintd.so +pammoddir=/lib/security + +pam_fprintd_so_SOURCES = pam_fprintd.c +pam_fprintd_so_CFLAGS = -fPIC $(WARN_CFLAGS) $(GLIB_CFLAGS) +pam_fprintd_so_LDFLAGS = -shared +pam_fprintd_so_LDADD = $(PAM_LIBS) $(GLIB_LIBS) + +endif + +EXTRA_DIST = pam_fprint.c diff --git a/pam/pam_fprintd.c b/pam/pam_fprintd.c new file mode 100644 index 0000000..749ea7a --- /dev/null +++ b/pam/pam_fprintd.c @@ -0,0 +1,376 @@ +/* + * pam_fprint: PAM module for fingerprint authentication through fprintd + * Copyright (C) 2007 Daniel Drake + * Copyright (C) 2008 Bastien Nocera + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include + +#include + +#define PAM_SM_AUTH +#include + +#define MAX_TRIES 3 +#define TIMEOUT 30 + +enum fp_verify_result { + VERIFY_NO_MATCH = 0, + VERIFY_MATCH = 1, + VERIFY_RETRY = 100, + VERIFY_RETRY_TOO_SHORT = 101, + VERIFY_RETRY_CENTER_FINGER = 102, + VERIFY_RETRY_REMOVE_FINGER = 103, +}; + +static const char *verify_result_str(int result) +{ + switch (result) { + case VERIFY_NO_MATCH: + return "No match"; + case VERIFY_MATCH: + return "Match!"; + case VERIFY_RETRY: + return "Retry scan"; + case VERIFY_RETRY_TOO_SHORT: + return "Swipe too short, please retry"; + case VERIFY_RETRY_CENTER_FINGER: + return "Finger not centered, please retry"; + case VERIFY_RETRY_REMOVE_FINGER: + return "Please remove finger and retry"; + default: + return "Unknown"; + } +} + +enum fp_finger { + LEFT_THUMB = 1, /** thumb (left hand) */ + LEFT_INDEX, /** index finger (left hand) */ + LEFT_MIDDLE, /** middle finger (left hand) */ + LEFT_RING, /** ring finger (left hand) */ + LEFT_LITTLE, /** little finger (left hand) */ + RIGHT_THUMB, /** thumb (right hand) */ + RIGHT_INDEX, /** index finger (right hand) */ + RIGHT_MIDDLE, /** middle finger (right hand) */ + RIGHT_RING, /** ring finger (right hand) */ + RIGHT_LITTLE, /** little finger (right hand) */ +}; + +static gboolean send_info_msg(pam_handle_t *pamh, const char *msg) +{ + const struct pam_message mymsg = { + .msg_style = PAM_TEXT_INFO, + .msg = msg, + }; + const struct pam_message *msgp = &mymsg; + const struct pam_conv *pc; + struct pam_response *resp; + int r; + + r = pam_get_item(pamh, PAM_CONV, (const void **) &pc); + if (r != PAM_SUCCESS) + return FALSE; + + if (!pc || !pc->conv) + return FALSE; + + return (pc->conv(1, &msgp, &resp, pc->appdata_ptr) == PAM_SUCCESS); +} + +static gboolean send_err_msg(pam_handle_t *pamh, const char *msg) +{ + const struct pam_message mymsg = { + .msg_style = PAM_ERROR_MSG, + .msg = msg, + }; + const struct pam_message *msgp = &mymsg; + const struct pam_conv *pc; + struct pam_response *resp; + int r; + + r = pam_get_item(pamh, PAM_CONV, (const void **) &pc); + if (r != PAM_SUCCESS) + return FALSE; + + if (!pc || !pc->conv) + return FALSE; + + return (pc->conv(1, &msgp, &resp, pc->appdata_ptr) == PAM_SUCCESS); +} + + +static const char *fingerstr(enum fp_finger finger) +{ + const char *names[] = { + [LEFT_THUMB] = "left thumb", + [LEFT_INDEX] = "left index", + [LEFT_MIDDLE] = "left middle", + [LEFT_RING] = "left ring", + [LEFT_LITTLE] = "left little", + [RIGHT_THUMB] = "right thumb", + [RIGHT_INDEX] = "right index", + [RIGHT_MIDDLE] = "right middle", + [RIGHT_RING] = "right ring", + [RIGHT_LITTLE] = "right little", + }; + if (finger < LEFT_THUMB || finger > RIGHT_LITTLE) + return "UNKNOWN"; + return names[finger]; +} + +static DBusGProxy *create_manager (DBusGConnection **ret_conn) +{ + GError *error = NULL; + DBusGConnection *connection; + DBusGProxy *manager; + + connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); + if (connection == NULL) { + g_error_free (error); + return NULL; + } + + manager = dbus_g_proxy_new_for_name(connection, + "net.reactivated.Fprint", "/net/reactivated/Fprint/Manager", + "net.reactivated.Fprint.Manager"); + *ret_conn = connection; + + return manager; +} + +static DBusGProxy *open_device(DBusGConnection *connection, DBusGProxy *manager, const char *username) +{ + GError *error = NULL; + GPtrArray *devices; + gchar *path; + DBusGProxy *dev; + + if (!dbus_g_proxy_call (manager, "GetDevices", &error, + G_TYPE_INVALID, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), + &devices, G_TYPE_INVALID)) { + //g_print("list_devices failed: %s", error->message); + g_error_free (error); + return NULL; + } + + if (devices->len == 0) { + //g_print("No devices found\n"); + return NULL; + } + + //g_print("found %d devices\n", devices->len); + path = g_ptr_array_index(devices, 0); + //g_print("Using device %s\n", path); + + dev = dbus_g_proxy_new_for_name(connection, + "net.reactivated.Fprint", + path, + "net.reactivated.Fprint.Device"); + + g_ptr_array_foreach(devices, (GFunc) g_free, NULL); + g_ptr_array_free(devices, TRUE); + + if (!dbus_g_proxy_call (dev, "Claim", &error, G_TYPE_STRING, username, G_TYPE_INVALID, G_TYPE_INVALID)) { + //g_print("failed to claim device: %s\n", error->message); + g_error_free (error); + g_object_unref (dev); + return NULL; + } + return dev; +} + +typedef struct { + guint max_tries; + int result; + gboolean verify_completed; + gboolean timed_out; + pam_handle_t *pamh; +} verify_data; + +static void verify_result(GObject *object, int result, gpointer user_data) +{ + verify_data *data = user_data; + + //g_print("Verify result: %s (%d)\n", verify_result_str(result), result); + if (result == VERIFY_NO_MATCH || result == VERIFY_MATCH) { + data->verify_completed = TRUE; + data->result = result; + } +} + +static void verify_finger_selected(GObject *object, int finger, gpointer user_data) +{ + verify_data *data = user_data; + char *msg; + //FIXME + const char *driver_name = "Fingerprint reader"; + + if (finger == -1) { + msg = g_strdup_printf ("Scan finger on %s", driver_name); + } else { + msg = g_strdup_printf ("Scan %s finger on %s", fingerstr(finger), driver_name); + } + send_info_msg (data->pamh, msg); + g_free (msg); +} + +static gboolean verify_timeout_cb (gpointer user_data) +{ + verify_data *data = user_data; + + data->timed_out = TRUE; + data->verify_completed = TRUE; + + send_info_msg (data->pamh, "Verification timed out"); + + return FALSE; +} + +static int do_verify(pam_handle_t *pamh, DBusGProxy *dev) +{ + GError *error; + verify_data *data; + int ret; + + data = g_new0 (verify_data, 1); + data->max_tries = MAX_TRIES; + data->pamh = pamh; + + dbus_g_proxy_add_signal(dev, "VerifyStatus", G_TYPE_INT, NULL); + dbus_g_proxy_add_signal(dev, "VerifyFingerSelected", G_TYPE_INT, NULL); + dbus_g_proxy_connect_signal(dev, "VerifyStatus", G_CALLBACK(verify_result), + data, NULL); + dbus_g_proxy_connect_signal(dev, "VerifyFingerSelected", G_CALLBACK(verify_finger_selected), + data, NULL); + + + ret = PAM_AUTH_ERR; + + while (ret == PAM_AUTH_ERR && data->max_tries > 0) { + guint timeout_id; + + timeout_id = g_timeout_add_seconds (TIMEOUT, verify_timeout_cb, data); + + if (!dbus_g_proxy_call (dev, "VerifyStart", &error, G_TYPE_UINT, -1, G_TYPE_INVALID, G_TYPE_INVALID)) { + //g_print("VerifyStart failed: %s", error->message); + g_error_free (error); + break; + } + + while (!data->verify_completed) + g_main_context_iteration(NULL, TRUE); + + /* Ignore errors from VerifyStop */ + dbus_g_proxy_call (dev, "VerifyStop", NULL, G_TYPE_INVALID, G_TYPE_INVALID); + + g_source_remove (timeout_id); + + if (data->timed_out) + ret = PAM_AUTHINFO_UNAVAIL; + else { + if (data->result == VERIFY_NO_MATCH) + ret = PAM_AUTH_ERR; + else if (data->result == VERIFY_MATCH) + ret = PAM_SUCCESS; + else if (data->result < 0) + ret = PAM_AUTHINFO_UNAVAIL; + else { + send_info_msg (data->pamh, verify_result_str (data->result)); + ret = PAM_AUTH_ERR; + } + } + data->max_tries--; + } + + dbus_g_proxy_disconnect_signal(dev, "VerifyStatus", G_CALLBACK(verify_result), data); + dbus_g_proxy_disconnect_signal(dev, "VerifyFingerSelected", G_CALLBACK(verify_finger_selected), data); + + g_free (data); + + return ret; +} + +static void release_device(DBusGProxy *dev) +{ + GError *error = NULL; + if (!dbus_g_proxy_call (dev, "Release", &error, G_TYPE_INVALID, G_TYPE_INVALID)) { + //g_print ("ReleaseDevice failed: %s\n", error->message); + g_error_free (error); + } +} + +static int do_auth(pam_handle_t *pamh, const char *username) +{ + DBusGProxy *manager; + DBusGConnection *connection; + GMainLoop *loop; + DBusGProxy *dev; + int ret; + + loop = g_main_loop_new(NULL, FALSE); + manager = create_manager (&connection); + if (manager == NULL) + return PAM_AUTHINFO_UNAVAIL; + + dev = open_device(connection, manager, username); + g_object_unref (manager); + if (!dev) + return PAM_AUTHINFO_UNAVAIL; + ret = do_verify(pamh, dev); + release_device(dev); + g_object_unref (dev); + + return ret; +} + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + const char *rhost = NULL; + const char *username; + int r; + + pam_get_item(pamh, PAM_RHOST, (const void **)(const void*) &rhost); + if (rhost != NULL && strlen(rhost) > 0) { + /* remote login (e.g. over SSH) */ + return PAM_AUTHINFO_UNAVAIL; + } + + r = pam_get_user(pamh, &username, NULL); + if (r != PAM_SUCCESS) + return PAM_AUTHINFO_UNAVAIL; + + r = do_auth(pamh, username); + + return r; +} + +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return PAM_SUCCESS; +} + diff --git a/src/Makefile.am b/src/Makefile.am index 49ec91d..2b74b82 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,7 +8,7 @@ libexec_PROGRAMS = fprintd fprintd_SOURCES = main.c manager.c device.c file_storage.c fprintd_LDADD = $(FPRINT_LIBS) $(DAEMON_LIBS) -fprintd_CFLAGS = $(AM_CFLAGS) $(FPRINT_CFLAGS) $(DAEMON_CFLAGS) -DLOCALEDIR=\""$(datadir)/locale"\" -DPLUGINDIR=\""$(libdir)/fprintd/modules"\" +fprintd_CFLAGS = $(WARN_CFLAGS) $(FPRINT_CFLAGS) $(DAEMON_CFLAGS) -DLOCALEDIR=\""$(datadir)/locale"\" -DPLUGINDIR=\""$(libdir)/fprintd/modules"\" manager-dbus-glue.h: manager.xml dbus-binding-tool --prefix=fprint_manager --mode=glib-server $< --output=$@ diff --git a/tests/Makefile.am b/tests/Makefile.am index 9ea58a9..f262975 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -5,15 +5,15 @@ CLEANFILES = $(BUILT_SOURCES) bin_PROGRAMS = verify enroll list verify_SOURCES = verify.c -verify_CFLAGS = $(AM_CFLAGS) $(GLIB_CFLAGS) +verify_CFLAGS = $(WARN_CFLAGS) $(GLIB_CFLAGS) verify_LDADD = $(GLIB_LIBS) enroll_SOURCES = enroll.c -enroll_CFLAGS = $(AM_CFLAGS) $(GLIB_CFLAGS) +enroll_CFLAGS = $(WARN_CFLAGS) $(GLIB_CFLAGS) enroll_LDADD = $(GLIB_LIBS) list_SOURCES = list.c -list_CFLAGS = $(AM_CFLAGS) $(GLIB_CFLAGS) +list_CFLAGS = $(WARN_CFLAGS) $(GLIB_CFLAGS) list_LDADD = $(GLIB_LIBS) manager-dbus-glue.h: ../src/manager.xml