pam_fprintd.c 11.4 KB
Newer Older
Bastien Nocera's avatar
Bastien Nocera committed
1 2 3 4 5
/*
 * pam_fprint: PAM module for fingerprint authentication through fprintd
 * Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
 * Copyright (C) 2008 Bastien Nocera <hadess@hadess.net>
 * 
6 7 8
 * Experimental code. This will be moved out of fprintd into it's own
 * package once the system has matured.
 *
Bastien Nocera's avatar
Bastien Nocera committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 * 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 <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
28
#include <syslog.h>
Bastien Nocera's avatar
Bastien Nocera committed
29 30

#include <dbus/dbus-glib-bindings.h>
31
#include <dbus/dbus-glib-lowlevel.h>
Bastien Nocera's avatar
Bastien Nocera committed
32 33 34 35

#define PAM_SM_AUTH
#include <security/pam_modules.h>

36 37
#include "marshal.h"

38 39 40
#define N_(x) x
#include "fingerprint-strings.h"

Bastien Nocera's avatar
Bastien Nocera committed
41 42 43
#define MAX_TRIES 3
#define TIMEOUT 30

44 45 46 47 48 49 50 51 52
#define D(pamh, ...) {					\
	if (debug) {					\
		char *s;				\
		s = g_strdup_printf (__VA_ARGS__);	\
		send_debug_msg (pamh, s);		\
		g_free (s);				\
	}						\
}

53

54
static gboolean debug = FALSE;
55

Bastien Nocera's avatar
Bastien Nocera committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
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);
}

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
static void send_debug_msg(pam_handle_t *pamh, const char *msg)
{
	gconstpointer item;
	const char *service;

	if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS || !item)
		service = "<unknown>";
	else
		service = item;

	openlog (service, LOG_CONS | LOG_PID, LOG_AUTHPRIV);

	syslog (LOG_AUTHPRIV|LOG_WARNING, "%s(%s): %s", "pam_fprintd", service, msg);

	closelog ();

}

static DBusGProxy *create_manager (pam_handle_t *pamh, DBusGConnection **ret_conn, GMainLoop **ret_loop)
Bastien Nocera's avatar
Bastien Nocera committed
117 118
{
	DBusGConnection *connection;
119
	DBusConnection *conn;
Bastien Nocera's avatar
Bastien Nocera committed
120
	DBusGProxy *manager;
121 122 123
	DBusError error;
	GMainLoop *loop;
	GMainContext *ctx;
Bastien Nocera's avatar
Bastien Nocera committed
124

125 126 127 128 129 130 131 132 133 134
	/* Otherwise dbus-glib doesn't setup it value types */
	connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, NULL);

	if (connection != NULL)
		dbus_g_connection_unref (connection);

	/* And set us up a private D-Bus connection */
	dbus_error_init (&error);
	conn = dbus_bus_get_private (DBUS_BUS_SYSTEM, &error);
	if (conn == NULL) {
135
		D(pamh, "Error with getting the bus: %s", error.message);
136
		dbus_error_free (&error);
Bastien Nocera's avatar
Bastien Nocera committed
137 138 139
		return NULL;
	}

140 141 142 143 144 145 146
	/* Set up our own main loop context */
	ctx = g_main_context_new ();
	loop = g_main_loop_new (ctx, FALSE);
	dbus_connection_setup_with_g_main (conn, ctx);

	connection = dbus_connection_get_g_connection (conn);

Bastien Nocera's avatar
Bastien Nocera committed
147
	manager = dbus_g_proxy_new_for_name(connection,
148 149
					    "net.reactivated.Fprint",
					    "/net/reactivated/Fprint/Manager",
Bastien Nocera's avatar
Bastien Nocera committed
150 151
					    "net.reactivated.Fprint.Manager");
	*ret_conn = connection;
152
	*ret_loop = loop;
Bastien Nocera's avatar
Bastien Nocera committed
153 154 155 156

	return manager;
}

157
static void close_and_unref (DBusGConnection *connection)
Bastien Nocera's avatar
Bastien Nocera committed
158 159 160 161 162 163 164 165
{
	DBusConnection *conn;

	conn = dbus_g_connection_get_connection (connection);
	dbus_connection_close (conn);
	dbus_g_connection_unref (connection);
}

