35 Commits

Author SHA1 Message Date
52058c1ea0 Release 1.90.6 2020-12-07 15:34:48 +01:00
22cdc0a7ea device: Always use FpFinger instead of a mixture of it and finger numbers
We already use FpFinger for storage operations and prints management,
but internally we keep still using the old finger number, that uses
different values for invalid data.

Let's be consistent, and always use FpFinger everywhere.
2020-12-07 15:27:14 +01:00
043fcaafec pam_fprintd: Guard against NULL pointer returned by finger_str_to_msg
Otherwise the PAM module will crash trying to send an info message about
the selected print.
2020-12-07 15:27:14 +01:00
bf2236620e pam_fprintd: Implement auto-pointers and use early-return more around
Implement simple auto-pointers for the types we use in pam_fprintd with
a basic implementation based on GLib one so that we can have the same
features without having neither an header-dependency on it.
2020-12-07 15:27:14 +01:00
0122d351f9 fprintd: Add tests for device properties values 2020-12-07 15:27:14 +01:00
4435706d20 tests: Add test that PAM gives up when daemon disappears 2020-12-07 15:27:14 +01:00
c5877bbc12 pam: Stop authentication with PAM_AUTHINFO_UNAVAIL on name owner change
If fprintd disappears or is replaced, then we might be getting signals
from another daemon/verifcation session.

As such we must give up at that point.

Related: #47
2020-12-07 15:27:14 +01:00
a170a3a09f tests: Check that PAM stops if the device could not be claimed 2020-12-07 15:27:14 +01:00
a76af6ce71 pam_fprintd: Clear the data value before setting it if set 2020-12-07 15:27:14 +01:00
34a24eac77 tests/pam: Ensure that we ignore verify events before VerifyStart has completed
We had a race that was causing the events to be handled even if we were
not ready to accept them, causing a potential non-authentication.

So simulate this case, by sending a 'verify-match' event before we
started the verification and ensure that we ignore it.
2020-12-07 15:27:14 +01:00
47751548b2 pam_fprintd: Ignore any verify signal if we didn't complete VerifyStart
In case fprintd is emitting a verify signal for another request that is
still going on while we're about to start a new verification, we'd just
accept such signal, so potentially allowing a log-in because another
concurrent request succeeded.

To avoid this, use async call to VerifyStart and open a verify window
(during which we accept the verification related signals) that is kept
open just once the VerifyStart call has been completed and before
stopping the verification again. As that's the only moment in which we
can be sure that we've control of the daemon events for such device.

Thanks to Benjamin to find out the race.

Fixes: #47
2020-12-07 15:27:14 +01:00
a30c45629e tests/pam: Ensure that we fail in case the user has no prints enrolled
This is both in case in we start the authentication and in the absurd
but (hey, testing!) situation in which prints gets deleted in between
the device claiming and the verification start.

To handle this second scenario we need to instruct fprintd mock to raise
an error on some special command
2020-12-07 15:27:14 +01:00
3242b99410 dbusmock/fprintd: Support more complex verify scripts
This now allows:
 * Sending signals before and after method return
 * Exiting the daemon
 * Emulating NoEnrolledPrints DBus method error

Co-authored-by: Benjamin Berg <bberg@redhat.com>
2020-12-07 15:27:14 +01:00
5ccb9ba0ec tests: Do not eat fprintd output in PAM test
It may be useful, just let it go to stdout/stderr.
2020-12-07 15:27:14 +01:00
f4eaacd0ec pam: Return a fixed string about protocol error if there is no message
This can only happen if fprintd is not adhering to the protocol.
2020-12-07 15:27:14 +01:00
34b21fa917 tests: Port test to use the OutputChecker 2020-12-07 15:27:14 +01:00
2d98d4543f verify: Add print about start for tests
This allows properly reading the output only to the point where we can
ensure that verification has started.
2020-12-07 15:27:14 +01:00
8c46fddd03 verify: Fix verify script to work correctly
The verify script would start an async routine. However, this blocks the
dbus return, which really is needed.

Also, we should only return one item of the script for each VerifyStart
run. So, fix things by pop'ing the first item and putting it on the bus
from a GLib.add_timeout handler.
2020-12-07 15:27:14 +01:00
3a00643d5b tests: Add OutputChecker class to improve test code 2020-12-07 15:27:14 +01:00
eb73e024e1 utils: Fix race in verify accepting unrelated signals
Signals like VerifyResult may be received from unrelated Verify
operations. To avoid races, we need to ignore any VerifyResult that
happenes before the DBus method returns.

The only way to do this race-free is to use the async version of the
VerifyStart method.
2020-12-07 15:27:14 +01:00
a4b06c2219 device: Emit VerifyFingerSelected after the method returned
In order to be race free, clients need to ignore all signals until after
the DBus method to start verification has returned. So the signal must
be emitted later than it currently is.
2020-12-07 15:27:14 +01:00
5ccaa094a0 build: Generate fprintd dbus sources using interactive flags
Since we can't depend on newer GLib yet, we patch the generated sources
to generate some new ones with fixed flags.
2020-12-07 15:14:07 +01:00
fc7e4d0e5c device: Do not require authentication for release/stop
If someone has started an operation, then we don't really need to
confirm they are permitted to stop it again. Not doing this has the
advantage that we cannot run into a second interactive authorization
step accidentally.
2020-12-07 15:14:07 +01:00
583cd870d8 device: Use a common error function if an action is ongoing
There is no need to dupliate the code. Just create one function that
sets an error and returns FALSE if action is not ACTION_NONE.
2020-12-07 12:02:32 +01:00
2ca2d5e62c device: Use a switch to check current action so we can be more selective
For example we were allowing to verify stop while doing other actions
different from enrolling (such as delete or open/close).
2020-12-07 12:02:32 +01:00
c5c81a2ea8 device: Add ACTION_DELETE to prevent concurrent operations
Delete needs to operate on the device, so no other actions are permitted
at the same time. And using the libfprint _sync methods does not
guard against reentrance.
2020-12-07 12:02:32 +01:00
c0ad5880a4 tests: Call VerifyStop in enroll_verify_list_delete test
The test didn't call this and the device would be left in an
inconsistent state, causing an error at shutdown time.
2020-12-07 12:02:32 +01:00
2dc3a4e2c5 device: Use more standard naming for local errors 2020-12-07 12:00:58 +01:00
3b0d93bcc2 tests: Add more authorization tests 2020-12-07 12:00:58 +01:00
eac171ab0f device: Add separate state for delete which will claim internally
Also rename the different claim states to make them a bit more
understandable.
2020-12-07 12:00:58 +01:00
7533f63a06 device: Move permissions checks per DBus invocation in a single function
This way we can avoid repeating the same checks multiple times, and
we have a single point where we check the permissions needed for method
invocation.
2020-12-07 11:59:42 +01:00
a38917ab26 fprintd: Re-order permissions table by priority and add docs
Given that we could do operations where at least one permission, is
requested, we should give more priority to the weaker ones that are
acceptable and in case raise the level at later points.
2020-12-05 01:01:36 +01:00
a92b8e5f60 device: Always return FALSE if setting an error in Check claimed
We may have a case where the sender matches with the
session's sender but have a session invocation already set.

In such case we set an error, but still return TRUE.
2020-12-05 01:01:36 +01:00
29f34cf23c tests/fprintd: Do not hang if we error on name appeared callback
But instead only wait for name to appear and do the tests in the main
function so that we can properly check the exception and depending on
its type skip the test or raise it so that it can be caught by the test
suite
2020-12-04 22:56:37 +01:00
a10f0dc22d net.reactivated.Fprint: Allow actual fprind interfaces to be used
We allowed a non-existant net.reactivated.Fprint interface, while our
interfaces are Manager and Device.

Allow them to be used.
2020-12-03 17:54:21 +01:00
16 changed files with 1106 additions and 396 deletions

18
NEWS
View File

@ -1,6 +1,24 @@
This file lists notable changes in each release. For the full history of all This file lists notable changes in each release. For the full history of all
changes, see ChangeLog. changes, see ChangeLog.
Version 1.90.6:
The 1.90.5 release was unusable due to a number of inter-related issues
with the DBus interface and authorization. We also found a number of
problems with possible security implications.
Currently fprintd will do interactive authorization even if this was not
requested using the correct DBus method call flag. All API users MUST be
updated to set the flag as it will be enabled in the future!
Highlights:
- Fix fprintd DBus configuration
- Change details of what requires authorization
- Fix various race conditions in pam_fprintd
- Permit interactive authorization from fprintd utilities
- Do not allow deletion while another operation is ongoing
Version 1.90.5: Version 1.90.5:
The 1.90.4 release contained some bad errors, this release addresses those. The 1.90.4 release contained some bad errors, this release addresses those.

View File

@ -13,7 +13,9 @@
<!-- Anyone can talk to the service --> <!-- Anyone can talk to the service -->
<policy context="default"> <policy context="default">
<allow send_destination="net.reactivated.Fprint" <allow send_destination="net.reactivated.Fprint"
send_interface="net.reactivated.Fprint"/> send_interface="net.reactivated.Fprint.Manager"/>
<allow send_destination="net.reactivated.Fprint"
send_interface="net.reactivated.Fprint.Device"/>
<!-- Basic D-Bus API stuff --> <!-- Basic D-Bus API stuff -->
<allow send_destination="net.reactivated.Fprint" <allow send_destination="net.reactivated.Fprint"

View File

@ -1,5 +1,5 @@
project('fprintd', 'c', project('fprintd', 'c',
version: '1.90.5', version: '1.90.6',
license: 'GPLv2+', license: 'GPLv2+',
default_options: [ default_options: [
'buildtype=debugoptimized', 'buildtype=debugoptimized',
@ -62,6 +62,7 @@ common_cflags = cc.get_supported_arguments([
add_project_arguments(common_cflags, language: 'c') add_project_arguments(common_cflags, language: 'c')
host_system = host_machine.system() host_system = host_machine.system()
# NOTE: Bump gdbus-codegen min version once we can depend on 2.64!
glib_min_version = '2.56' glib_min_version = '2.56'
libfprint_min_version = '1.90.1' libfprint_min_version = '1.90.1'

View File

@ -43,6 +43,7 @@
#define N_(s) (s) #define N_(s) (s)
#include "fingerprint-strings.h" #include "fingerprint-strings.h"
#include "pam_fprintd_autoptrs.h"
#define DEFAULT_MAX_TRIES 3 #define DEFAULT_MAX_TRIES 3
#define DEFAULT_TIMEOUT 30 #define DEFAULT_TIMEOUT 30
@ -83,10 +84,8 @@ static bool send_msg(pam_handle_t *pamh, const char *msg, int style)
const struct pam_message *msgp = &mymsg; const struct pam_message *msgp = &mymsg;
const struct pam_conv *pc; const struct pam_conv *pc;
struct pam_response *resp; struct pam_response *resp;
int r;
r = pam_get_item(pamh, PAM_CONV, (const void **) &pc); if (pam_get_item(pamh, PAM_CONV, (const void **) &pc) != PAM_SUCCESS)
if (r != PAM_SUCCESS)
return false; return false;
if (!pc || !pc->conv) if (!pc || !pc->conv)
@ -110,39 +109,35 @@ open_device (pam_handle_t *pamh,
sd_bus *bus, sd_bus *bus,
bool *has_multiple_devices) bool *has_multiple_devices)
{ {
sd_bus_error error = SD_BUS_ERROR_NULL; pf_auto(sd_bus_error) error = SD_BUS_ERROR_NULL;
sd_bus_message *m = NULL; pf_autoptr(sd_bus_message) m = NULL;
size_t num_devices; size_t num_devices;
const char *path = NULL; const char *path = NULL;
char *ret;
const char *s; const char *s;
int r; int r;
*has_multiple_devices = false; *has_multiple_devices = false;
r = sd_bus_call_method (bus, if (sd_bus_call_method (bus,
"net.reactivated.Fprint", "net.reactivated.Fprint",
"/net/reactivated/Fprint/Manager", "/net/reactivated/Fprint/Manager",
"net.reactivated.Fprint.Manager", "net.reactivated.Fprint.Manager",
"GetDevices", "GetDevices",
&error, &error,
&m, &m,
NULL); NULL) < 0) {
if (r < 0) {
pam_syslog (pamh, LOG_ERR, "GetDevices failed: %s", error.message); pam_syslog (pamh, LOG_ERR, "GetDevices failed: %s", error.message);
sd_bus_error_free (&error);
return NULL; return NULL;
} }
r = sd_bus_message_enter_container (m, 'a', "o"); r = sd_bus_message_enter_container (m, 'a', "o");
if (r < 0) { if (r < 0) {
pam_syslog (pamh, LOG_ERR, "Failed to parse answer from GetDevices(): %d", r); pam_syslog (pamh, LOG_ERR, "Failed to parse answer from GetDevices(): %d", r);
goto out; return NULL;
} }
r = sd_bus_message_read_basic (m, 'o', &path); if (sd_bus_message_read_basic (m, 'o', &path) < 0)
if (r < 0) return NULL;
goto out;
num_devices = 1; num_devices = 1;
while ((r = sd_bus_message_read_basic(m, 'o', &s)) > 0) while ((r = sd_bus_message_read_basic(m, 'o', &s)) > 0)
@ -153,22 +148,35 @@ open_device (pam_handle_t *pamh,
sd_bus_message_exit_container (m); sd_bus_message_exit_container (m);
out: return strdup (path);
ret = path ? strdup (path) : NULL;
sd_bus_message_unref (m);
return ret;
} }
typedef struct { typedef struct {
char *dev;
bool has_multiple_devices;
unsigned max_tries; unsigned max_tries;
char *result; char *result;
bool timed_out; bool timed_out;
bool is_swipe; bool is_swipe;
bool verify_started;
int verify_ret;
pam_handle_t *pamh; pam_handle_t *pamh;
char *driver; char *driver;
} verify_data; } verify_data;
static void
verify_data_free (verify_data *data)
{
free (data->result);
free (data->driver);
free (data->dev);
free (data);
}
PF_DEFINE_AUTOPTR_CLEANUP_FUNC (verify_data, verify_data_free)
static int static int
verify_result (sd_bus_message *m, verify_result (sd_bus_message *m,
void *userdata, void *userdata,
@ -193,15 +201,29 @@ verify_result (sd_bus_message *m,
return 0; return 0;
} }
if (!data->verify_started) {
pam_syslog (data->pamh, LOG_ERR, "Unexpected VerifyResult '%s', %"PRIu64" signal", result, done);
return 0;
}
if (debug) if (debug)
pam_syslog (data->pamh, LOG_DEBUG, "Verify result: %s (done: %d)", result, done ? 1 : 0); pam_syslog (data->pamh, LOG_DEBUG, "Verify result: %s (done: %d)", result, done ? 1 : 0);
if (data->result) {
free (data->result);
data->result = NULL;
}
if (done) { if (done) {
data->result = strdup (result); data->result = strdup (result);
return 0; return 0;
} }
msg = verify_result_str_to_msg (result, data->is_swipe); msg = verify_result_str_to_msg (result, data->is_swipe);
if (!msg) {
data->result = strdup ("Protocol error with fprintd!");
return 0;
}
send_err_msg (data->pamh, msg); send_err_msg (data->pamh, msg);
return 0; return 0;
@ -214,18 +236,26 @@ verify_finger_selected (sd_bus_message *m,
{ {
verify_data *data = userdata; verify_data *data = userdata;
const char *finger_name = NULL; const char *finger_name = NULL;
char *msg; pf_autofree char *msg = NULL;
if (sd_bus_message_read_basic (m, 's', &finger_name) < 0) { if (sd_bus_message_read_basic (m, 's', &finger_name) < 0) {
pam_syslog (data->pamh, LOG_ERR, "Failed to parse VerifyFingerSelected signal: %d", errno); pam_syslog (data->pamh, LOG_ERR, "Failed to parse VerifyFingerSelected signal: %d", errno);
return 0; return 0;
} }
if (!data->verify_started) {
pam_syslog (data->pamh, LOG_ERR, "Unexpected VerifyFingerSelected %s signal", finger_name);
return 0;
}
msg = finger_str_to_msg(finger_name, data->driver, data->is_swipe); msg = finger_str_to_msg(finger_name, data->driver, data->is_swipe);
if (!msg) {
data->result = strdup ("Protocol error with fprintd!");
return 0;
}
if (debug) if (debug)
pam_syslog (data->pamh, LOG_DEBUG, "verify_finger_selected %s", msg); pam_syslog (data->pamh, LOG_DEBUG, "verify_finger_selected %s", msg);
send_info_msg (data->pamh, msg); send_info_msg (data->pamh, msg);
free (msg);
return 0; return 0;
} }
@ -240,7 +270,7 @@ get_property_string (sd_bus *bus,
sd_bus_error *error, sd_bus_error *error,
char **ret) { char **ret) {
sd_bus_message *reply = NULL; pf_autoptr(sd_bus_message) reply = NULL;
const char *s; const char *s;
char *n; char *n;
int r; int r;
@ -251,121 +281,133 @@ get_property_string (sd_bus *bus,
r = sd_bus_message_enter_container(reply, 'v', "s"); r = sd_bus_message_enter_container(reply, 'v', "s");
if (r < 0) if (r < 0)
goto fail; return sd_bus_error_set_errno(error, r);
r = sd_bus_message_read_basic(reply, 's', &s); r = sd_bus_message_read_basic(reply, 's', &s);
if (r < 0) if (r < 0)
goto fail; return sd_bus_error_set_errno(error, r);
n = strdup(s); n = strdup(s);
if (!n) { if (!n) {
r = -ENOMEM; return sd_bus_error_set_errno(error, -ENOMEM);
goto fail;
} }
sd_bus_message_unref (reply);
*ret = n; *ret = n;
return 0; return 0;
}
fail:
if (reply != NULL) static int verify_started_cb (sd_bus_message *m,
sd_bus_message_unref (reply); void *userdata,
return sd_bus_error_set_errno(error, r); sd_bus_error *ret_error) {
const sd_bus_error *error = sd_bus_message_get_error (m);
verify_data *data = userdata;
if (error) {
if (sd_bus_error_has_name (error, "net.reactivated.Fprint.Error.NoEnrolledPrints")) {
pam_syslog (data->pamh, LOG_DEBUG, "No prints enrolled");
data->verify_ret = PAM_USER_UNKNOWN;
} else {
data->verify_ret = PAM_AUTH_ERR;
}
if (debug)
pam_syslog (data->pamh, LOG_DEBUG, "VerifyStart failed: %s", error->message);
return 1;
}
if (debug)
pam_syslog (data->pamh, LOG_DEBUG, "VerifyStart completed successfully");
data->verify_started = true;
return 1;
} }
static int static int
do_verify (pam_handle_t *pamh, do_verify (sd_bus *bus,
sd_bus *bus, verify_data *data)
const char *dev,
bool has_multiple_devices)
{ {
verify_data *data; pf_autoptr(sd_bus_slot) verify_status_slot = NULL;
sd_bus_slot *verify_status_slot, *verify_finger_selected_slot; pf_autoptr(sd_bus_slot) verify_finger_selected_slot = NULL;
char *scan_type = NULL; pf_autofree char *scan_type = NULL;
int ret;
int r; int r;
data = calloc (1, sizeof(verify_data));
data->max_tries = max_tries;
data->pamh = pamh;
/* Get some properties for the device */ /* Get some properties for the device */
r = get_property_string (bus, r = get_property_string (bus,
"net.reactivated.Fprint", "net.reactivated.Fprint",
dev, data->dev,
"net.reactivated.Fprint.Device", "net.reactivated.Fprint.Device",
"scan-type", "scan-type",
NULL, NULL,
&scan_type); &scan_type);
if (r < 0) if (r < 0)
pam_syslog (data->pamh, LOG_ERR, "Failed to get scan-type for %s: %d", dev, r); pam_syslog (data->pamh, LOG_ERR, "Failed to get scan-type for %s: %d", data->dev, r);
if (debug) if (debug)
pam_syslog (data->pamh, LOG_DEBUG, "scan-type for %s: %s", dev, scan_type); pam_syslog (data->pamh, LOG_DEBUG, "scan-type for %s: %s", data->dev, scan_type);
if (str_equal (scan_type, "swipe")) if (str_equal (scan_type, "swipe"))
data->is_swipe = true; data->is_swipe = true;
free (scan_type);
if (has_multiple_devices) { if (data->has_multiple_devices) {
get_property_string (bus, get_property_string (bus,
"net.reactivated.Fprint", "net.reactivated.Fprint",
dev, data->dev,
"net.reactivated.Fprint.Device", "net.reactivated.Fprint.Device",
"name", "name",
NULL, NULL,
&data->driver); &data->driver);
if (r < 0) if (r < 0)
pam_syslog (data->pamh, LOG_ERR, "Failed to get driver name for %s: %d", dev, r); pam_syslog (data->pamh, LOG_ERR, "Failed to get driver name for %s: %d", data->dev, r);
if (debug && r == 0) if (debug && r == 0)
pam_syslog (data->pamh, LOG_DEBUG, "driver name for %s: %s", dev, data->driver); pam_syslog (data->pamh, LOG_DEBUG, "driver name for %s: %s", data->dev, data->driver);
} }
verify_status_slot = NULL;
sd_bus_match_signal (bus, sd_bus_match_signal (bus,
&verify_status_slot, &verify_status_slot,
"net.reactivated.Fprint", "net.reactivated.Fprint",
dev, data->dev,
"net.reactivated.Fprint.Device", "net.reactivated.Fprint.Device",
"VerifyStatus", "VerifyStatus",
verify_result, verify_result,
data); data);
verify_finger_selected_slot = NULL;
sd_bus_match_signal (bus, sd_bus_match_signal (bus,
&verify_finger_selected_slot, &verify_finger_selected_slot,
"net.reactivated.Fprint", "net.reactivated.Fprint",
dev, data->dev,
"net.reactivated.Fprint.Device", "net.reactivated.Fprint.Device",
"VerifyFingerSelected", "VerifyFingerSelected",
verify_finger_selected, verify_finger_selected,
data); data);
ret = PAM_AUTH_ERR; while (data->max_tries > 0) {
while (ret == PAM_AUTH_ERR && data->max_tries > 0) {
uint64_t verification_end = now () + (timeout * USEC_PER_SEC); uint64_t verification_end = now () + (timeout * USEC_PER_SEC);
sd_bus_message *m = NULL;
sd_bus_error error = SD_BUS_ERROR_NULL;
data->timed_out = false; data->timed_out = false;
data->verify_started = false;
data->verify_ret = PAM_INCOMPLETE;
r = sd_bus_call_method (bus, free (data->result);
data->result = NULL;
if (debug)
pam_syslog (data->pamh, LOG_DEBUG, "About to call VerifyStart");
r = sd_bus_call_method_async (bus,
NULL,
"net.reactivated.Fprint", "net.reactivated.Fprint",
dev, data->dev,
"net.reactivated.Fprint.Device", "net.reactivated.Fprint.Device",
"VerifyStart", "VerifyStart",
&error, verify_started_cb,
&m, data,
"s", "s",
"any"); "any");
if (r < 0) { if (r < 0) {
if (sd_bus_error_has_name (&error, "net.reactivated.Fprint.Error.NoEnrolledPrints"))
ret = PAM_USER_UNKNOWN;
if (debug) if (debug)
pam_syslog (pamh, LOG_DEBUG, "VerifyStart failed: %s", error.message); pam_syslog (data->pamh, LOG_DEBUG, "VerifyStart call failed: %d", r);
sd_bus_error_free (&error);
break; break;
} }
@ -379,29 +421,38 @@ do_verify (pam_handle_t *pamh,
r = sd_bus_process (bus, NULL); r = sd_bus_process (bus, NULL);
if (r < 0) if (r < 0)
break; break;
if (data->verify_ret != PAM_INCOMPLETE)
break;
if (!data->verify_started)
continue;
if (data->result != NULL) if (data->result != NULL)
break; break;
if (r == 0) { if (r == 0) {
if (debug) { if (debug) {
pam_syslog(pamh, LOG_DEBUG, "Waiting for %"PRId64" seconds (%"PRId64" usecs)", pam_syslog(data->pamh, LOG_DEBUG,
"Waiting for %"PRId64" seconds (%"PRId64" usecs)",
wait_time / USEC_PER_SEC, wait_time / USEC_PER_SEC,
wait_time); wait_time);
} }
r = sd_bus_wait (bus, wait_time); if (sd_bus_wait (bus, wait_time) < 0)
if (r < 0)
break; break;
} }
} }
if (data->verify_ret != PAM_INCOMPLETE) {
return data->verify_ret;
}
if (now () >= verification_end) { if (now () >= verification_end) {
data->timed_out = true; data->timed_out = true;
send_info_msg (data->pamh, _("Verification timed out")); send_info_msg (data->pamh, _("Verification timed out"));
} }
/* Ignore errors from VerifyStop */ /* Ignore errors from VerifyStop */
data->verify_started = false;
sd_bus_call_method (bus, sd_bus_call_method (bus,
"net.reactivated.Fprint", "net.reactivated.Fprint",
dev, data->dev,
"net.reactivated.Fprint.Device", "net.reactivated.Fprint.Device",
"VerifyStop", "VerifyStop",
NULL, NULL,
@ -410,43 +461,28 @@ do_verify (pam_handle_t *pamh,
NULL); NULL);
if (data->timed_out) { if (data->timed_out) {
ret = PAM_AUTHINFO_UNAVAIL; return PAM_AUTHINFO_UNAVAIL;
break;
} else { } else {
if (str_equal (data->result, "verify-no-match")) { if (str_equal (data->result, "verify-no-match")) {
send_err_msg (data->pamh, "Failed to match fingerprint"); send_err_msg (data->pamh, "Failed to match fingerprint");
ret = PAM_AUTH_ERR;
} else if (str_equal (data->result, "verify-match")) { } else if (str_equal (data->result, "verify-match")) {
ret = PAM_SUCCESS; return PAM_SUCCESS;
break;
} else if (str_equal (data->result, "verify-unknown-error")) { } else if (str_equal (data->result, "verify-unknown-error")) {
ret = PAM_AUTHINFO_UNAVAIL; return PAM_AUTHINFO_UNAVAIL;
} else if (str_equal (data->result, "verify-disconnected")) { } else if (str_equal (data->result, "verify-disconnected")) {
ret = PAM_AUTHINFO_UNAVAIL; return PAM_AUTHINFO_UNAVAIL;
break;
} else { } else {
send_err_msg (data->pamh, _("An unknown error occurred")); send_err_msg (data->pamh, _("An unknown error occurred"));
ret = PAM_AUTH_ERR; return PAM_AUTH_ERR;
break;
} }
free (data->result);
data->result = NULL;
} }
data->max_tries--; data->max_tries--;
} }
if (data->max_tries == 0) if (data->max_tries == 0)
ret = PAM_MAXTRIES; return PAM_MAXTRIES;
sd_bus_slot_unref (verify_status_slot); return PAM_AUTH_ERR;
sd_bus_slot_unref (verify_finger_selected_slot);
if (data->result)
free (data->result);
free (data->driver);
free (data);
return ret;
} }
static bool static bool
@ -455,8 +491,8 @@ user_has_prints (pam_handle_t *pamh,
const char *dev, const char *dev,
const char *username) const char *username)
{ {
sd_bus_error error = SD_BUS_ERROR_NULL; pf_auto(sd_bus_error) error = SD_BUS_ERROR_NULL;
sd_bus_message *m = NULL; pf_autoptr(sd_bus_message) m = NULL;
size_t num_fingers = 0; size_t num_fingers = 0;
const char *s; const char *s;
int r; int r;
@ -478,23 +514,19 @@ user_has_prints (pam_handle_t *pamh,
pam_syslog (pamh, LOG_DEBUG, "ListEnrolledFingers failed for %s: %s", pam_syslog (pamh, LOG_DEBUG, "ListEnrolledFingers failed for %s: %s",
username, error.message); username, error.message);
} }
sd_bus_error_free (&error);
return false; return false;
} }
r = sd_bus_message_enter_container (m, 'a', "s"); r = sd_bus_message_enter_container (m, 'a', "s");
if (r < 0) { if (r < 0) {
pam_syslog (pamh, LOG_ERR, "Failed to parse answer from ListEnrolledFingers(): %d", r); pam_syslog (pamh, LOG_ERR, "Failed to parse answer from ListEnrolledFingers(): %d", r);
goto out; return false;
} }
num_fingers = 0;
while ((r = sd_bus_message_read_basic(m, 's', &s)) > 0) while ((r = sd_bus_message_read_basic(m, 's', &s)) > 0)
num_fingers++; num_fingers++;
sd_bus_message_exit_container (m); sd_bus_message_exit_container (m);
out:
sd_bus_message_unref (m);
return (num_fingers > 0); return (num_fingers > 0);
} }
@ -503,10 +535,9 @@ release_device (pam_handle_t *pamh,
sd_bus *bus, sd_bus *bus,
const char *dev) const char *dev)
{ {
sd_bus_error error = SD_BUS_ERROR_NULL; pf_auto(sd_bus_error) error = SD_BUS_ERROR_NULL;
int r;
r = sd_bus_call_method (bus, if (sd_bus_call_method (bus,
"net.reactivated.Fprint", "net.reactivated.Fprint",
dev, dev,
"net.reactivated.Fprint.Device", "net.reactivated.Fprint.Device",
@ -514,10 +545,8 @@ release_device (pam_handle_t *pamh,
&error, &error,
NULL, NULL,
NULL, NULL,
NULL); NULL) < 0) {
if (r < 0) {
pam_syslog (pamh, LOG_ERR, "ReleaseDevice failed: %s", error.message); pam_syslog (pamh, LOG_ERR, "ReleaseDevice failed: %s", error.message);
sd_bus_error_free (&error);
} }
} }
@ -527,10 +556,9 @@ claim_device (pam_handle_t *pamh,
const char *dev, const char *dev,
const char *username) const char *username)
{ {
sd_bus_error error = SD_BUS_ERROR_NULL; pf_auto(sd_bus_error) error = SD_BUS_ERROR_NULL;
int r;
r = sd_bus_call_method (bus, if (sd_bus_call_method (bus,
"net.reactivated.Fprint", "net.reactivated.Fprint",
dev, dev,
"net.reactivated.Fprint.Device", "net.reactivated.Fprint.Device",
@ -538,53 +566,88 @@ claim_device (pam_handle_t *pamh,
&error, &error,
NULL, NULL,
"s", "s",
username); username) < 0) {
if (r < 0) {
if (debug) if (debug)
pam_syslog (pamh, LOG_DEBUG, "failed to claim device %s", error.message); pam_syslog (pamh, LOG_DEBUG, "failed to claim device %s", error.message);
sd_bus_error_free (&error);
return false; return false;
} }
return true; return true;
} }
static int
name_owner_changed (sd_bus_message *m,
void *userdata,
sd_bus_error *ret_error)
{
verify_data *data = userdata;
const char *name = NULL;
const char *old_owner = NULL;
const char *new_owner = NULL;
if (sd_bus_message_read (m, "sss", &name, &old_owner, &new_owner) < 0) {
pam_syslog (data->pamh, LOG_ERR, "Failed to parse NameOwnerChanged signal: %d", errno);
return 0;
}
if (strcmp (name, "net.reactivated.Fprint") != 0)
return 0;
/* Name owner for fprintd changed, give up as we might start listening
* to events from a new name owner otherwise. */
data->verify_ret = PAM_AUTHINFO_UNAVAIL;
if (debug)
pam_syslog (data->pamh, LOG_ERR, "fprintd name owner changed during operation!\n");
return 0;
}
static int do_auth(pam_handle_t *pamh, const char *username) static int do_auth(pam_handle_t *pamh, const char *username)
{ {
char *dev;
bool have_prints; bool have_prints;
bool has_multiple_devices; pf_autoptr(verify_data) data = NULL;
int ret = PAM_AUTHINFO_UNAVAIL; pf_autoptr(sd_bus) bus = NULL;
sd_bus *bus = NULL; pf_autoptr(sd_bus_slot) name_owner_changed_slot = NULL;
data = calloc (1, sizeof(verify_data));
data->max_tries = max_tries;
data->pamh = pamh;
if (sd_bus_open_system (&bus) < 0) { if (sd_bus_open_system (&bus) < 0) {
pam_syslog (pamh, LOG_ERR, "Error with getting the bus: %d", errno); pam_syslog (pamh, LOG_ERR, "Error with getting the bus: %d", errno);
return PAM_AUTHINFO_UNAVAIL; return PAM_AUTHINFO_UNAVAIL;
} }
dev = open_device (pamh, bus, &has_multiple_devices); name_owner_changed_slot = NULL;
if (dev == NULL) { sd_bus_match_signal (bus,
sd_bus_unref (bus); &name_owner_changed_slot,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"NameOwnerChanged",
name_owner_changed,
data);
data->dev = open_device (pamh, bus, &data->has_multiple_devices);
if (data->dev == NULL) {
return PAM_AUTHINFO_UNAVAIL; return PAM_AUTHINFO_UNAVAIL;
} }
have_prints = user_has_prints (pamh, bus, dev, username); have_prints = user_has_prints (pamh, bus, data->dev, username);
if (debug) if (debug)
pam_syslog (pamh, LOG_DEBUG, "prints registered: %s\n", have_prints ? "yes" : "no"); pam_syslog (pamh, LOG_DEBUG, "prints registered: %s\n", have_prints ? "yes" : "no");
if (!have_prints) if (!have_prints)
goto out; return PAM_AUTHINFO_UNAVAIL;
if (claim_device (pamh, bus, dev, username)) { if (claim_device (pamh, bus, data->dev, username)) {
ret = do_verify (pamh, bus, dev, has_multiple_devices); int ret = do_verify (bus, data);
release_device (pamh, bus, dev); release_device (pamh, bus, data->dev);
return ret;
} }
out: return PAM_AUTHINFO_UNAVAIL;
free (dev);
sd_bus_unref (bus);
return ret;
} }
static bool static bool
@ -614,7 +677,6 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
{ {
const char *username; const char *username;
int i; int i;
int r;
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
@ -622,8 +684,7 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
if (is_remote (pamh)) if (is_remote (pamh))
return PAM_AUTHINFO_UNAVAIL; return PAM_AUTHINFO_UNAVAIL;
r = pam_get_user(pamh, &username, NULL); if (pam_get_user(pamh, &username, NULL) != PAM_SUCCESS)
if (r != PAM_SUCCESS)
return PAM_AUTHINFO_UNAVAIL; return PAM_AUTHINFO_UNAVAIL;
for (i = 0; i < argc; i++) { for (i = 0; i < argc; i++) {
@ -674,9 +735,7 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
} }
} }
r = do_auth(pamh, username); return do_auth(pamh, username);
return r;
} }
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,

View File

@ -0,0 +1,60 @@
/*
* pam_fprint: PAM module for fingerprint authentication through fprintd
* Copyright (C) 2020 Marco Trevisan <marco.trevisan@canonical.com>
*
* 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.
*/
#pragma once
#include <stdlib.h>
/* Define auto-pointers functions, based on GLib Macros */
#define _CLEANUP_FUNC(func) __attribute__((cleanup(func)))
#define _PF_AUTOPTR_FUNC_NAME(TypeName) pf_autoptr_cleanup_##TypeName
#define _PF_AUTOPTR_TYPENAME(TypeName) TypeName##_pf_autoptr
#define PF_DEFINE_AUTOPTR_CLEANUP_FUNC(TypeName, cleanup) \
typedef TypeName *_PF_AUTOPTR_TYPENAME (TypeName); \
static __attribute__((__unused__)) inline void \
_PF_AUTOPTR_FUNC_NAME (TypeName) (TypeName **_ptr) \
{ if (_ptr) (cleanup) (*_ptr); }
#define PF_DEFINE_AUTO_CLEAN_FUNC(TypeName, cleanup) \
static __attribute__((__unused__)) inline void \
_PF_AUTOPTR_FUNC_NAME (TypeName) (TypeName *_ptr) \
{ cleanup (_ptr); }
static inline void
autoptr_cleanup_generic_free (void *p)
{
void **pp = (void**)p;
free (*pp);
}
#define pf_autofree _CLEANUP_FUNC (autoptr_cleanup_generic_free)
#define pf_autoptr(TypeName) \
_CLEANUP_FUNC (_PF_AUTOPTR_FUNC_NAME (TypeName)) \
_PF_AUTOPTR_TYPENAME (TypeName)
#define pf_auto(TypeName) \
_CLEANUP_FUNC (_PF_AUTOPTR_FUNC_NAME (TypeName)) TypeName
PF_DEFINE_AUTOPTR_CLEANUP_FUNC (sd_bus, sd_bus_unref)
PF_DEFINE_AUTOPTR_CLEANUP_FUNC (sd_bus_message, sd_bus_message_unref)
PF_DEFINE_AUTOPTR_CLEANUP_FUNC (sd_bus_slot, sd_bus_slot_unref)
PF_DEFINE_AUTO_CLEAN_FUNC (sd_bus_error, sd_bus_error_free)

View File

@ -0,0 +1,110 @@
--- a/src/fprintd-dbus.c 2020-12-04 16:38:28.527712626 +0100
+++ b/src/fprintd-dbus.c 2020-12-04 16:40:03.561692619 +0100
@@ -1149,7 +1149,7 @@
"ListEnrolledFingers",
g_variant_new ("(s)",
arg_username),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
callback,
@@ -1213,7 +1213,7 @@
"ListEnrolledFingers",
g_variant_new ("(s)",
arg_username),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
error);
@@ -1253,7 +1253,7 @@
"DeleteEnrolledFingers",
g_variant_new ("(s)",
arg_username),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
callback,
@@ -1312,7 +1312,7 @@
"DeleteEnrolledFingers",
g_variant_new ("(s)",
arg_username),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
error);
@@ -1348,7 +1348,7 @@
g_dbus_proxy_call (G_DBUS_PROXY (proxy),
"DeleteEnrolledFingers2",
g_variant_new ("()"),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
callback,
@@ -1404,7 +1404,7 @@
_ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy),
"DeleteEnrolledFingers2",
g_variant_new ("()"),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
error);
@@ -1443,7 +1443,7 @@
"Claim",
g_variant_new ("(s)",
arg_username),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
callback,
@@ -1502,7 +1502,7 @@
"Claim",
g_variant_new ("(s)",
arg_username),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
error);
@@ -1633,7 +1633,7 @@
"VerifyStart",
g_variant_new ("(s)",
arg_finger_name),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
callback,
@@ -1692,7 +1692,7 @@
"VerifyStart",
g_variant_new ("(s)",
arg_finger_name),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
error);
@@ -1823,7 +1823,7 @@
"EnrollStart",
g_variant_new ("(s)",
arg_finger_name),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
callback,
@@ -1882,7 +1882,7 @@
"EnrollStart",
g_variant_new ("(s)",
arg_finger_name),
- G_DBUS_CALL_FLAGS_NONE,
+ G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
-1,
cancellable,
error);