166
static DBusGProxy *open_device(pam_handle_t *pamh, DBusGConnection *connection, DBusGProxy *manager, const char *username)
Bastien Nocera's avatar
Bastien Nocera committed
167 168 169 170 171
{
	GError *error = NULL;
	gchar *path;
	DBusGProxy *dev;

Bastien Nocera's avatar
Bastien Nocera committed
172 173 174
	if (!dbus_g_proxy_call (manager, "GetDefaultDevice", &error,
				G_TYPE_INVALID, DBUS_TYPE_G_OBJECT_PATH,
				&path, G_TYPE_INVALID)) {
175
		D(pamh, "get_default_devices failed: %s", error->message);
Bastien Nocera's avatar
Bastien Nocera committed
176 177 178 179
		g_error_free (error);
		return NULL;
	}
	
Bastien Nocera's avatar
Bastien Nocera committed
180
	if (path == NULL) {
181
		D(pamh, "No devices found\n");
Bastien Nocera's avatar
Bastien Nocera committed
182 183 184
		return NULL;
	}

185
	D(pamh, "Using device %s\n", path);
Bastien Nocera's avatar
Bastien Nocera committed
186 187 188 189 190 191

	dev = dbus_g_proxy_new_for_name(connection,
					"net.reactivated.Fprint",
					path,
					"net.reactivated.Fprint.Device");
	
Bastien Nocera's avatar
Bastien Nocera committed
192
	g_free (path);
Bastien Nocera's avatar
Bastien Nocera committed
193 194

	if (!dbus_g_proxy_call (dev, "Claim", &error, G_TYPE_STRING, username, G_TYPE_INVALID, G_TYPE_INVALID)) {
195
		D(pamh, "failed to claim device: %s\n", error->message);
Bastien Nocera's avatar
Bastien Nocera committed
196 197 198 199 200 201 202 203 204
		g_error_free (error);
		g_object_unref (dev);
		return NULL;
	}
	return dev;
}

typedef struct {
	guint max_tries;
205
	char *result;
Bastien Nocera's avatar
Bastien Nocera committed
206
	gboolean timed_out;
207
	gboolean is_swipe;
Bastien Nocera's avatar
Bastien Nocera committed
208
	pam_handle_t *pamh;
209
	GMainLoop *loop;
210 211

	char *driver;
Bastien Nocera's avatar
Bastien Nocera committed
212 213
} verify_data;

214
static void verify_result(GObject *object, const char *result, gboolean done, gpointer user_data)
Bastien Nocera's avatar
Bastien Nocera committed
215 216
{
	verify_data *data = user_data;
217
	const char *msg;
Bastien Nocera's avatar
Bastien Nocera committed
218

219
	D(data->pamh, "Verify result: %s\n", result);
220
	if (done != FALSE) {
221
		data->result = g_strdup (result);
222
		g_main_loop_quit (data->loop);
223
		return;
Bastien Nocera's avatar
Bastien Nocera committed
224
	}
225

226
	msg = verify_result_str_to_msg (result, data->is_swipe);
227
	send_err_msg (data->pamh, msg);
Bastien Nocera's avatar
Bastien Nocera committed
228 229
}

230
static void verify_finger_selected(GObject *object, const char *finger_name, gpointer user_data)
Bastien Nocera's avatar
Bastien Nocera committed
231 232 233 234
{
	verify_data *data = user_data;
	char *msg;

235
	if (g_str_equal (finger_name, "any")) {
236 237 238 239
		if (data->is_swipe == FALSE)
			msg = g_strdup_printf ("Place your finger on %s", data->driver);
		else
			msg = g_strdup_printf ("Swipe your finger on %s", data->driver);
Bastien Nocera's avatar
Bastien Nocera committed
240
	} else {
241
		msg = g_strdup_printf (finger_str_to_msg(finger_name, data->is_swipe), data->driver);
Bastien Nocera's avatar
Bastien Nocera committed
242
	}
243
	D(data->pamh, "verify_finger_selected %s", msg);
Bastien Nocera's avatar
Bastien Nocera committed
244 245 246 247 248 249 250 251 252 253
	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;
	send_info_msg (data->pamh, "Verification timed out");
254 255
	g_main_loop_quit (data->loop);

Bastien Nocera's avatar
Bastien Nocera committed
256 257 258
	return FALSE;
}

259
static int do_verify(GMainLoop *loop, pam_handle_t *pamh, DBusGProxy *dev)
Bastien Nocera's avatar
Bastien Nocera committed
260
{
261
	GError *error = NULL;
262
	GHashTable *props;
263
	DBusGProxy *p;
Bastien Nocera's avatar
Bastien Nocera committed
264 265 266 267 268 269
	verify_data *data;
	int ret;

	data = g_new0 (verify_data, 1);
	data->max_tries = MAX_TRIES;
	data->pamh = pamh;
270
	data->loop = loop;
Bastien Nocera's avatar
Bastien Nocera committed
271

272
	/* Get some properties for the device */
273
	p = dbus_g_proxy_new_from_proxy (dev, "org.freedesktop.DBus.Properties", NULL);
274

275
	if (dbus_g_proxy_call (p, "GetAll", NULL, G_TYPE_STRING, "net.reactivated.Fprint.Device", G_TYPE_INVALID,
276
			       dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), &props, G_TYPE_INVALID)) {
277
		const char *scan_type;
278 279
		data->driver = g_value_dup_string (g_hash_table_lookup (props, "name"));
		scan_type = g_value_dup_string (g_hash_table_lookup (props, "scan-type"));
280 281
		if (g_str_equal (scan_type, "swipe"))
			data->is_swipe = TRUE;
282 283
		g_hash_table_destroy (props);
	}
284 285 286

	g_object_unref (p);

287 288 289
	if (!data->driver)
		data->driver = g_strdup ("Fingerprint reader");

290 291
	dbus_g_proxy_add_signal(dev, "VerifyStatus", G_TYPE_STRING, G_TYPE_BOOLEAN, NULL);
	dbus_g_proxy_add_signal(dev, "VerifyFingerSelected", G_TYPE_STRING, NULL);
Bastien Nocera's avatar
Bastien Nocera committed
292 293 294 295 296 297 298 299
	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) {
300
		GSource *source;
Bastien Nocera's avatar
Bastien Nocera committed
301 302
		guint timeout_id;

303 304 305 306 307 308
		/* Set up the timeout on our non-default context */
		source = g_timeout_source_new_seconds (TIMEOUT);
		timeout_id = g_source_attach (source, g_main_loop_get_context (loop));
		g_source_set_callback (source, verify_timeout_cb, data, NULL);

		data->timed_out = FALSE;
Bastien Nocera's avatar
Bastien Nocera committed
309

310
		if (!dbus_g_proxy_call (dev, "VerifyStart", &error, G_TYPE_STRING, "any", G_TYPE_INVALID, G_TYPE_INVALID)) {
311
			D(pamh, "VerifyStart failed: %s", error->message);
Bastien Nocera's avatar
Bastien Nocera committed
312
			g_error_free (error);
313 314 315

			g_source_remove (timeout_id);
			g_source_unref (source);
Bastien Nocera's avatar
Bastien Nocera committed
316 317 318
			break;
		}

319 320 321 322
		g_main_loop_run (loop);

		g_source_remove (timeout_id);
		g_source_unref (source);
Bastien Nocera's avatar
Bastien Nocera committed
323 324 325 326

		/* Ignore errors from VerifyStop */
		dbus_g_proxy_call (dev, "VerifyStop", NULL, G_TYPE_INVALID, G_TYPE_INVALID);

327
		if (data->timed_out) {
Bastien Nocera's avatar
Bastien Nocera committed
328
			ret = PAM_AUTHINFO_UNAVAIL;
329 330
			break;
		} else {
331
			if (g_str_equal (data->result, "verify-no-match")) {
332
				send_err_msg (data->pamh, "Failed to match fingerprint");
Bastien Nocera's avatar
Bastien Nocera committed
333
				ret = PAM_AUTH_ERR;
334
			} else if (g_str_equal (data->result, "verify-match"))
Bastien Nocera's avatar
Bastien Nocera committed
335
				ret = PAM_SUCCESS;
336
			else if (g_str_equal (data->result, "verify-unknown-error"))
Bastien Nocera's avatar
Bastien Nocera committed
337
				ret = PAM_AUTHINFO_UNAVAIL;
338 339 340 341 342
			else if (g_str_equal (data->result, "verify-disconnected")) {
				ret = PAM_AUTHINFO_UNAVAIL;
				g_free (data->result);
				break;
			} else {
343
				send_info_msg (data->pamh, "An unknown error occured");
Bastien Nocera's avatar
Bastien Nocera committed
344
				ret = PAM_AUTH_ERR;
345
				g_free (data->result);
346
				break;
Bastien Nocera's avatar
Bastien Nocera committed
347
			}
348 349
			g_free (data->result);
			data->result = NULL;
Bastien Nocera's avatar
Bastien Nocera committed
350 351 352 353 354 355 356
		}
		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);