View File

@ -34,16 +34,16 @@
static const char *FINGERS_NAMES[] = { static const char *FINGERS_NAMES[] = {
[FP_FINGER_UNKNOWN] = "unknown", [FP_FINGER_UNKNOWN] = "unknown",
"left-thumb", [FP_FINGER_LEFT_THUMB] = "left-thumb",
"left-index-finger", [FP_FINGER_LEFT_INDEX] = "left-index-finger",
"left-middle-finger", [FP_FINGER_LEFT_MIDDLE] = "left-middle-finger",
"left-ring-finger", [FP_FINGER_LEFT_RING] = "left-ring-finger",
"left-little-finger", [FP_FINGER_LEFT_LITTLE] = "left-little-finger",
"right-thumb", [FP_FINGER_RIGHT_THUMB] = "right-thumb",
"right-index-finger", [FP_FINGER_RIGHT_INDEX] = "right-index-finger",
"right-middle-finger", [FP_FINGER_RIGHT_MIDDLE] = "right-middle-finger",
"right-ring-finger", [FP_FINGER_RIGHT_RING] = "right-ring-finger",
"right-little-finger" [FP_FINGER_RIGHT_LITTLE] = "right-little-finger"
}; };
static void fprint_device_dbus_skeleton_iface_init (FprintDBusDeviceIface *); static void fprint_device_dbus_skeleton_iface_init (FprintDBusDeviceIface *);
@ -60,12 +60,14 @@ typedef enum {
ACTION_ENROLL, ACTION_ENROLL,
ACTION_OPEN, ACTION_OPEN,
ACTION_CLOSE, ACTION_CLOSE,
ACTION_DELETE,
} FprintDeviceAction; } FprintDeviceAction;
typedef enum { typedef enum {
STATE_CLAIMED, STATE_CLAIMED,
STATE_UNCLAIMED, STATE_UNCLAIMED,
STATE_IGNORED, STATE_AUTO_CLAIM,
STATE_ANYTIME,
} FprintDeviceClaimState; } FprintDeviceClaimState;
typedef struct { typedef struct {
@ -366,22 +368,22 @@ guint32 _fprint_device_get_id(FprintDevice *rdev)
} }
static const char * static const char *
finger_num_to_name (int finger_num) fp_finger_to_name (FpFinger finger)
{ {
if (finger_num == -1) if (finger == FP_FINGER_UNKNOWN)
return "any"; return "any";
if (!FP_FINGER_IS_VALID (finger_num)) if (!FP_FINGER_IS_VALID (finger))
return NULL; return NULL;
return FINGERS_NAMES[finger_num]; return FINGERS_NAMES[finger];
} }
static int static FpFinger
finger_name_to_num (const char *finger_name) finger_name_to_fp_finger (const char *finger_name)
{ {
guint i; FpFinger i;
if (finger_name == NULL || *finger_name == '\0' || g_str_equal (finger_name, "any")) if (finger_name == NULL || *finger_name == '\0' || g_str_equal (finger_name, "any"))
return -1; return FP_FINGER_UNKNOWN;
for (i = FP_FINGER_FIRST; i <= FP_FINGER_LAST; i++) { for (i = FP_FINGER_FIRST; i <= FP_FINGER_LAST; i++) {
if (g_str_equal (finger_name, FINGERS_NAMES[i])) if (g_str_equal (finger_name, FINGERS_NAMES[i]))
@ -389,7 +391,7 @@ finger_name_to_num (const char *finger_name)
} }
/* Invalid, let's try that */ /* Invalid, let's try that */
return -1; return FP_FINGER_UNKNOWN;
} }
static const char * static const char *
@ -464,21 +466,78 @@ enroll_result_to_name (gboolean completed, gboolean enrolled, GError *error)
} }
} }
static FprintDevicePermission
get_permissions_for_invocation (GDBusMethodInvocation *invocation)
{
FprintDevicePermission required_perms;
const char *method_name;
required_perms = FPRINT_DEVICE_PERMISSION_NONE;
method_name = g_dbus_method_invocation_get_method_name (invocation);
if (g_str_equal (method_name, "Claim")) {
required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY;
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "DeleteEnrolledFingers")) {
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "DeleteEnrolledFingers2")) {
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "EnrollStart")) {
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "ListEnrolledFingers")) {
required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY;
} else if (g_str_equal (method_name, "VerifyStart")) {
required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY;
} else if (g_str_equal (method_name, "Release")) {
} else if (g_str_equal (method_name, "EnrollStop")) {
} else if (g_str_equal (method_name, "VerifyStop")) {
/* Don't require permissiong for for release/stop operations.
* We are authenticated already if we could start, and we don't
* want to end up authorizing interactively again.
*/
} else {
g_assert_not_reached ();
}
return required_perms;
}
static FprintDeviceClaimState
get_claim_state_for_invocation (GDBusMethodInvocation *invocation)
{
const char *method_name;
method_name = g_dbus_method_invocation_get_method_name (invocation);
if (g_str_equal (method_name, "Claim")) {
return STATE_UNCLAIMED;
} else if (g_str_equal (method_name, "DeleteEnrolledFingers")) {
return STATE_AUTO_CLAIM;
} else if (g_str_equal (method_name, "ListEnrolledFingers")) {
return STATE_ANYTIME;
}
return STATE_CLAIMED;
}
static gboolean static gboolean
_fprint_device_check_claimed (FprintDevice *rdev, _fprint_device_check_claimed (FprintDevice *rdev,
GDBusMethodInvocation *invocation, GDBusMethodInvocation *invocation,
FprintDeviceClaimState requested_state,
GError **error) GError **error)
{ {
FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev); FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev);
g_autoptr(SessionData) session = NULL; g_autoptr(SessionData) session = NULL;
FprintDeviceClaimState requested_state;
const char *sender; const char *sender;
gboolean retval;
if (requested_state == STATE_IGNORED) requested_state = get_claim_state_for_invocation (invocation);
if (requested_state == STATE_ANYTIME)
return TRUE; return TRUE;
session = session_data_get (priv); session = session_data_get (priv);
if (requested_state == STATE_AUTO_CLAIM)
requested_state = session ? STATE_CLAIMED : STATE_UNCLAIMED;
if (requested_state == STATE_UNCLAIMED) { if (requested_state == STATE_UNCLAIMED) {
/* Is it already claimed? */ /* Is it already claimed? */
@ -501,14 +560,14 @@ _fprint_device_check_claimed (FprintDevice *rdev,
} }
sender = g_dbus_method_invocation_get_sender (invocation); sender = g_dbus_method_invocation_get_sender (invocation);
retval = g_str_equal (sender, session->sender);
if (retval == FALSE || session->invocation != NULL) { if (!g_str_equal (sender, session->sender) || session->invocation != NULL) {
g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
_("Device already in use by another user")); _("Device already in use by another user"));
return FALSE;
} }
return retval; return TRUE;
} }
static gboolean static gboolean
@ -519,7 +578,7 @@ _fprint_device_check_polkit_for_action (FprintDevice *rdev,
{ {
FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev); FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev);
const char *sender; const char *sender;
g_autoptr(GError) _error = NULL; g_autoptr(GError) local_error = NULL;
g_autoptr(PolkitAuthorizationResult) result = NULL; g_autoptr(PolkitAuthorizationResult) result = NULL;
g_autoptr(PolkitSubject) subject = NULL; g_autoptr(PolkitSubject) subject = NULL;
@ -532,11 +591,11 @@ _fprint_device_check_polkit_for_action (FprintDevice *rdev,
action, action,
NULL, NULL,
POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
NULL, &_error); NULL, &local_error);
if (result == NULL) { if (result == NULL) {
g_set_error (error, FPRINT_ERROR, g_set_error (error, FPRINT_ERROR,
FPRINT_ERROR_PERMISSION_DENIED, FPRINT_ERROR_PERMISSION_DENIED,
"Not Authorized: %s", _error->message); "Not Authorized: %s", local_error->message);
return FALSE; return FALSE;
} }
@ -779,13 +838,14 @@ static gboolean fprint_device_claim (FprintDBusDevice *dbus_dev,
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
char *sender, *user; char *sender, *user;
if (!_fprint_device_check_claimed (rdev, invocation, STATE_UNCLAIMED, &error)) { if (!_fprint_device_check_claimed (rdev, invocation, &error)) {
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
user = g_object_steal_qdata (G_OBJECT (invocation), quark_auth_user); user = g_object_steal_qdata (G_OBJECT (invocation), quark_auth_user);
g_assert (user); g_assert (user);
g_assert (g_str_equal (username, "") || g_str_equal (user, username));
sender = g_strdup (g_dbus_method_invocation_get_sender (invocation)); sender = g_strdup (g_dbus_method_invocation_get_sender (invocation));
_fprint_device_add_client (rdev, sender); _fprint_device_add_client (rdev, sender);
@ -838,7 +898,7 @@ static gboolean fprint_device_release (FprintDBusDevice *dbus_dev,
FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); FprintDevice *rdev = FPRINT_DEVICE (dbus_dev);
FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev); FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev);
if (!_fprint_device_check_claimed (rdev, invocation, STATE_CLAIMED, &error)) { if (!_fprint_device_check_claimed (rdev, invocation, &error)) {
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
@ -849,6 +909,8 @@ static gboolean fprint_device_release (FprintDBusDevice *dbus_dev,
} else if (priv->current_action == ACTION_IDENTIFY || } else if (priv->current_action == ACTION_IDENTIFY ||
priv->current_action == ACTION_VERIFY) { priv->current_action == ACTION_VERIFY) {
g_warning("Verification was in progress, stopping it"); g_warning("Verification was in progress, stopping it");
} else if (priv->current_action == ACTION_DELETE) {
g_warning("Deletion was in progress, stopping it");
} }
g_cancellable_cancel (priv->current_cancellable); g_cancellable_cancel (priv->current_cancellable);
@ -893,6 +955,48 @@ static void report_verify_status (FprintDevice *rdev,
session->verify_status_reported = TRUE; session->verify_status_reported = TRUE;
} }
static gboolean can_start_action(FprintDevice *rdev, GError **error) {
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
switch (priv->current_action) {
case ACTION_NONE:
return TRUE;
case ACTION_ENROLL:
g_set_error (error,
FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Enrollment already in progress");
break;
case ACTION_IDENTIFY:
case ACTION_VERIFY:
g_set_error (error,
FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Enrollment already in progress");
break;
case ACTION_OPEN:
g_set_error (error,
FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Claim already in progress");
break;
case ACTION_CLOSE:
g_set_error (error,
FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Release already in progress");
break;
case ACTION_DELETE:
g_set_error (error,
FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Delete already in progress");
break;
default: /* Fallback only. */
g_assert_not_reached();
g_set_error (error,
FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Another operation is already in progress");
}
return FALSE;
}
static void match_cb (FpDevice *device, static void match_cb (FpDevice *device,
FpPrint *match, FpPrint *match,
FpPrint *print, FpPrint *print,
@ -1030,28 +1134,21 @@ static gboolean fprint_device_verify_start (FprintDBusDevice *dbus_dev,
g_autoptr(FpPrint) print = NULL; g_autoptr(FpPrint) print = NULL;
g_autoptr(SessionData) session = NULL; g_autoptr(SessionData) session = NULL;
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
int finger_num = finger_name_to_num (finger_name); FpFinger finger = finger_name_to_fp_finger (finger_name);
if (!_fprint_device_check_claimed (rdev, invocation, STATE_CLAIMED, &error)) { if (!_fprint_device_check_claimed (rdev, invocation, &error)) {
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
session = session_data_get (priv); session = session_data_get (priv);
if (priv->current_action != ACTION_NONE) { if (!can_start_action (rdev, &error)) {
if (priv->current_action == ACTION_ENROLL) {
g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Enrollment in progress");
} else {
g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Verification already in progress");
}
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
if (finger_num == -1) { if (finger == FP_FINGER_UNKNOWN) {
g_autoptr(GSList) prints = NULL; g_autoptr(GSList) prints = NULL;
prints = store.discover_prints(priv->dev, session->username); prints = store.discover_prints(priv->dev, session->username);
@ -1067,19 +1164,19 @@ static gboolean fprint_device_verify_start (FprintDBusDevice *dbus_dev,
gallery = g_ptr_array_new_with_free_func (g_object_unref); gallery = g_ptr_array_new_with_free_func (g_object_unref);
for (l = prints; l != NULL; l = l->next) { for (l = prints; l != NULL; l = l->next) {
g_debug ("adding finger %d to the gallery", GPOINTER_TO_INT (l->data)); g_debug ("adding finger %u to the gallery", GPOINTER_TO_UINT (l->data));
store.print_data_load(priv->dev, GPOINTER_TO_INT (l->data), store.print_data_load(priv->dev, GPOINTER_TO_UINT (l->data),
session->username, &print); session->username, &print);
if (print) if (print)
g_ptr_array_add (gallery, g_steal_pointer (&print)); g_ptr_array_add (gallery, g_steal_pointer (&print));
} }
} else { } else {
finger_num = GPOINTER_TO_INT (prints->data); finger = GPOINTER_TO_UINT (prints->data);
} }
} }
if (fp_device_supports_identify (priv->dev) && finger_num == -1) { if (fp_device_supports_identify (priv->dev) && finger == FP_FINGER_UNKNOWN) {
if (gallery->len == 0) { if (gallery->len == 0) {
g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS, g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS,
"No fingerprints on that device"); "No fingerprints on that device");
@ -1097,14 +1194,14 @@ static gboolean fprint_device_verify_start (FprintDBusDevice *dbus_dev,
} else { } else {
priv->current_action = ACTION_VERIFY; priv->current_action = ACTION_VERIFY;
g_debug("start verification device %d finger %d", priv->id, finger_num); g_debug("start verification device %d finger %d", priv->id, finger);
store.print_data_load(priv->dev, finger_num, store.print_data_load(priv->dev, finger,
session->username, &print); session->username, &print);
if (!print) { if (!print) {
g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS, g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS,
"No such print %d", finger_num); "No such print %d", finger);
g_dbus_method_invocation_return_gerror (invocation, g_dbus_method_invocation_return_gerror (invocation,
error); error);
return TRUE; return TRUE;
@ -1117,12 +1214,12 @@ static gboolean fprint_device_verify_start (FprintDBusDevice *dbus_dev,
(GAsyncReadyCallback) verify_cb, rdev); (GAsyncReadyCallback) verify_cb, rdev);
} }
fprint_dbus_device_complete_verify_start (dbus_dev, invocation);
/* Emit VerifyFingerSelected telling the front-end which finger /* Emit VerifyFingerSelected telling the front-end which finger
* we selected for auth */ * we selected for auth */
g_signal_emit(rdev, signals[SIGNAL_VERIFY_FINGER_SELECTED], g_signal_emit(rdev, signals[SIGNAL_VERIFY_FINGER_SELECTED],
0, finger_num_to_name (finger_num)); 0, fp_finger_to_name (finger));
fprint_dbus_device_complete_verify_start (dbus_dev, invocation);
return TRUE; return TRUE;
} }
@ -1135,22 +1232,26 @@ static gboolean fprint_device_verify_stop (FprintDBusDevice *dbus_dev,
FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev); FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev);
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
if (!_fprint_device_check_claimed (rdev, invocation, STATE_CLAIMED, &error)) { if (!_fprint_device_check_claimed (rdev, invocation, &error)) {
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
if (priv->current_action == ACTION_NONE) { switch (priv->current_action) {
g_dbus_method_invocation_return_error_literal (invocation, case ACTION_VERIFY:
FPRINT_ERROR, case ACTION_IDENTIFY:
break;
case ACTION_NONE:
g_dbus_method_invocation_return_error_literal (
invocation, FPRINT_ERROR,
FPRINT_ERROR_NO_ACTION_IN_PROGRESS, FPRINT_ERROR_NO_ACTION_IN_PROGRESS,
"No verification in progress"); "No verification in progress");
return TRUE; return TRUE;
} else if (priv->current_action == ACTION_ENROLL) { default:
g_dbus_method_invocation_return_error_literal (invocation, g_dbus_method_invocation_return_error_literal (
FPRINT_ERROR, invocation, FPRINT_ERROR,
FPRINT_ERROR_ALREADY_IN_USE, FPRINT_ERROR_ALREADY_IN_USE,
"Enrollment in progress"); "Another operation is already in progress");
return TRUE; return TRUE;
} }
@ -1213,7 +1314,7 @@ static gboolean try_delete_print(FprintDevice *rdev)
guint index; guint index;
store.print_data_load (priv->dev, store.print_data_load (priv->dev,
GPOINTER_TO_INT (fingers->data), GPOINTER_TO_UINT (fingers->data),
username, username,
&print); &print);
@ -1257,7 +1358,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (GDate, g_date_free);
#endif #endif
static FpPrint* static FpPrint*
fprint_device_create_enroll_template(FprintDevice *rdev, gint finger_num) fprint_device_create_enroll_template(FprintDevice *rdev, FpFinger finger)
{ {
g_autoptr(SessionData) session = NULL; g_autoptr(SessionData) session = NULL;
FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev); FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev);
@ -1269,7 +1370,7 @@ fprint_device_create_enroll_template(FprintDevice *rdev, gint finger_num)
session = session_data_get (priv); session = session_data_get (priv);
template = fp_print_new (priv->dev); template = fp_print_new (priv->dev);
fp_print_set_finger (template, finger_num); fp_print_set_finger (template, finger);
fp_print_set_username (template, session->username); fp_print_set_username (template, session->username);
datetime = g_date_time_new_now_local (); datetime = g_date_time_new_now_local ();
g_date_time_get_ymd (datetime, &year, &month, &day); g_date_time_get_ymd (datetime, &year, &month, &day);
@ -1347,36 +1448,29 @@ static gboolean fprint_device_enroll_start (FprintDBusDevice *dbus_dev,
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); FprintDevice *rdev = FPRINT_DEVICE (dbus_dev);
FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev); FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev);
int finger_num = finger_name_to_num (finger_name); FpFinger finger = finger_name_to_fp_finger (finger_name);
if (!_fprint_device_check_claimed (rdev, invocation, STATE_CLAIMED, &error)) { if (!_fprint_device_check_claimed (rdev, invocation, &error)) {
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
if (finger_num == -1) { if (finger == FP_FINGER_UNKNOWN) {
g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_INVALID_FINGERNAME, g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_INVALID_FINGERNAME,
"Invalid finger name"); "Invalid finger name");
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
if (priv->current_action != ACTION_NONE) { if (!can_start_action (rdev, &error)) {
if (priv->current_action == ACTION_ENROLL) {
g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Enrollment already in progress");
} else {
g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE,
"Verification in progress");
}
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
g_debug("start enrollment device %d finger %d", priv->id, finger_num); g_debug("start enrollment device %d finger %d", priv->id, finger);
priv->current_cancellable = g_cancellable_new (); priv->current_cancellable = g_cancellable_new ();
priv->enroll_data = finger_num; priv->enroll_data = finger;
fp_device_enroll (priv->dev, fp_device_enroll (priv->dev,
fprint_device_create_enroll_template (rdev, priv->enroll_data), fprint_device_create_enroll_template (rdev, priv->enroll_data),
priv->current_cancellable, priv->current_cancellable,
@ -1400,24 +1494,25 @@ static gboolean fprint_device_enroll_stop (FprintDBusDevice *dbus_dev,
FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev); FprintDevicePrivate *priv = fprint_device_get_instance_private(rdev);
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
if (!_fprint_device_check_claimed (rdev, invocation, STATE_CLAIMED, &error)) { if (!_fprint_device_check_claimed (rdev, invocation, &error)) {
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
if (priv->current_action != ACTION_ENROLL) { switch (priv->current_action) {
if (priv->current_action == ACTION_NONE) { case ACTION_ENROLL:
g_set_error (&error, FPRINT_ERROR, FPRINT_ERROR_NO_ACTION_IN_PROGRESS, break;
case ACTION_NONE:
g_dbus_method_invocation_return_error_literal (
invocation, FPRINT_ERROR,
FPRINT_ERROR_NO_ACTION_IN_PROGRESS,
"No enrollment in progress"); "No enrollment in progress");
} else if (priv->current_action == ACTION_VERIFY) { return TRUE;
g_set_error (&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, default:
"Verification in progress"); g_dbus_method_invocation_return_error_literal (
} else if (priv->current_action == ACTION_IDENTIFY) { invocation, FPRINT_ERROR,
g_set_error (&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, FPRINT_ERROR_ALREADY_IN_USE,
"Identification in progress"); "Another operation is already in progress");
} else
g_assert_not_reached ();
g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
@ -1461,8 +1556,8 @@ static gboolean fprint_device_list_enrolled_fingers (FprintDBusDevice *dbus_dev,
ret = g_ptr_array_new (); ret = g_ptr_array_new ();
for (item = prints; item; item = item->next) { for (item = prints; item; item = item->next) {
int finger_num = GPOINTER_TO_INT (item->data); FpFinger finger = GPOINTER_TO_UINT (item->data);
g_ptr_array_add (ret, (char *) finger_num_to_name (finger_num)); g_ptr_array_add (ret, (char *) fp_finger_to_name (finger));
} }
g_ptr_array_add (ret, NULL); g_ptr_array_add (ret, NULL);
@ -1491,7 +1586,7 @@ static void delete_enrolled_fingers(FprintDevice *rdev, const char *user)
g_autoptr(FpPrint) print = NULL; g_autoptr(FpPrint) print = NULL;
store.print_data_load(priv->dev, store.print_data_load(priv->dev,
GPOINTER_TO_INT (l->data), GPOINTER_TO_UINT (l->data),
user, user,
&print); &print);
@ -1570,11 +1665,14 @@ static gboolean fprint_device_delete_enrolled_fingers (FprintDBusDevice *dbus_de
log_offending_client (invocation); log_offending_client (invocation);
#endif #endif
user = g_object_steal_qdata (G_OBJECT (invocation), quark_auth_user); if (!can_start_action(rdev, &error)) {
g_assert (user); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE;
}
if (!_fprint_device_check_claimed (rdev, invocation, STATE_CLAIMED, priv->current_action = ACTION_DELETE;
&error)) {
if (!_fprint_device_check_claimed (rdev, invocation, &error)) {
/* Return error for anything but FPRINT_ERROR_CLAIM_DEVICE */ /* Return error for anything but FPRINT_ERROR_CLAIM_DEVICE */
if (!g_error_matches (error, FPRINT_ERROR, FPRINT_ERROR_CLAIM_DEVICE)) { if (!g_error_matches (error, FPRINT_ERROR, FPRINT_ERROR_CLAIM_DEVICE)) {
g_dbus_method_invocation_return_gerror (invocation, g_dbus_method_invocation_return_gerror (invocation,
@ -1593,11 +1691,17 @@ static gboolean fprint_device_delete_enrolled_fingers (FprintDBusDevice *dbus_de
if (!opened && fp_device_has_storage (priv->dev)) if (!opened && fp_device_has_storage (priv->dev))
fp_device_open_sync (priv->dev, NULL, NULL); fp_device_open_sync (priv->dev, NULL, NULL);
user = g_object_steal_qdata (G_OBJECT (invocation), quark_auth_user);
g_assert (user);
g_assert (g_str_equal (username, "") || g_str_equal (user, username));
delete_enrolled_fingers (rdev, user); delete_enrolled_fingers (rdev, user);
if (!opened && fp_device_has_storage (priv->dev)) if (!opened && fp_device_has_storage (priv->dev))
fp_device_close_sync (priv->dev, NULL, NULL); fp_device_close_sync (priv->dev, NULL, NULL);
priv->current_action = ACTION_NONE;
fprint_dbus_device_complete_delete_enrolled_fingers (dbus_dev, fprint_dbus_device_complete_delete_enrolled_fingers (dbus_dev,
invocation); invocation);
return TRUE; return TRUE;
@ -1611,14 +1715,24 @@ static gboolean fprint_device_delete_enrolled_fingers2 (FprintDBusDevice *dbus_d
g_autoptr(SessionData) session = NULL; g_autoptr(SessionData) session = NULL;
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
if (!_fprint_device_check_claimed (rdev, invocation, STATE_CLAIMED, &error)) { if (!_fprint_device_check_claimed (rdev, invocation, &error)) {
g_dbus_method_invocation_return_gerror (invocation, error); g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE; return TRUE;
} }
if (!can_start_action(rdev, &error)) {
g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE;
}
priv->current_action = ACTION_DELETE;
session = session_data_get (priv); session = session_data_get (priv);
delete_enrolled_fingers (rdev, session->username); delete_enrolled_fingers (rdev, session->username);
priv->current_action = ACTION_NONE;
fprint_dbus_device_complete_delete_enrolled_fingers2 (dbus_dev, fprint_dbus_device_complete_delete_enrolled_fingers2 (dbus_dev,
invocation); invocation);
return TRUE; return TRUE;
@ -1650,8 +1764,7 @@ action_authorization_handler (GDBusInterfaceSkeleton *interface,
FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (interface); FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (interface);
FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); FprintDevice *rdev = FPRINT_DEVICE (dbus_dev);
FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev);
FprintDeviceClaimState required_state = STATE_IGNORED; FprintDevicePermission required_perms;
FprintDevicePermission required_perms = FPRINT_DEVICE_PERMISSION_NONE;
gboolean needs_user_auth = FALSE; gboolean needs_user_auth = FALSE;
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
const gchar *method_name; const gchar *method_name;
@ -1664,43 +1777,16 @@ action_authorization_handler (GDBusInterfaceSkeleton *interface,
if (g_str_equal (method_name, "Claim")) { if (g_str_equal (method_name, "Claim")) {
needs_user_auth = TRUE; needs_user_auth = TRUE;
required_state = STATE_UNCLAIMED;
required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY;
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "DeleteEnrolledFingers")) { } else if (g_str_equal (method_name, "DeleteEnrolledFingers")) {
needs_user_auth = TRUE; needs_user_auth = TRUE;
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "DeleteEnrolledFingers2")) {
required_state = STATE_CLAIMED;
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "EnrollStart")) {
required_state = STATE_CLAIMED;
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "EnrollStop")) {
required_state = STATE_CLAIMED;
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "ListEnrolledFingers")) { } else if (g_str_equal (method_name, "ListEnrolledFingers")) {
needs_user_auth = TRUE; needs_user_auth = TRUE;
required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY;
} else if (g_str_equal (method_name, "Release")) {
required_state = STATE_CLAIMED;
required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY;
required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL;
} else if (g_str_equal (method_name, "VerifyStart")) {
required_state = STATE_CLAIMED;
required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY;
} else if (g_str_equal (method_name, "VerifyStop")) {
required_state = STATE_CLAIMED;
required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY;
} else {
g_assert_not_reached ();
} }
/* This is just a quick check in order to avoid authentication if /* This is just a quick check in order to avoid authentication if
* the user cannot make the call at this time anyway. * the user cannot make the call at this time anyway.
* The method handler itself is required to check again! */ * The method handler itself is required to check again! */
if (!_fprint_device_check_claimed (rdev, invocation, required_state, if (!_fprint_device_check_claimed (rdev, invocation, &error)) {
&error)) {
return handle_unauthorized_access (rdev, invocation, error); return handle_unauthorized_access (rdev, invocation, error);
} }
@ -1709,6 +1795,8 @@ action_authorization_handler (GDBusInterfaceSkeleton *interface,
return handle_unauthorized_access (rdev, invocation, error); return handle_unauthorized_access (rdev, invocation, error);
} }
required_perms = get_permissions_for_invocation (invocation);
/* This may possibly block the invocation till the user has not /* This may possibly block the invocation till the user has not
* provided an authentication method, so other calls could arrive */ * provided an authentication method, so other calls could arrive */
if (!fprint_device_check_polkit_for_permissions (rdev, invocation, if (!fprint_device_check_polkit_for_permissions (rdev, invocation,

View File

@ -251,7 +251,7 @@ static GSList *scan_dev_storedir(char *devpath,
continue; continue;
} }
list = g_slist_prepend(list, GINT_TO_POINTER(val)); list = g_slist_prepend (list, GUINT_TO_POINTER (val));
} }
g_dir_close(dir); g_dir_close(dir);

View File

@ -53,11 +53,16 @@ typedef enum {
FPRINT_ERROR_NO_SUCH_DEVICE, /*< nick=net.reactivated.Fprint.Error.NoSuchDevice >*/ FPRINT_ERROR_NO_SUCH_DEVICE, /*< nick=net.reactivated.Fprint.Error.NoSuchDevice >*/
} FprintError; } FprintError;
/* Enum of possible permissions, orders and nick matter here:
- The order controls the priority of a required permission when various are
accepted: the lowest the value, the more priorty it has.
- Nick must match the relative polkit rule.
*/
typedef enum { typedef enum {
FPRINT_DEVICE_PERMISSION_NONE = 0, FPRINT_DEVICE_PERMISSION_NONE = 0,
FPRINT_DEVICE_PERMISSION_ENROLL = (1 << 0), /*< nick=net.reactivated.fprint.device.enroll >*/ FPRINT_DEVICE_PERMISSION_VERIFY = (1 << 0), /*< nick=net.reactivated.fprint.device.verify >*/
FPRINT_DEVICE_PERMISSION_SETUSERNAME = (1 << 1), /*< nick=net.reactivated.fprint.device.setusername >*/ FPRINT_DEVICE_PERMISSION_ENROLL = (1 << 1), /*< nick=net.reactivated.fprint.device.enroll >*/
FPRINT_DEVICE_PERMISSION_VERIFY = (1 << 2), /*< nick=net.reactivated.fprint.device.verify >*/ FPRINT_DEVICE_PERMISSION_SETUSERNAME = (1 << 2), /*< nick=net.reactivated.fprint.device.setusername >*/
} FprintDevicePermission; } FprintDevicePermission;
/* Manager */ /* Manager */

View File

@ -14,7 +14,8 @@ foreach interface_name: dbus_interfaces
) )
endforeach endforeach
fprintd_dbus_sources = gnome.gdbus_codegen('fprintd-dbus', # NOTE: We should pass "--glib-min-required 2.64" but cannot
fprintd_dbus_sources_base = gnome.gdbus_codegen('fprintd-dbus',
sources: dbus_interfaces_files, sources: dbus_interfaces_files,
autocleanup: 'all', autocleanup: 'all',
interface_prefix: 'net.reactivated.Fprint.', interface_prefix: 'net.reactivated.Fprint.',
@ -22,6 +23,23 @@ fprintd_dbus_sources = gnome.gdbus_codegen('fprintd-dbus',
object_manager: true, object_manager: true,
) )
# FIXME: remove this and just use fprintd_dbus_sources when we're on glib 2.64
fprintd_dbus_sources = [
fprintd_dbus_sources_base[1] # header file
]
fprintd_dbus_sources += custom_target('fprintd-dbus-interactive',
input: fprintd_dbus_sources_base[0], # c file,
output: 'fprintd-dbus-interactive.c',
command: [
find_program('patch'),
'-p1',
'--merge',
'@INPUT@',
files('dbus-interactive-auth.patch'),
'-o', '@OUTPUT@',
])
fprintd_enum_files = gnome.mkenums_simple('fprintd-enums', fprintd_enum_files = gnome.mkenums_simple('fprintd-enums',
sources: 'fprintd.h', sources: 'fprintd.h',
) )

View File

@ -18,6 +18,8 @@ __email__ = 'hadess@hadess.net'
__copyright__ = '(c) 2020 Red Hat Inc.' __copyright__ = '(c) 2020 Red Hat Inc.'
__license__ = 'LGPL 3+' __license__ = 'LGPL 3+'
import sys
from gi.repository import GLib
import dbus import dbus
import asyncio import asyncio
@ -213,9 +215,22 @@ def can_verify_finger(device, finger_name):
return True return True
return False return False
async def send_verify_script(device, script): def glib_sleep(timeout):
for [result, done, timeout] in device.verify_script: waiting = True
await asyncio.sleep(timeout)
def done_waiting():
nonlocal waiting
waiting = False
GLib.timeout_add(timeout, done_waiting)
while (waiting):
GLib.main_context_default().iteration(True)
def device_run_script(device, result, done):
if result == 'MOCK: quit':
sys.exit(0)
# Emit signal
device.EmitSignal(DEVICE_IFACE, 'VerifyStatus', 'sb', [ device.EmitSignal(DEVICE_IFACE, 'VerifyStatus', 'sb', [
result, result,
done done
@ -249,13 +264,44 @@ def VerifyStart(device, finger_name):
if finger_name == 'any' and not device.has_identification: if finger_name == 'any' and not device.has_identification:
finger_name = device.fingers[device.claimed_user][0] finger_name = device.fingers[device.claimed_user][0]
device.selected_finger = finger_name device.selected_finger = finger_name
device.EmitSignal(DEVICE_IFACE, 'VerifyFingerSelected', 's', [ # Needs to happen after method return
finger_name GLib.idle_add(device.EmitSignal,
DEVICE_IFACE, 'VerifyFingerSelected', 's', [
device.selected_finger
]) ])
if device.verify_script is not None and len(device.verify_script) > 0: error = None
asyncio.run(send_verify_script(device, device.verify_script)) base_delay = 0
while device.verify_script is not None and len(device.verify_script) > 0:
result, done, timeout = device.verify_script.pop(0)
# We stop when "timeout >= 0 and done"
if result == 'MOCK: no-prints':
# Special case to change return value of DBus call, ignores timeout
error = dbus.exceptions.DBusException(
'No enrolled prints for user \'%s\'' % device.claimed_user,
name='net.reactivated.Fprint.Error.NoEnrolledPrints')
elif timeout < 0:
# Negative timeouts mean emitting before the DBus call returns
device_run_script(device, result, done)
glib_sleep(-timeout)
else:
# Positive or zero means emitting afterwards the given timeout
base_delay += timeout
GLib.timeout_add(base_delay,
device_run_script,
device,
result,
done)
# Stop processing commands when the done flag is set
if done:
break
if error:
raise error
@dbus.service.method(DEVICE_MOCK_IFACE, @dbus.service.method(DEVICE_MOCK_IFACE,
in_signature='sb', out_signature='') in_signature='sb', out_signature='')
@ -383,3 +429,11 @@ def SetVerifyScript(device, script):
''' '''
device.verify_script = script device.verify_script = script
@dbus.service.method(DEVICE_MOCK_IFACE,
in_signature='s', out_signature='')
def SetClaimed(device, user):
if user == '':
device.claimed_user = None
else:
device.claimed_user = user

View File

@ -493,32 +493,55 @@ class FPrintdManagerPreStartTests(FPrintdTest):
self.manager.GetDefaultDevice() self.manager.GetDefaultDevice()
def test_manager_get_devices_on_name_appeared(self): def test_manager_get_devices_on_name_appeared(self):
self._appeared_res = [] self._appeared_name = None
def on_name_appeared(connection, name, name_owner): def on_name_appeared(connection, name, name_owner):
self._appeared_res.append(connection.call_sync('net.reactivated.Fprint', self._appeared_name = name
'/net/reactivated/Fprint/Manager',
'net.reactivated.Fprint.Manager', def on_name_vanished(connection, name):
'GetDefaultDevice', None, None, self._appeared_name = 'NAME_VANISHED'
Gio.DBusCallFlags.NO_AUTO_START, 500, None))
id = Gio.bus_watch_name_on_connection(self.dbus, id = Gio.bus_watch_name_on_connection(self.dbus,
'net.reactivated.Fprint', Gio.BusNameWatcherFlags.NONE, 'net.reactivated.Fprint', Gio.BusNameWatcherFlags.NONE,
on_name_appeared, None) on_name_appeared, on_name_vanished)
self.addCleanup(Gio.bus_unwatch_name, id)
self.daemon_start() self.daemon_start()
while not self._appeared_res: while not self._appeared_name:
ctx.iteration(True) ctx.iteration(True)
self.assertIsNotNone(self._appeared_res[0]) self.assertEqual(self._appeared_name, 'net.reactivated.Fprint')
dev_path = self._appeared_res[0][0]
self.assertTrue(dev_path.startswith('/net/reactivated/Fprint/Device/'))
Gio.bus_unwatch_name(id) try:
appeared_device = self.dbus.call_sync(
'net.reactivated.Fprint',
'/net/reactivated/Fprint/Manager',
'net.reactivated.Fprint.Manager',
'GetDefaultDevice', None, None,
Gio.DBusCallFlags.NO_AUTO_START, 500, None)
except GLib.GError as e:
if 'net.reactivated.Fprint.Error.NoSuchDevice' in e.message:
self.skipTest("Need virtual_image device to run the test")
raise(e)
self.assertIsNotNone(appeared_device)
[dev_path] = appeared_device
self.assertTrue(dev_path.startswith('/net/reactivated/Fprint/Device/'))
class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest): class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest):
def test_name_property(self):
self.assertEqual(self.device.get_cached_property('name').unpack(),
'Virtual image device for debugging')
def test_enroll_stages_property(self):
self.assertEqual(self.device.get_cached_property('num-enroll-stages').unpack(), 5)
def test_scan_type(self):
self.assertEqual(self.device.get_cached_property('scan-type').unpack(),
'swipe')
def test_allowed_claim_release_enroll(self): def test_allowed_claim_release_enroll(self):
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername', self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername',
'net.reactivated.fprint.device.enroll']) 'net.reactivated.fprint.device.enroll'])
@ -580,6 +603,34 @@ class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest):
with self.assertFprintError('PermissionDenied'): with self.assertFprintError('PermissionDenied'):
self.device.Claim('(s)', 'testuser') self.device.Claim('(s)', 'testuser')
def test_unallowed_enroll_with_verify_claim(self):
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify'])
self.device.Claim('(s)', '')
with self.assertFprintError('PermissionDenied'):
self.enroll_image('whorl', finger='right-thumb')
def test_unallowed_delete_with_verify_claim(self):
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify'])
self.device.Claim('(s)', '')
with self.assertFprintError('PermissionDenied'):
self.device.DeleteEnrolledFingers('(s)', 'testuser')
def test_unallowed_delete2_with_verify_claim(self):
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify'])
self.device.Claim('(s)', '')
with self.assertFprintError('PermissionDenied'):
self.device.DeleteEnrolledFingers2()
def test_unallowed_verify_with_enroll_claim(self):
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll'])
self.device.Claim('(s)', '')
with self.assertFprintError('PermissionDenied'):
self.device.VerifyStart('(s)', 'any')
def test_unallowed_claim_current_user(self): def test_unallowed_claim_current_user(self):
self._polkitd_obj.SetAllowed(['']) self._polkitd_obj.SetAllowed([''])
@ -597,20 +648,11 @@ class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest):
self.device.Release() self.device.Release()
def test_unallowed_release(self): def test_always_allowed_release(self):
self.device.Claim('(s)', 'testuser') self.device.Claim('(s)', 'testuser')
self._polkitd_obj.SetAllowed(['']) self._polkitd_obj.SetAllowed([''])
with self.assertFprintError('PermissionDenied'):
self.device.Release()
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername'])
with self.assertFprintError('PermissionDenied'):
self.device.Release()
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll'])
self.device.Release() self.device.Release()
def test_unclaimed_release(self): def test_unclaimed_release(self):
@ -780,6 +822,7 @@ class FPrintdVirtualDeviceClaimedTest(FPrintdVirtualDeviceBaseTest):
self.wait_for_result() self.wait_for_result()
self.assertTrue(self._verify_stopped) self.assertTrue(self._verify_stopped)
self.assertEqual(self._last_result, 'verify-match') self.assertEqual(self._last_result, 'verify-match')
self.device.VerifyStop()
self.assertEqual(self.device.ListEnrolledFingers('(s)', 'testuser'), ['right-index-finger']) self.assertEqual(self.device.ListEnrolledFingers('(s)', 'testuser'), ['right-index-finger'])
@ -959,15 +1002,11 @@ class FPrintdVirtualDeviceClaimedTest(FPrintdVirtualDeviceBaseTest):
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll']) self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll'])
self.enroll_image('whorl') self.enroll_image('whorl')
def test_unallowed_enroll_stop(self): def test_always_allowed_enroll_stop(self):
self.device.EnrollStart('(s)', 'right-index-finger') self.device.EnrollStart('(s)', 'right-index-finger')
self._polkitd_obj.SetAllowed(['']) self._polkitd_obj.SetAllowed([''])
with self.assertFprintError('PermissionDenied'):
self.device.EnrollStop()
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll'])
self.device.EnrollStop() self.device.EnrollStop()
def test_unallowed_verify_start(self): def test_unallowed_verify_start(self):
@ -976,15 +1015,11 @@ class FPrintdVirtualDeviceClaimedTest(FPrintdVirtualDeviceBaseTest):
with self.assertFprintError('PermissionDenied'): with self.assertFprintError('PermissionDenied'):
self.device.VerifyStart('(s)', 'any') self.device.VerifyStart('(s)', 'any')
def test_unallowed_verify_stop(self): def test_always_allowed_verify_stop(self):
self.enroll_image('whorl') self.enroll_image('whorl')
self.device.VerifyStart('(s)', 'any') self.device.VerifyStart('(s)', 'any')
self._polkitd_obj.SetAllowed(['']) self._polkitd_obj.SetAllowed([''])
with self.assertFprintError('PermissionDenied'):
self.device.VerifyStop()
self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify'])
self.device.VerifyStop() self.device.VerifyStop()
def test_list_enrolled_fingers_current_user(self): def test_list_enrolled_fingers_current_user(self):

148
tests/output_checker.py Normal file
View File

@ -0,0 +1,148 @@
#! /usr/bin/env python3
# Copyright © 2020, RedHat Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
# Authors:
# Benjamin Berg <bberg@redhat.com>
import os
import sys
import fcntl
import io
import re
import time
import threading
class OutputChecker(object):
def __init__(self, out=sys.stdout):
self._output = out
self._pipe_fd_r, self._pipe_fd_w = os.pipe()
self._partial_buf = b''
self._lines_sem = threading.Semaphore()
self._lines = []
self._reader_io = io.StringIO()
# Just to be sure, shouldn't be a problem even if we didn't set it
fcntl.fcntl(self._pipe_fd_r, fcntl.F_SETFL,
fcntl.fcntl(self._pipe_fd_r, fcntl.F_GETFL) | os.O_CLOEXEC)
fcntl.fcntl(self._pipe_fd_w, fcntl.F_SETFL,
fcntl.fcntl(self._pipe_fd_w, fcntl.F_GETFL) | os.O_CLOEXEC)
# Start copier thread
self._thread = threading.Thread(target=self._copy)
self._thread.start()
def _copy(self):
while True:
r = os.read(self._pipe_fd_r, 1024)
if not r:
return
l = r.split(b'\n')
l[0] = self._partial_buf + l[0]
self._lines.extend(l[:-1])
self._partial_buf = l[-1]
self._lines_sem.release()
os.write(self._output.fileno(), r)
def check_line_re(self, needle_re, timeout=0, failmsg=None):
deadline = time.time() + timeout
if isinstance(needle_re, str):
needle_re = needle_re.encode('ascii')
r = re.compile(needle_re)
ret = []
while True:
try:
l = self._lines.pop(0)
except IndexError:
# Check if should wake up
if not self._lines_sem.acquire(timeout = deadline - time.time()):
if failmsg:
raise AssertionError(failmsg)
else:
raise AssertionError('Timed out waiting for needle %s (timeout: %0.2f)' % (str(needle_re), timeout))
continue
ret.append(l)
if r.search(l):
return ret
def check_line(self, needle, timeout=0, failmsg=None):
if isinstance(needle, str):
needle = needle.encode('ascii')
needle_re = re.escape(needle)
return self.check_line_re(needle_re, timeout=timeout, failmsg=failmsg)
def check_no_line_re(self, needle_re, wait=0, failmsg=None):
deadline = time.time() + wait
if isinstance(needle_re, str):
needle_re = needle_re.encode('ascii')
r = re.compile(needle_re)
ret = []
while True:
try:
l = self._lines.pop(0)
except IndexError:
# Check if should wake up
if not self._lines_sem.acquire(timeout = deadline - time.time()):
# Timed out, so everything is good
break
continue
ret.append(l)
if r.search(l):
if failmsg:
raise AssertionError(failmsg)
else:
raise AssertionError('Found needle %s but shouldn\'t have been there (timeout: %0.2f)' % (str(needle_re), timeout))
return ret
def check_no_line(self, needle, wait=0, failmsg=None):
if isinstance(needle, str):
needle = needle.encode('ascii')
needle_re = re.escape(needle)
return self.check_no_line_re(needle_re, wait=wait, failmsg=failmsg)
def clear(self):
ret = self._lines
self._lines = []
return ret
def assert_closed(self, timeout=1):
self._thread.join(timeout)
if self._thread.is_alive() != False:
raise AssertionError("OutputCheck: Write side has not been closed yet!")
@property
def fd(self):
return self._pipe_fd_w
def writer_attached(self):
os.close(self._pipe_fd_w)
self._pipe_fd_w = -1

View File

@ -17,7 +17,6 @@ import sys
import subprocess import subprocess
import dbus import dbus
import dbusmock import dbusmock
import fcntl
import glob import glob
import os import os
import shutil import shutil
@ -75,10 +74,7 @@ class TestPamFprintd(dbusmock.DBusTestCase):
def setUp(self): def setUp(self):
(self.p_mock, self.obj_fprintd_manager) = self.spawn_server_template( (self.p_mock, self.obj_fprintd_manager) = self.spawn_server_template(
self.template_name, {}, stdout=subprocess.PIPE) self.template_name, {})
# set log to nonblocking
flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
self.obj_fprintd_mock = dbus.Interface(self.obj_fprintd_manager, 'net.reactivated.Fprint.Manager.Mock') self.obj_fprintd_mock = dbus.Interface(self.obj_fprintd_manager, 'net.reactivated.Fprint.Manager.Mock')
def tearDown(self): def tearDown(self):
@ -143,6 +139,66 @@ class TestPamFprintd(dbusmock.DBusTestCase):
self.assertRegex(res.info[0], r'Swipe your left little finger across the fingerprint reader') self.assertRegex(res.info[0], r'Swipe your left little finger across the fingerprint reader')
self.assertEqual(len(res.errors), 0) self.assertEqual(len(res.errors), 0)
def test_pam_fprintd_no_fingers(self):
self.setup_device()
self.device_mock.SetEnrolledFingers('toto', dbus.Array(set([]), signature='s'))
script = [
( 'verify-match', True, 1 )
]
self.device_mock.SetVerifyScript(script)
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_AUTHINFO_UNAVAIL)
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
def test_pam_fprintd_no_fingers_while_verifying(self):
self.setup_device()
script = [
( 'MOCK: no-prints', True, 1),
( 'verify-match', True, 1 )
]
self.device_mock.SetVerifyScript(script)
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_USER_UNKNOWN)
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
def test_pam_fprintd_blocks_unexpected_auth(self):
self.setup_device()
script = [
( 'verify-match', True, -500 ), # This one is sent before VerifyStart has completed
( 'verify-no-match', True, 1 ),
( 'verify-no-match', True, 1 ),
( 'verify-no-match', True, 1 ),
]
self.device_mock.SetVerifyScript(script)
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_MAXTRIES)
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
self.assertRegex(res.info[0], r'Swipe your left little finger across the fingerprint reader')
self.assertEqual(len(res.errors), 3)
self.assertRegex(res.errors[0], r'Failed to match fingerprint')
self.assertRegex(res.errors[0], r'Failed to match fingerprint')
self.assertRegex(res.errors[0], r'Failed to match fingerprint')
def test_pam_fprintd_blocks_unexpected_auth2(self):
self.setup_device()
script = [
( 'verify-no-match', True, 1 ),
( 'verify-match', True, -500 ), # This one is sent before VerifyStart has completed
( 'verify-no-match', True, 1 ),
( 'verify-no-match', True, 1 ),
]
self.device_mock.SetVerifyScript(script)
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_MAXTRIES)
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
self.assertRegex(res.info[0], r'Swipe your left little finger across the fingerprint reader')
self.assertEqual(len(res.errors), 3)
self.assertRegex(res.errors[0], r'Failed to match fingerprint')
self.assertRegex(res.errors[0], r'Failed to match fingerprint')
self.assertRegex(res.errors[0], r'Failed to match fingerprint')
def test_pam_fprintd_dual_reader_auth(self): def test_pam_fprintd_dual_reader_auth(self):
device_path = self.obj_fprintd_mock.AddDevice('FDO Sandpaper Reader', 3, 'press') device_path = self.obj_fprintd_mock.AddDevice('FDO Sandpaper Reader', 3, 'press')
sandpaper_device_mock = self.dbus_con.get_object('net.reactivated.Fprint', device_path) sandpaper_device_mock = self.dbus_con.get_object('net.reactivated.Fprint', device_path)
@ -196,6 +252,20 @@ class TestPamFprintd(dbusmock.DBusTestCase):
self.assertRegex(res.errors[1], r'Failed to match fingerprint') self.assertRegex(res.errors[1], r'Failed to match fingerprint')
self.assertRegex(res.errors[2], r'Failed to match fingerprint') self.assertRegex(res.errors[2], r'Failed to match fingerprint')
def test_pam_already_claimed(self):
self.setup_device()
script = [
( 'verify-match', True, 2 )
]
self.device_mock.SetVerifyScript(script)
self.device_mock.SetClaimed('toto')
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_AUTHINFO_UNAVAIL)
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
self.assertEqual(len(res.info), 0)
self.assertEqual(len(res.errors), 0)
def test_pam_timeout(self): def test_pam_timeout(self):
self.setup_device() self.setup_device()
@ -203,6 +273,19 @@ class TestPamFprintd(dbusmock.DBusTestCase):
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ]) res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
self.assertRegex(res.info[1], r'Verification timed out') self.assertRegex(res.info[1], r'Verification timed out')
def test_pam_notices_fprintd_disappearing(self):
self.setup_device()
script = [
( 'MOCK: quit', True, 0 ),
]
self.device_mock.SetVerifyScript(script)
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_AUTHINFO_UNAVAIL)
res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ])
self.assertEqual(len(res.errors), 0)
self.assertEqual(len(res.info), 0)
if __name__ == '__main__': if __name__ == '__main__':
if 'PAM_WRAPPER_SERVICE_DIR' not in os.environ: if 'PAM_WRAPPER_SERVICE_DIR' not in os.environ:
print('Cannot run test without environment set correctly, run "meson test" instead') print('Cannot run test without environment set correctly, run "meson test" instead')

View File

@ -18,9 +18,9 @@ import subprocess
import dbus import dbus
import dbus.mainloop.glib import dbus.mainloop.glib
import dbusmock import dbusmock
import fcntl
import os import os
import time import time
from output_checker import OutputChecker
VALID_FINGER_NAMES = [ VALID_FINGER_NAMES = [
@ -77,10 +77,8 @@ class TestFprintdUtilsBase(dbusmock.DBusTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
(self.p_mock, self.obj_fprintd_manager) = self.spawn_server_template( (self.p_mock, self.obj_fprintd_manager) = self.spawn_server_template(
self.template_name, {}, stdout=subprocess.PIPE) self.template_name, {})
# set log to nonblocking # set log to nonblocking
flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
self.obj_fprintd_mock = dbus.Interface(self.obj_fprintd_manager, 'net.reactivated.Fprint.Manager.Mock') self.obj_fprintd_mock = dbus.Interface(self.obj_fprintd_manager, 'net.reactivated.Fprint.Manager.Mock')
def tearDown(self): def tearDown(self):
@ -102,22 +100,21 @@ class TestFprintdUtilsBase(dbusmock.DBusTestCase):
def start_utility_process(self, utility_name, args=[], sleep=True): def start_utility_process(self, utility_name, args=[], sleep=True):
utility = [ os.path.join(self.tools_prefix, 'fprintd-{}'.format(utility_name)) ] utility = [ os.path.join(self.tools_prefix, 'fprintd-{}'.format(utility_name)) ]
output = OutputChecker()
process = subprocess.Popen(self.wrapper_args + utility + args, process = subprocess.Popen(self.wrapper_args + utility + args,
stdout=subprocess.PIPE, stdout=output.fd,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT)
universal_newlines=True) output.writer_attached()
flags = fcntl.fcntl(process.stdout, fcntl.F_GETFL)
fcntl.fcntl(process.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
self.addCleanup(output.assert_closed)
self.addCleanup(self.try_stop_utility_process, process) self.addCleanup(self.try_stop_utility_process, process)
if sleep: if sleep:
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
return process return process, output
def stop_utility_process(self, process): def stop_utility_process(self, process):
print(process.stdout.read())
process.terminate() process.terminate()
process.wait() process.wait()
@ -127,17 +124,12 @@ class TestFprintdUtilsBase(dbusmock.DBusTestCase):
except: except:
pass pass
def get_process_output(self, process):
out = process.stdout.read()
self.addCleanup(print, out)
return out
def run_utility_process(self, utility_name, args=[], sleep=True, timeout=None): def run_utility_process(self, utility_name, args=[], sleep=True, timeout=None):
proc = self.start_utility_process(utility_name, args=args, sleep=sleep) proc, output = self.start_utility_process(utility_name, args=args, sleep=sleep)
ret = proc.wait(timeout=timeout if timeout is not None else self.sleep_time * 4) ret = proc.wait(timeout=timeout if timeout is not None else self.sleep_time * 4)
self.assertLessEqual(ret, 128) self.assertLessEqual(ret, 128)
return self.get_process_output(proc), ret return b''.join(output.clear()), ret
class TestFprintdUtils(TestFprintdUtilsBase): class TestFprintdUtils(TestFprintdUtilsBase):
@ -146,65 +138,62 @@ class TestFprintdUtils(TestFprintdUtilsBase):
self.setup_device() self.setup_device()
def test_fprintd_enroll(self): def test_fprintd_enroll(self):
process = self.start_utility_process('enroll', ['-f', 'right-index-finger', 'toto']) process, out = self.start_utility_process('enroll', ['-f', 'right-index-finger', 'toto'])
out = self.get_process_output(process) out.check_line(rb'right-index-finger', 0)
self.assertRegex(out, r'right-index-finger')
self.device_mock.EmitEnrollStatus('enroll-completed', True) self.device_mock.EmitEnrollStatus('enroll-completed', True)
time.sleep(self.sleep_time)
out = self.get_process_output(process) out.check_line(rb'Enroll result: enroll-completed', self.sleep_time)
self.assertRegex(out, 'Enroll result: enroll-completed')
def test_fprintd_list(self): def test_fprintd_list(self):
# Rick has no fingerprints enrolled # Rick has no fingerprints enrolled
out, ret = self.run_utility_process('list', ['rick']) out, ret = self.run_utility_process('list', ['rick'])
self.assertRegex(out, r'has no fingers enrolled for') self.assertRegex(out, rb'has no fingers enrolled for')
self.assertEqual(ret, 0) self.assertEqual(ret, 0)
# Toto does # Toto does
out, ret = self.run_utility_process('list', ['toto']) out, ret = self.run_utility_process('list', ['toto'])
self.assertRegex(out, r'right-little-finger') self.assertRegex(out, rb'right-little-finger')
self.assertEqual(ret, 0) self.assertEqual(ret, 0)
def test_fprintd_delete(self): def test_fprintd_delete(self):
# Has fingerprints enrolled # Has fingerprints enrolled
out, ret = self.run_utility_process('list', ['toto']) out, ret = self.run_utility_process('list', ['toto'])
self.assertRegex(out, r'left-little-finger') self.assertRegex(out, rb'left-little-finger')
self.assertEqual(ret, 0) self.assertEqual(ret, 0)
self.assertRegex(out, r'right-little-finger') self.assertRegex(out, rb'right-little-finger')
# Delete fingerprints # Delete fingerprints
out, ret = self.run_utility_process('delete', ['toto']) out, ret = self.run_utility_process('delete', ['toto'])
self.assertRegex(out, r'Fingerprints deleted') self.assertRegex(out, rb'Fingerprints deleted')
self.assertEqual(ret, 0) self.assertEqual(ret, 0)
# Doesn't have fingerprints # Doesn't have fingerprints
out, ret = self.run_utility_process('list', ['toto']) out, ret = self.run_utility_process('list', ['toto'])
self.assertRegex(out, r'has no fingers enrolled for') self.assertRegex(out, rb'has no fingers enrolled for')
self.assertEqual(ret, 0) self.assertEqual(ret, 0)
class TestFprintdUtilsNoDeviceTests(TestFprintdUtilsBase): class TestFprintdUtilsNoDeviceTests(TestFprintdUtilsBase):
def test_fprintd_enroll(self): def test_fprintd_enroll(self):
out, ret = self.run_utility_process('enroll', ['toto']) out, ret = self.run_utility_process('enroll', ['toto'])
self.assertIn('No devices available', out) self.assertIn(b'No devices available', out)
self.assertEqual(ret, 1) self.assertEqual(ret, 1)
def test_fprintd_list(self): def test_fprintd_list(self):
out, ret = self.run_utility_process('list', ['toto']) out, ret = self.run_utility_process('list', ['toto'])
self.assertIn('No devices available', out) self.assertIn(b'No devices available', out)
self.assertEqual(ret, 1) self.assertEqual(ret, 1)
def test_fprintd_delete(self): def test_fprintd_delete(self):
out, ret = self.run_utility_process('delete', ['toto']) out, ret = self.run_utility_process('delete', ['toto'])
self.assertIn('No devices available', out) self.assertIn(b'No devices available', out)
self.assertEqual(ret, 1) self.assertEqual(ret, 1)
def test_fprintd_verify(self): def test_fprintd_verify(self):
out, ret = self.run_utility_process('verify', ['toto']) out, ret = self.run_utility_process('verify', ['toto'])
self.assertIn('No devices available', out) self.assertIn(b'No devices available', out)
self.assertEqual(ret, 1) self.assertEqual(ret, 1)
@ -213,24 +202,20 @@ class TestFprintdUtilsVerify(TestFprintdUtilsBase):
super().setUp() super().setUp()
self.setup_device() self.setup_device()
def start_verify_process(self, user='toto', finger=None, checkEnrolled=True): def start_verify_process(self, user='toto', finger=None, nowait=False):
args = [user] args = [user]
if finger: if finger:
args += ['-f', finger] args += ['-f', finger]
self.process = self.start_utility_process('verify', args) self.process, self.output = self.start_utility_process('verify', args)
out = self.get_process_output(self.process) if nowait:
return
self.assertNotRegex(out, r'Device already in use by [A-z]+') preamble = self.output.check_line(b'Verify started!')
self.assertNotIn('Verify result:', out)
if checkEnrolled and finger: out = b''.join(preamble)
self.assertNotIn('''Finger '{}' not enrolled for user {}'''.format(
finger, user), out)
if checkEnrolled: self.assertNotIn(b'Verify result:', out)
for f in self.enrolled_fingers:
self.assertIn(f, out)
if finger: if finger:
expected_finger = finger expected_finger = finger
@ -239,9 +224,8 @@ class TestFprintdUtilsVerify(TestFprintdUtilsBase):
self.assertEqual(self.device_mock.GetSelectedFinger(), expected_finger) self.assertEqual(self.device_mock.GetSelectedFinger(), expected_finger)
def assertVerifyMatch(self, match): def assertVerifyMatch(self, match):
self.assertIn('Verify result: {} (done)'.format( self.output.check_line(r'Verify result: {} (done)'.format(
'verify-match' if match else 'verify-no-match'), 'verify-match' if match else 'verify-no-match'))
self.get_process_output(self.process))
def test_fprintd_verify(self): def test_fprintd_verify(self):
self.start_verify_process() self.start_verify_process()
@ -280,14 +264,16 @@ class TestFprintdUtilsVerify(TestFprintdUtilsBase):
def test_fprintd_verify_not_enrolled_fingers(self): def test_fprintd_verify_not_enrolled_fingers(self):
for finger in [f for f in VALID_FINGER_NAMES if f not in self.enrolled_fingers]: for finger in [f for f in VALID_FINGER_NAMES if f not in self.enrolled_fingers]:
self.start_verify_process(finger=finger, nowait=True)
regex = r'Finger \'{}\' not enrolled'.format(finger) regex = r'Finger \'{}\' not enrolled'.format(finger)
with self.assertRaisesRegex(AssertionError, regex): self.output.check_line_re(regex, timeout=self.sleep_time)
self.start_verify_process(finger=finger)
self.device_mock.Release() self.device_mock.Release()
def test_fprintd_verify_no_enrolled_fingers(self): def test_fprintd_verify_no_enrolled_fingers(self):
self.set_enrolled_fingers([]) self.set_enrolled_fingers([])
self.start_verify_process() self.start_verify_process(nowait=True)
self.output.check_line(b'No fingers enrolled for this device.', timeout=self.sleep_time)
self.assertEqual(self.process.poll(), 1) self.assertEqual(self.process.poll(), 1)
def test_fprintd_list_all_fingers(self): def test_fprintd_list_all_fingers(self):
@ -299,16 +285,17 @@ class TestFprintdUtilsVerify(TestFprintdUtilsBase):
( 'verify-match', True, 2 ) ( 'verify-match', True, 2 )
] ]
self.device_mock.SetVerifyScript(script) self.device_mock.SetVerifyScript(script)
time.sleep(2)
self.start_verify_process() self.start_verify_process()
time.sleep(self.sleep_time * 4) time.sleep(2 + self.sleep_time)
self.assertVerifyMatch(True) self.assertVerifyMatch(True)
def test_fprintd_multiple_verify_fails(self): def test_fprintd_multiple_verify_fails(self):
self.start_verify_process() self.start_verify_process()
with self.assertRaisesRegex(AssertionError, r'Device already in use'): self.start_verify_process(nowait=True)
self.start_verify_process() self.output.check_line_re(rb'Device already in use by [A-z]+', timeout=self.sleep_time)
if __name__ == '__main__': if __name__ == '__main__':
# avoid writing to stderr # avoid writing to stderr

View File

@ -120,12 +120,18 @@ static void find_finger (FprintDBusDevice *dev, const char *username)
} }
} }
struct VerifyState {
GError *error;
gboolean started;
gboolean completed;
};
static void verify_result(GObject *object, const char *result, gboolean done, void *user_data) static void verify_result(GObject *object, const char *result, gboolean done, void *user_data)
{ {
gboolean *verify_completed = user_data; struct VerifyState *verify_state = user_data;
g_print("Verify result: %s (%s)\n", result, done ? "done" : "not done"); g_print("Verify result: %s (%s)\n", result, done ? "done" : "not done");
if (done != FALSE) if (done != FALSE)
*verify_completed = TRUE; verify_state->completed = TRUE;
} }
static void verify_finger_selected(GObject *object, const char *name, void *user_data) static void verify_finger_selected(GObject *object, const char *name, void *user_data)
@ -133,12 +139,27 @@ static void verify_finger_selected(GObject *object, const char *name, void *user
g_print("Verifying: %s\n", name); g_print("Verifying: %s\n", name);
} }
static void verify_started_cb (GObject *obj,
GAsyncResult *res,
gpointer user_data)
{
struct VerifyState *verify_state = user_data;
if (fprint_dbus_device_call_verify_start_finish (FPRINT_DBUS_DEVICE (obj), res, &verify_state->error))
verify_state->started = TRUE;
}
static void proxy_signal_cb (GDBusProxy *proxy, static void proxy_signal_cb (GDBusProxy *proxy,
const gchar *sender_name, const gchar *sender_name,
const gchar *signal_name, const gchar *signal_name,
GVariant *parameters, GVariant *parameters,
gpointer user_data) gpointer user_data)
{ {
struct VerifyState *verify_state = user_data;
if (!verify_state->started)
return;
if (g_str_equal (signal_name, "VerifyStatus")) { if (g_str_equal (signal_name, "VerifyStatus")) {
const gchar *result; const gchar *result;
gboolean done; gboolean done;
@ -156,23 +177,44 @@ static void proxy_signal_cb (GDBusProxy *proxy,
static void do_verify (FprintDBusDevice *dev) static void do_verify (FprintDBusDevice *dev)
{ {
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
gboolean verify_completed = FALSE; struct VerifyState verify_state = { 0 };
/* This one is funny. We connect to the signal immediately to avoid
* race conditions. However, we must ignore any authentication results
* that happen before our start call returns.
* This is because the verify call itself may internally try to verify
* against fprintd (possibly using a separate account).
*
* To do so, we *must* use the async version of the verify call, as the
* sync version would cause the signals to be queued and only processed
* after it returns.
*/
g_signal_connect (dev, "g-signal", G_CALLBACK (proxy_signal_cb), g_signal_connect (dev, "g-signal", G_CALLBACK (proxy_signal_cb),
&verify_completed); &verify_state);
if (!fprint_dbus_device_call_verify_start_sync (dev, finger_name, NULL, fprint_dbus_device_call_verify_start (dev, finger_name, NULL,
&error)) { verify_started_cb,
g_print("VerifyStart failed: %s\n", error->message); &verify_state);
/* Wait for verify start while discarding any VerifyStatus signals */
while (!verify_state.started && !verify_state.error)
g_main_context_iteration(NULL, TRUE);
if (verify_state.error) {
g_print("VerifyStart failed: %s\n", verify_state.error->message);
g_clear_error (&verify_state.error);
exit (1); exit (1);
} }
g_print("Verify started!\n");
while (!verify_completed) /* VerifyStatus signals are processing, wait for completion. */
while (!verify_state.completed)
g_main_context_iteration(NULL, TRUE); g_main_context_iteration(NULL, TRUE);
g_signal_handlers_disconnect_by_func (dev, proxy_signal_cb, g_signal_handlers_disconnect_by_func (dev, proxy_signal_cb,
&verify_completed); &verify_state);
if (!fprint_dbus_device_call_verify_stop_sync (dev, NULL, &error)) { if (!fprint_dbus_device_call_verify_stop_sync (dev, NULL, &error)) {
g_print("VerifyStop failed: %s\n", error->message); g_print("VerifyStop failed: %s\n", error->message);