357
	g_free (data->driver);
Bastien Nocera's avatar
Bastien Nocera committed
358 359 360 361 362
	g_free (data);

	return ret;
}

363
static void release_device(pam_handle_t *pamh, DBusGProxy *dev)
Bastien Nocera's avatar
Bastien Nocera committed
364 365 366
{
	GError *error = NULL;
	if (!dbus_g_proxy_call (dev, "Release", &error, G_TYPE_INVALID, G_TYPE_INVALID)) {
367
		D(pamh, "ReleaseDevice failed: %s\n", error->message);
Bastien Nocera's avatar
Bastien Nocera committed
368 369 370 371 372 373 374 375 376
		g_error_free (error);
	}
}

static int do_auth(pam_handle_t *pamh, const char *username)
{
	DBusGProxy *manager;
	DBusGConnection *connection;
	DBusGProxy *dev;
377
	GMainLoop *loop;
Bastien Nocera's avatar
Bastien Nocera committed
378 379
	int ret;

380
	manager = create_manager (pamh, &connection, &loop);
Bastien Nocera's avatar
Bastien Nocera committed
381 382 383
	if (manager == NULL)
		return PAM_AUTHINFO_UNAVAIL;

384
	dev = open_device(pamh, connection, manager, username);
Bastien Nocera's avatar
Bastien Nocera committed
385
	g_object_unref (manager);
Bastien Nocera's avatar
Bastien Nocera committed
386 387
	if (!dev) {
		g_main_loop_unref (loop);
Bastien Nocera's avatar
Bastien Nocera committed
388
		close_and_unref (connection);
Bastien Nocera's avatar
Bastien Nocera committed
389
		return PAM_AUTHINFO_UNAVAIL;
Bastien Nocera's avatar
Bastien Nocera committed
390
	}
391
	ret = do_verify(loop, pamh, dev);
Bastien Nocera's avatar
Bastien Nocera committed
392

393
	g_main_loop_unref (loop);
394
	release_device(pamh, dev);
Bastien Nocera's avatar
Bastien Nocera committed
395
	g_object_unref (dev);
Bastien Nocera's avatar
Bastien Nocera committed
396
	close_and_unref (connection);
Bastien Nocera's avatar
Bastien Nocera committed
397 398 399 400 401 402 403 404 405

	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;
406
	guint i;
Bastien Nocera's avatar
Bastien Nocera committed
407 408
	int r;

Bastien Nocera's avatar
Bastien Nocera committed
409 410
	g_type_init ();

411 412 413
	dbus_g_object_register_marshaller (fprintd_marshal_VOID__STRING_BOOLEAN,
					   G_TYPE_NONE, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INVALID);

Bastien Nocera's avatar
Bastien Nocera committed
414 415 416 417 418 419 420 421 422 423
	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;

424 425 426 427 428 429 430
	for (i = 0; i < argc; i++) {
		if (argv[i] != NULL && g_str_equal (argv[i], "debug")) {
			g_message ("debug on");
			debug = TRUE;
		}
	}

Bastien Nocera's avatar
Bastien Nocera committed
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
	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;
}