227 Commits

Author SHA1 Message Date
7d22a2b5b9 Release 1.90.8 2020-12-11 16:00:28 +01:00
de725a91e4 verify: Print message about verification start from callback
It seems that GLib may process multiple DBus signals in one mainloop
iteration. This could cause messages to be re-ordered, which in turn
caused a race condition in the CI that could trigger random failures.
2020-12-11 16:00:28 +01:00
18392cba54 manager: Export the object manager in /net/reactivated/Fprint
Given we're going to use an object manager it can just stay at the root
of the project, while it will be just used to manage the devices
2020-12-11 15:30:26 +01:00
783d82f359 device: Expose method name when logging authorization steps 2020-12-11 14:03:37 +00:00
c00a3375d1 device: Use standard names for local errors and remove unused one 2020-12-11 14:03:37 +00:00
5aa61adabc build: make systemd dependency optional
The systemd dependency is only used to install some systemd service
files. This can easily be made optional.
2020-12-11 15:01:24 +01:00
1fc10f15ee pam: Stop authorization if we couldn't parse signals
This really should never ever happen. If it does, don't continue but
stop instead.
2020-12-11 10:34:51 +01:00
c24badfd68 pam: Move NameOwnerChanged registration after initialization
We must ignore NameOwnerChanged that happen due to automatic startup.
The easy way to do so is to just register it only when we get to the
point that a name owner change has security implications.

While add it, change it to always log at a warning level.

Fixes: #94
2020-12-11 10:34:51 +01:00
4612c1f3ed Release 1.90.7 2020-12-09 13:16:12 +01:00
ca216a32af test_pam_fprintd: Add test verifying the case in which we've no devices 2020-12-08 21:14:24 +01:00
944493e472 pam_fprintd: Protect usage of strdup for NULL values
It's not smart as g_strdup, so need to ensure we don't use it for NULL
strings.

This is a regression caused by commit bf223662
2020-12-08 21:14:01 +01:00
34f24cbe19 ci: Move build jobs to build phase 2020-12-07 18:42:24 +01:00
9314069a88 ci: Add check-source stage where we check syntax 2020-12-07 18:42:24 +01:00
66e21eac8f .git-blame-ignore-revs: Ignore formatting commit and add hint how to use it 2020-12-07 18:42:22 +01:00
f73429f062 fprintd: Reindent the source code following uncrustify rules 2020-12-07 18:41:45 +01:00
c18ebaf9da scripts: Add uncrustify scripts for reformatting fprintd source code
We follow libfprint here, using GNOME format
2020-12-07 18:41:45 +01:00
4a80bfacec fingerprint-strings: Make the finger_str_to_msg loop clearer
Just continue earlier instead of using a long if check
2020-12-07 18:41:45 +01:00
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
267b322f6d Release 1.90.5 2020-12-01 14:22:55 +01:00
3b83240e57 tests: Fix detection of non-functional file permissions
If we rely on CI_PROJECT_NAME being set, then the test will fail in
similar environments outside of the fprintd main CI. So just add a
os.stat call afterwards to check whether the permission changes took
effect, and if not, then skip.

So, instead try to create a file and check that this fails.
2020-12-01 14:22:55 +01:00
897cbd341e tests: Skip flaky test_enroll_verify_list_delete test with old libfprint
The test can hang forever unless a fixed libfprint version is used.
2020-12-01 12:46:17 +01:00
9d0305ea05 tests: Use system utils outside of the test environment
The code to pick up the utilities from the test environment would fail
if the environment variables are not set. In that case, we can just use
the binary name and rely on PATH though.
2020-12-01 11:41:11 +01:00
3dbfdabe01 tests: Translate skips into error code 77
This makes the meson output nicer, as it will correctly display that the
test has been skipped. It only happens if all tests in the run were
skipped, but meson always does one test a time.
2020-12-01 10:04:11 +01:00
45cf63d589 tests: Skip hotplug test if "removed" property does not exist
The test requires libfprint 1.90.4 to work, otherwise it will just hang
forever.
2020-12-01 10:04:11 +01:00
29ed88a50a meson: Use warning_level feature of meson
This is nicer than adding -Wall and gives users more control.

Add -Wno-unused-parameter for now as there are lot of places where
this would need to be changed and it is reasonable in most cases.

Add -Wno-pedantic because it conflicts with
g_signal_handlers_disconnect_*
2020-12-01 09:00:55 +00:00
e301779c20 Fix integer signedness mismatches 2020-12-01 09:00:55 +00:00
be5d283a3e device: Remove unused code
The code was left over when adding the function to create the session data.
2020-12-01 09:00:55 +00:00
ebfcbdd13e pam: Use %d with errno instead of %m
Otherwise GCC warns sometimes, and it is easy enough to replace the use
of %m.
2020-12-01 09:00:55 +00:00
ec7376d7e6 meson: Fix CFLAGS use by using add_project_arguments
It seems that meson will not always apply the CFLAGS as defined through
the environment if "c_args=" is used in the default_options array for
the project() call.

Switching to add_project_arguments solves this problem.

See https://github.com/mesonbuild/meson/issues/8037
2020-12-01 09:00:55 +00:00
df568e1ce1 net.reactivated.Fprint: Explicitly allow basic D-Bus APIs 2020-11-28 20:29:57 +00:00
7ee61393ec net.reactivated.Fprint: Only allow clients to send messages with fprintd iface to us
In the way the rule is currently set it would allow clients to send
messages with the fprintd interface to any other service, while we only
allow them to be redirected to fprintd itself.

This was causing a debian linter failure [1].

[1] https://lintian.debian.org/tags/dbus-policy-without-send-destination.html
2020-11-28 20:29:57 +00:00
57ca0dc95e tests/pam: Don't run PAM tests in parallel
Given they could re-use the same pam-wrapper temporary dir, it's better
to avoid running them concurrently.
2020-11-27 19:30:08 +01:00
85ba309e9d tests: Remove pam-wrapper temporary folder on test completion
PAM wrapper creates /tmp/pam.X files during its execution (strictly as
it does not follow $TMPDIR either), however given the low number of
combinations, we may end up in re-using the same pam.* folder during
meson test, causing a failure.

As per this, remove these temporary files on tearDown so that we won't
try reusing the same folder multiple times.
2020-11-27 19:12:06 +01:00
3f2174676e device: Re-define polkit auto-pointer funcs if not defined
In case we're using an old polkit version that does not support
auto-pointers, we need to re-define such functions manually or fprintd
won't compile.

Given that polkit doesn't provide us version informations in headers we
need to get that from pkg-config
2020-11-27 18:21:29 +01:00
5e18d46971 Revert "meson: Bump polkit dependency to 0.114"
We only depend on newer polkit for auto-pointers usage, so it's not
worth to bump its dependency just for them

This reverts commit a8bd2bc25e.
2020-11-27 18:18:51 +01:00
a8bd2bc25e meson: Bump polkit dependency to 0.114
It's the first one supporting auto-pointers
2020-11-27 17:53:58 +01:00
88a627f187 Release 1.90.4 2020-11-27 14:26:12 +01:00
a8818f9bfb device: Do not disconnect g-authorize-method handler
Disconnecting the g-authorize-method handler is not really needed, as it
is a signal from the same object. This basically reverts 6eb9f263fd
(device: Disconnect authorization callback and remove clients) but keeps
the code to clear known clients in the dispose handler.

Closes: #91
2020-11-27 14:23:41 +01:00
c5a3089f89 device: Remove some debug spew
Closes: #89
2020-11-25 19:09:02 +01:00
5de1261df6 device: Re-fetch session data after synchronous operation
As the mainloop is iterated, the session-data may be modified while the
client vanished handler is running. Re-fetch the session data to avoid
seeing an old state and closing the device a second time.

See: https://gitlab.freedesktop.org/libfprint/fprintd/-/issues/87#note_706357
2020-11-25 19:09:02 +01:00
e5c82d7b96 device: Throw a critical warning if the device was not cleaned up
On finalization, the device should always be cleaned up properly (no
data associated with an action may be left). Show a critical warning if
this is not the case, as it indicates a programming error.
2020-11-25 19:09:02 +01:00
9c842e2c2f manager: Fix leak of FprintDevice reference
When adding a device a reference was leaked.
2020-11-25 19:09:02 +01:00
ec5cce083c device: Fix leaks on device finalization 2020-11-25 19:09:02 +01:00
ab8118bde2 tests: Add device removal test 2020-11-25 19:09:00 +01:00
08fb209aed manager: Fix unexport of removed devices
The correct way to unexport the object again is to unexported it on the
manager rather than on the interface skeleton. This fixes notifications
about device removal on DBus.
2020-11-25 19:08:20 +01:00
6eb9f263fd device: Disconnect authorization callback and remove clients
Add a dispose function to disconnect the authorization callback and
remove all clients (i.e. unwatch their bus names) before destroying the
hash table.
2020-11-25 19:08:20 +01:00
74d05e7996 device: Destroy auth object in finalize 2020-11-25 19:08:20 +01:00
83cd09ba2f device: Unwatch names when removing them
If a device is unplugged/destroyed while a client is using it, then we
would still end up watching the name. The vanish notification will then
access the destroyed FprintDevice object.

Fix this by unwatching the bus name when removing the client entry from
the dictionary.
2020-11-25 19:08:20 +01:00
8ed77829a7 tests: Add test for a client vanishing during claim
Note that this test only works if the virtual_image driver opens up a
window for race condition by delaying the open callback.
2020-11-25 19:05:47 +01:00
b63c76319f device: Make possible client vanished race testable using critical
The tests cannot currently parse the logs of fprintd. This means we need
to rely on fprintd aborting when a condition is hit that needs to be
tested.

This makes certain possible races when clients vanish testable.
2020-11-25 19:05:46 +01:00
fd9a86eca4 tests: Fail test if return code is less than 0
This means that the application received a signal.
2020-11-25 19:04:44 +01:00
38ba7199b7 utils: Use auto-pointers 2020-11-13 13:32:51 +01:00
827baff301 enroll: Cleanup error with autoptr 2020-11-13 13:32:51 +01:00
6a5d46c8b0 file_storage: Use autopointers 2020-11-13 13:32:51 +01:00
1ae0f4926d device: Always use auto-pointers
Remove manual memory management when possible, using new GLib facilities

In a few cases, avoid creating a temporary GError instead.
2020-11-13 13:32:31 +01:00
e1c2698807 main: Use more auto-pointers 2020-11-11 11:40:51 +01:00
b14bfd8226 device: Implement lock-free session getting scheme
Add a scheme that allows getting and referencing the current session
data while also adding a reference at the same time. This allows getting
the session and using the constant attributes from outside the main
thread without worrying about it being destroyed.

Implement the getter/setter in a safe way by marking the pointer as
invalid while we get the reference.
2020-11-11 11:35:13 +01:00
1e2f360ade device: Fix possible race condition when checking claimed state
We already check the claimed state in advance during authorization. This
makes sense, as we can avoid authorization if the API has been used
incorrectly. However, as the mainloop is running and handling other
request the claimed state might change at any point until the method
handler is actually running.

As such, check the claimed state again in each method. Doing so fixes
the possible race condition.
2020-11-10 14:45:59 +01:00
778a8540aa device.policy: Use auth-self-keep for enrollment
When user is requested for enrolling, we should ask for password as
anyone who has physical access to the machine could otherwise enroll
its own fingers, and have access to it.

Fixes #5
2020-11-10 14:45:59 +01:00
4e707f0d31 manager: Use GEnum to register DBus errors from FprintError
Given that mk_genenum already parses FprintError, add the nick metadata
to the errors so that it matches the wanted DBus error and automatically
generate the errors list.

In this way we'll have to only touch one definition to get everything
updated
2020-11-10 14:45:59 +01:00
4c78012103 device: Use meson to generate fprint device permission flags 2020-11-10 14:45:59 +01:00
e59f3cbc4f device: Use GFlags to handle permission types easily as flags
And thanks to the GEnumValues we can easily get their nick string from
them that we can pass to Polkit
2020-11-10 14:45:59 +01:00
1a860aa882 tests/fprintd: Add tests ensuring that concurrent calls to fprintd work
Simulate the case in which multiple users are trying to access a device
at the same time, verifying that the access is granted only to the one
that first completes the authorization phase and that no other client is
then allowed.
2020-11-10 14:45:59 +01:00
a183b779ec tests/fprintd: Make easier to call device methods asynchronously 2020-11-10 14:45:59 +01:00
9d6c7eb1a9 dbusmock/polkitd: Add ability to simulate call hangs or delays
Added various methods that allow to make methods to delay to return a
value, both by using timing functions and using a way to manually
stop and restart the calls.

This is mostly done using async callbacks in dbus methods
2020-11-10 14:45:59 +01:00
110c0018a2 tests/fprintd: Make possible to call gdbus client as an async process
As per this refactor the sync call we already had so that reuses the
same code.
2020-11-10 14:45:59 +01:00
5611317c72 tests: Add standalone PolkitD implementation
We need to be able to hack this to be an async daemon to perform some
multi-thread tests, so replacing default implementation with a simple
one that for now just does same of default
2020-11-10 14:45:59 +01:00
0904c8a527 tests/fprintd: Ensure we can claim and release with only the verify permission 2020-11-10 14:45:59 +01:00
a681996d1d device: Check Polkit actions asynchronously using skeleton authorize method
GDBus generated interface skeletons support natively an authorization
method that allows to filter calls before we get into the method
callback and that gets called into the call thread, before we go back
to main thread.

As per this, we can move all the polkit and other authorization checks
into this callback so that method handlers are now just assuming they're
the right to perform the requested operation.

As per the fact we'll share some data between another thread and the
callbacks, we will need to introduce some locking mechanism to ensure
safe data access.

This might be reduced by moving the claiming checks back to the method,
but would lead errors to be handled in different ordering, and so the
user to be requested for a password, and then - in case fail.
This can still happen now, but only if there are concurrent requests.
2020-11-10 14:45:59 +01:00
4e7cf47a3d device: Get sender from invocation instead that during username check
We now can get an invocation-owned sender at any moment with GDBus, so
there's no point of getting it as optional return-out value from the
username check function.
2020-11-10 14:45:59 +01:00
9d3f3fcaca device: Use some #define's for polkit supported actions
Avoid repeating the same strings everywhere that might just lead to
errors, use define's instead.
2020-11-10 14:45:59 +01:00
30474a6546 manager: Add GDBus object manager
This is not used right now in all its full possibilities, but will make
devices hotplug support easier to implement and handle at client-side
level.

As per this we can stop doing the manual tracking of the devices.
2020-11-10 14:45:59 +01:00
93bad82540 fprintd: Use GDBus codegen based implementation
Fprintd is dependent on the deprecated dbus-glib, also this doesn't provide
various features we can take advantage of, like the ones for async
authentication mechanism.

So, remove all the dbus-glib dependencies and simplify the code, but without
any further refactor, and keeping everything as it used to work, while this
will give room for further improvements in subsequent commits.

Internally, we just use dbus-codegen to generate the skeletons, and we
use the generated FprintdDBusManager with composition, while we
implement the device skeleton interface in FprintDevice, so that we
don't have to use it as a proxy, and keep being closer to what it used
to be with dbus-glib.

Fixes: #61
2020-11-10 14:45:59 +01:00
e224913b80 Revert "data: Add additional fprintd lockdown"
The current lockdown rules prevent USB devices from being accessed and
cause threading to not work.
As such, revert them until it is clear on how/if we can apply these
measures. It is primarily not clear on how to prevent fork/clone as
fprintd does not need those.

This reverts commit 2fd86624e5.

See: #82
2020-11-10 12:27:38 +00:00
b2cae5cccf tests/fprintd: Check that identification with multiple images works 2020-11-06 11:06:25 +01:00
3419901f65 build: Don't add the utils tests under the daemon suite 2020-11-04 21:06:13 +01:00
c85ca09e35 tests/fprintd-utils: Ensure that we exit with error if we have no device 2020-11-04 21:06:13 +01:00
ecc02cb588 utils: Uniform the no-devices error messages removing duplicated checks
Use error messages to be consistent, and avoid checking for a returned
value when dbus-glib function to fetch it returned false, as it's
implicit that we had a failure.

Otherwise if didn't fail we are sure that we got the requested value.
2020-11-04 21:06:13 +01:00
091f373109 tests/fprintd-utils: Check the tools return values in some tests 2020-11-04 21:06:13 +01:00
f6eb3b3ea5 verify: Pass the "any" finger parameter to the daemon
fprintd supports "any" finger parameter for the VerifyStart call, and it's
up to the daemon to pick the first known if the device doesn't support
identification.

So remove the check to verify utility and add a test to verify this is
respected.
2020-11-04 21:06:13 +01:00
d7ca9e6095 tests/fprintd-utils: Verify happens on first finger if device has no identification
Ensure that this is true when using the utility
2020-11-04 21:06:13 +01:00
6797928884 dbusmock/fprintd: Add ability to add a device with Identification support
Devices which have identification support "any" finger and do not fallback
to a single-finger check.
2020-11-04 21:06:13 +01:00
d6c70be822 dbusmock/fprintd: Add ability to remove devices 2020-11-04 21:06:13 +01:00
d33a7c7e9d tests/fprintd-utils: Check that all fingers are listed 2020-11-04 21:06:13 +01:00
94d3a18dcd tests/fprintd-utils: Check that verify returns an error if no finger is set 2020-11-04 21:06:13 +01:00
5635383c96 tests/fprintd-utils: Check that fprintd-verify requests the expected finger 2020-11-04 21:06:13 +01:00
ac98b881be dbusmock/fprintd: Make possible to retrieve the finger selected for verification
So that we can ensure that the client requested the one we expect
2020-11-04 21:06:13 +01:00
629f7fcc11 dbusmock/fprintd: Reset the current action on release
So it happens in the real daemon
2020-11-04 21:06:13 +01:00
5a703baa20 verify: Fail if we try to verify a non-enrolled finger
Since we list the fingers available fail early in case it's not found
2020-11-04 21:06:10 +01:00
6641cb6da8 tests/fprintd-utils: Verify that we can match all the enrolled fingers 2020-11-04 21:04:21 +01:00
870b48637a tests/fprintd-utils: Verify errors if the device is already claimed 2020-11-04 21:04:21 +01:00
4b0cde81fd tests/fprintd-utils: Add utility function to stop the utility process
This avoids addCleanup ordering errors and also errors when we may try to
print an invalid stdout pipe (like when we have processed it all), as python
might fail with something like:

 ======================================================================
 ERROR: test_fprintd_multiple_verify_fails (__main__.TestFprintdUtilsVerify)
 ----------------------------------------------------------------------
 Traceback (most recent call last):
   File "~/GNOME/fprintd/tests/test_fprintd_utils.py", line 102, in <lambda>
     self.addCleanup(lambda: print(process.stdout.read()))
   File "/usr/lib/python3.8/codecs.py", line 321, in decode
     data = self.buffer + input
 TypeError: can't concat NoneType to bytes
2020-11-04 21:04:21 +01:00
59b3d2af8d tests/fprintd-utils: Call addCleanup actions in reverse order
unittest addCleanup calls are called in reverse order, so we need to reverse
the order of the calls as well, otherwise we won't correctly terminate the
subprocess children
2020-11-04 21:04:21 +01:00
93bcac946e tests/fprintd-utils: Move verification tests to a single class
We can factorize various checks, so let's simplify test code
2020-11-04 21:04:19 +01:00
a5063dc0e4 tests/fprintd-utils: Setup the device on setUp()
No need to repeat the action in every unit test, but move the tests to a
different class to easily allow adding another class with tests with no
such initialization
2020-11-04 20:20:29 +01:00
5fbc38c938 tests/fprintd-utils: Run super setUp/tearDown functions 2020-11-04 20:04:21 +01:00
c42e627ddd pam: Always return translated string from helper
This means that the different functions in the header match as all
functions will return the translted string instead of only one of them.
2020-10-13 09:29:44 +00:00
2fd86624e5 data: Add additional fprintd lockdown 2020-10-13 09:28:39 +00:00
6dc699ae6f tests: Fix test not failing on error
An assertion that is raised within a callback will not be swallowed by
the C code that called the function. To ensure that errors will be
noticable, pass the result back to the surrounding scope and check it
there.
2020-10-02 17:54:20 +02:00
e075d37590 tests: Check that a verify operation can be immediately restarted
This excercises the path where we early-report a result and the
VerifyStop call must wait for the operation to complete intenernally.

Note that this test cannot fail right now due to the FpImageDevice
internal code still trying to hide the deactivation delay internally.

See libfprint!174
2020-10-01 12:19:35 +00:00
18d6a86e9d device: Use early match for reporting verify status
libfprint 1.90.1 supports early match result report which allows to inform
the clients as soon as the driver detected a match/no-match without having
to wait the whole device driver deactivation.

Use this feature in fprintd, by emitting the "VerifyStatus" signal as soon
as we've an usable result we can inform the client about.

As per this, ignore any possible error we may get afterwards, due to device
issues or late user cancellation, as the point of the verification action
(i.e. getting a match/no-match) can be considered accomplished.

Fixes https://gitlab.freedesktop.org/libfprint/fprintd/issues/35
2020-10-01 12:19:35 +00:00
14051cac76 device: Fix race condition when clients vanish
If a client vanishes while we are opening or closing then fprintd would
uncoditionally try to close the device immediately. This should not be
done, it instead needs to wait for the open/close to complete.

Add open/close to the current action enum and keep track of the fact
that they are ongoing. Also check, whether the device has been closed in
the meantime (after the nested mainloop is done).

Fixes: #65
2020-10-01 10:39:05 +00:00
4e47222962 Revert "ci: Work-around meson coverage bug"
This reverts commit c07a63da99.
2020-08-18 12:07:59 +02:00
c07a63da99 ci: Work-around meson coverage bug
See https://github.com/mesonbuild/meson/issues/7437
2020-08-17 16:41:07 +02:00
812a3552a6 build: Fix custom_target meson warning
WARNING: custom_target 'utils_marshal' has more than one output! Using the first one.
2020-08-17 16:14:25 +02:00
ff06a301f0 build: Bump libfprint req 2020-08-17 15:21:27 +02:00
19353c971c build: Add some linefeeds to generated XML files 2020-08-17 15:21:27 +02:00
290e56023f tests: Test more branches in pam verify signal handler 2020-08-17 15:11:08 +02:00
efe92a7c33 pam: Increase severity to error for unknown verify results 2020-05-27 14:15:15 +02:00
a7cf0ae3b2 pam: Fix double free after verification error
The data->result was free'ed both in the loop (before breaking) and
afterwards. As the first case did not set the pointer to NULL, this
could result in a double free.

Fix this by simply removing the free that is in the loop and relying on
the cleanup later on.
2020-05-27 13:38:37 +02:00
ba60533f71 device: Remove unused disconnected flag
We were saving the state of a disconnected device because we used a
workaround to figure it out, but now libfprint would provide us a proper
GError in such case, and so we'd handle it without the need of saving the
state, given it's never used anyways.
2020-05-11 15:11:53 +00:00
fcd2d65490 tests: Add PAM test for hardware failure
This error is supposed to help replicate the problems encountered in:
https://gitlab.freedesktop.org/libfprint/fprintd/-/issues/59
2020-04-07 10:47:35 +02:00
6dd010f05c ci: Add a job running tests built with address sanitizer 2020-04-01 13:58:08 +00:00
714f499ab6 tests: Double the timeouts when testing with address sanitizer 2020-04-01 13:58:08 +00:00
d72c802415 tests/fprintd: Ensure that the daemon doesn't crash or abort
An application terminating because of a signal like SIGSEGV, SIGABRT and
friends, will exit with a signal number that is 128 + $SIGNAL_NUMBER, so
let's ensure that the daemon has not been terminated because of a such error

This makes even more sense with address sanitizer builds, as the daemon
would exit with abort.
2020-04-01 13:58:08 +00:00
184e1bd4d0 build: Support running tests with address sanitizer
Make possible to run tests with address sanitizer to quickly check for
memory errors, although we have to disable the error exit code in case of
leaks because we have some which are due to something else down in the stack
(and LSAN suppression files doesn't allow to define the stack to ignore
as we can in valgrind).

However, we'd abort in case of memory errors anyways, so this still helps
to prevent major problems, while still logging the leaks.

In order to run pam module tests with ASAN we need to manually pass the
library to LD_PRELOAD, as we do for the wrapper.
2020-04-01 13:58:08 +00:00
f401f399a8 pam: Get preloaded libraries paths using compiler
In order to run pam module tests we need to pass the libraries via
LD_PRELOAD, this supports a list of library paths, so use the compiler in
order to find their full paths (with soname) and check their presence.

In order to support linker scripts we need to introduce a workaround.
See meson issue https://github.com/mesonbuild/meson/issues/6880
2020-04-01 13:58:08 +00:00
3dd0018f23 build: Set default CFLAGS using meson's c_args
Meson supports checking for default arguments natively without having to
do this for each one, so just use this feature.

Not doing this will become a warning as per meson 0.52.0 [1].

[1] https://github.com/mesonbuild/meson/pull/5627
2020-03-31 14:26:38 +02:00
90298134a2 tests/fprintd: Add checks for delete enrolled fingers permissions
The test doesn't need any assertion because we're calling DeleteEnrolledFingers
and in case it fails a net.reactivated.Fprint.Error.PermissionDenied error
would be thrown, and thus an exception would be raised at python level, making
the test to fail.
2020-03-27 20:45:59 +01:00
8ff4360750 tests: Add test for STATE_DIRECTORY being multiple paths 2020-03-27 17:05:13 +01:00
fd733e55be file_storage: Handle STATE_DIRECTORY containing multiple paths
As per systemd's documentation:
If multiple directories are set, then in the environment variable the
paths are concatenated with colon (":").

Handle that case by splitting the paths if there's a ":" in the
variable.

Closes: #50
2020-03-27 17:05:06 +01:00
6a1fffae82 tests/fprintd: Fix claim_from_other_client_is_released_when_vanished on CI
test_claim_from_other_client_is_released_when_vanished would fail on
the CI but work on a local system because we wouldn't want long enough
for the "vanished" code path to be taken into account. Add a small
timeout to make sure it works on the CI as well.
2020-03-27 16:57:16 +01:00
47bd3f7fbb tests/fprintd: Fix test_enroll_invalid_storage_dir test
enroll_image() was always waiting for enroll-completed rather than for
what the caller expected as the result.

======================================================================
FAIL: test_enroll_invalid_storage_dir (__main__.FPrintdVirtualDeviceClaimedTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/hadess/Projects/jhbuild/fprintd/tests/fprintd.py", line 661, in test_enroll_invalid_storage_dir
    self.enroll_image('whorl', expected_result='enroll-failed')
  File "/home/hadess/Projects/jhbuild/fprintd/tests/fprintd.py", line 384, in enroll_image
    self.wait_for_result('enroll-completed')
  File "/home/hadess/Projects/jhbuild/fprintd/tests/fprintd.py", line 373, in wait_for_result
    self.assertEqual(self._last_result, expected)
AssertionError: 'enroll-failed' != 'enroll-completed'
- enroll-failed
+ enroll-completed
2020-03-27 16:26:28 +01:00
4f3589c0dc build: List single unit python tests allowing to run them separately
We run a certain number of tests right now, without being able to easily
run them separated or to check which one failed.

So add a script to inspect all the available unittests per each python
script and use it to figure out the tests we can run in meson.

As per this, define a global 'python_tests' variable in meson that allows
to register new python tests easily without having to repeat the settings
for all the tests.

For each test we have, we check if we can fetch a list of unit tests, and
if possible we create a meson test for each one.
Otherwise we just fallback to normal behavior.

This is something that can be hopefully implemented into upstream meson [1].

[1] https://github.com/mesonbuild/meson/issues/6851
2020-03-27 02:29:57 +01:00
eccd790df7 tests/fprintd: Add missing implementation of list_tests
Use unittest_inspector that we provide for meson tests inspection as well
2020-03-27 02:01:14 +01:00
bc29114a2c tests: Add a tool to inspect the single python unit tests
The unit tests files are provided as python files, adding a tool that
allows to list them in order to be able to run them easily via

  python3 -m unittest module.Class.test_method
2020-03-27 01:57:22 +01:00
cf95187268 ci: Update CI after the fdo template changes
The CI definition needs to be updated to work with the new fdo
templates.

Adapted from bd4f118b5e
2020-03-24 15:50:57 +01:00
23c37cd9b5 Revert "ci: Fix CI syntax error"
This reverts commit 01ea517a97.
2020-03-24 15:49:30 +01:00
967e4f24ed Revert "ci: Fix unknown keys in CI"
This reverts commit 10a3e75937.
2020-03-24 15:49:22 +01:00
d7fec03f24 tests/fprintd: Increase allowed timeout
On my laptop it takes 48 seconds to run.
2020-03-23 12:22:53 -04:00
4a3ae5ccaf tests/fprintd: Allow tests to run even when virtual device is not available
The test file calls self.daemon_start() in order to start fprintd and
locate the virtual image device that's needed for the test to run.
However, since the virtual image driver is not available on all
libfprint installations, the test should be skipped if the driver is not
available.

The skipping mechanism used to work, by checking if self.device is None.
This is based on the assumption that self.device would be set to None in
cases where the driver is not available. However, even in the past
self.device is only set to None in the tearDown method and not in setUp,
so presumably in edge cases it didn't entirely work.

However, since 0fb4f3b021 which
consistently removes the self.device attribute rather than setting it to
None, the "self.device is None" check no longer works. In particular,
the following error message is shown:

    test_manager_get_default_device (__main__.FPrintdManagerTests) ...
    Did not find virtual device! Probably libfprint was build without
    the corresponding driver!
    ERROR

After this patch, the following is shown:

    test_manager_get_default_device (__main__.FPrintdManagerTests) ...
    Did not find virtual device! Probably libfprint was build without
    the corresponding driver!
    skipped 'Need virtual_image device to run the test'

We fix this bug by consistently setting self.device to None, in both the
setUp method before daemon_start gets called, and in tearDown. We also
make the same change to self.manager for consistency.

The issue was not caught on CI, as the CI configuration always installs
a libfprint version that has the virtual_image device explicitly enabled
in the preparation phase.
2020-03-23 12:22:20 -04:00
e828ea3b2d main: Add missing locale.h include
The include is present in all other binaries in utils/.
2020-03-19 23:33:58 -04:00
d27872ff86 test_fprintd_utils: Don't use hard-coded sleep time
Make sleep time on verification dependent on the environment, so that it's
different when under valgrind
2020-03-17 17:14:35 +01:00
cfbded36e1 test_fprintd_utils: Use a non-blocking pipe to read output
Avoid using a temporary file for reading the utilities output, so we can
read it as it comes, ignoring the previous one, and avoiding open/closing
the file.

To keep the output printing on cleanup working, adding an utility function
that reads the output and save it for later printing
2020-03-17 17:14:35 +01:00
072fbc2b46 tests_fprintd_utils: Print the process output when done
This is particularly useful when using valgrind or address sanitizer to read
the output log
2020-03-17 17:14:35 +01:00
7846359b65 test_fprintd_utils: De-duplicate utilities launching
Avoid repeating the same operation to launch the utilities all the times,
but provide instead a function that allows to start a process and saves its
output without having to handle this in every test.

Simplify the operation when we just want the final output, still reusing
the same code.
2020-03-17 17:14:35 +01:00
08339a0648 tests/fprintd: Skip some tests in CI depending on permissions 2020-03-17 17:14:35 +01:00
3dd10b4b37 tests/fprind: Check that device is released when Caller operation is done
Add support to run fprintd-utils to test fprint daemon, and ensure that a
device is released and its operation cancelled once a caller goes away.

Related to https://gitlab.freedesktop.org/libfprint/fprintd/issues/37
2020-03-17 17:14:35 +01:00
bee2e154b1 tests/fprintd: Ensure that other clients can't interfere with claimer 2020-03-17 17:14:35 +01:00
27f0b64d03 tests/fprintd: Add tests for verification through finger id 2020-03-17 17:14:35 +01:00
f4ee2f86a3 device: Throw NoEnrolledPrints on missing finger in gallery
Throw a NoEnrolledPrints error if the requested finger isn't present
in the gallery.
2020-03-17 17:14:35 +01:00
04829ed39f tests/fprintd: Check that we can't mix Enroll and Verify operations 2020-03-17 17:14:35 +01:00
756a80a63e tests/fprintd: Ensure we throw NoActionInProgress on *Stop() 2020-03-17 17:14:35 +01:00
b861500a9f device: Throw AlreadyInUse error if stopping an enroll during verification
When starting an enroll when verification is in progress (and vice-versa) we
emit an AlreadyInUse error, however when calling VerifyStop() during an
enrollment (and vice-versa) we just return a NoActionInProgress error, which
is not the case.

So let's be consistent and change the error type.
2020-03-17 17:14:33 +01:00
154d0c0373 device: Use proper function name in debug 2020-03-17 17:10:17 +01:00
b2cdc1ed1e tests/fprint: Check current API user permissions 2020-03-17 17:10:17 +01:00
ab47e03f05 tests/fprintd: Ensure device open is correctly handled 2020-03-17 17:10:17 +01:00
f92801a15c tests/fprintd: Ensure that verify fails on storage read failure 2020-03-17 17:10:17 +01:00
086ceb98ab tests/fprintd: Ensure enroll fails on storage save failure 2020-03-17 17:10:17 +01:00
8bdbc7e2b0 tests/fprintd: Ensure that we can't verify without enrolled fingers 2020-03-17 17:10:17 +01:00
1f8bb1abd4 tests/fprintd: Ensure that devices are available on name appeared
Given we don't support adding devices after we created the manager (yet) we
must ensure that once the name appeared we have them all
2020-03-17 17:10:17 +01:00
47d55a97c4 tests/fprintd: Add tests for Manager methods 2020-03-17 17:10:17 +01:00
657b90a066 tests/fprintd: Move assertFprintError to FPrintdTest
This may be used by any class inheriting FPrintdTest, so let's move it at
lower level.
2020-03-17 17:10:17 +01:00
3821b96ca5 tests/fprintd: Only enable 'virtual_image' driver
When we run tests in a system with real devices, we may try to initialize
the real ones, while we can just ignore them all in tests.

We do it in setUp instead of setUpClass to allow tests to change this if
they need to, but just for temporary.
2020-03-17 17:10:17 +01:00
22ad9b5ae8 tests/fprintd: Check all the error types during verify/enroll
Ensure that we properly handle all the errors that a driver may return us
2020-03-17 17:10:17 +01:00
2ddc8a86a2 tests/fprind: Add verify retry tests for all the cases
The tests are repeated for both verify and identify actions
2020-03-17 17:10:17 +01:00
930cae4647 tests/fprind: Add enroll retry tests for all the cases 2020-03-17 17:10:17 +01:00
5e9624bef5 device: Fix verify-disconnected state name
Respect protocol, and use proper name
2020-03-17 17:10:17 +01:00
efac52d94f device: Fix retry enroll error names
Use proper names for enroll retry errors, fixing a copy/paste error from the
verify code.
2020-03-17 17:10:17 +01:00
af18aa35e5 tests/fprintd: Add tests to verify permissions on prints management 2020-03-17 17:10:16 +01:00
0d7a703200 tests/fprintd: Split claimed device tests to a single class
Avoid doing fo reach test the Claim/Release procedure manually if it is just
a prerequisite for the test.
2020-03-17 17:09:19 +01:00
ad19c49e2e tests/fprintd: Move tests which need a claimed device 2020-03-17 17:09:19 +01:00
a8de1003a4 tests/fprintd: Use addCleanup to always terminate daemons
Ensure that the daemons are always terminated after each test, even on
failure, so that we don't have to do hacks such as always trying to stop
them
2020-03-17 17:09:19 +01:00
cefe939141 tests/fprintd: Use addCleanup to ensure we remove the test dir 2020-03-17 17:09:19 +01:00
24cd986476 tests/fprintd: Use GTestDBus unset to undefine envs
Unset the dbus environment that may affect the dbus tests using GTestDBus
utility, instead of doing it manually.
2020-03-17 17:09:19 +01:00
06480c7994 tests/fprintd: Use a new bus for each test unit
When creating a new unit we used to get the system bus via Gio.bus_get_sync,
however this has a singleton implementation, and so would always return the
same connection, creating issues in tests when a new test suite is added
because the newly got connection would be already closed.

So, just manually create a new bus connection, also close the bus and
cleanup the test bus in dbus.
2020-03-17 17:09:19 +01:00
b2ad590891 tests/fprintd: Call the super class on setup 2020-03-17 17:09:19 +01:00
85aad7bb01 tests/fprintd: Remove usage of never-used self.client variable 2020-03-17 17:09:19 +01:00
0fb4f3b021 tests/fprintd: Cleanup the client proxies on tearDown
Ensure we nullify them when the test is done.
2020-03-17 17:09:19 +01:00
21564885ea tests/fprintd: Do the parent tearDown after we've done our stuff 2020-03-17 17:09:19 +01:00
6064e30200 tests/fprintd: Remove the force-exit timeout
Since we rely on meson now to do this, we don't need to have manual
management of the timeout
2020-03-17 17:09:19 +01:00
fd8297306c tests/fprintd: Ensure that we get an error on early release
Ensure that we get an error when releasing a device that is in process of
enrollment, verification or identification
2020-03-17 17:09:19 +01:00
e7f804e9fc device: Cancel the ongoing operation when releasing the device
If a device is currently verifying, identifying or enrolling we may want the
user to stop the operation before we actually release the device.

Otherwise we may end-up in trying to close (failing) the internal device,
while fprintd is still considering the device active, causing a dead-lock
(the device can't be released, but neither claimed again or stop the current
action).

In fact calling Claim() -> EnrollStart() -> Release(), we would fail with
the error

  net.reactivated.Fprint.Error.Internal:
  Release failed with error: The device is still busy with another
  operation, please try again later. (36)"

However, if we try to call VerifyStop, after this error, we'd fail because
for the fprintd logic, the device is not claimed anymore, but actually
closed, and we'd need to claim it again, but... That would still cause an
internal error.

To avoid this, in case Relase() is called cancel the ongoing operation,
and wait until it's done before completing the release call.
2020-03-17 17:09:19 +01:00
0e993d92e2 device: Return 'verify-no-match' on cancelled verification
We were returning a 'verify-unknown-error' while we actually know what
happened, so better to return a soft operation failure.
2020-03-17 17:09:19 +01:00
b312a5e540 device: Return 'enroll-failed' on cancelled enrollment
We were returning an 'enroll-unknown-error' while we actually know what
happened, so better to return a soft operation failure.
2020-03-17 17:09:19 +01:00
c12778ec5b tests/fprintd: Verify that each enroll stage happens
Instead of automatically replying with the 'whorl' image for every enroll
state signal with result 'enroll-stage-passed', only perform the number
of required enroll stages and ensure that we get the expected results.

This also will allow to manually perform enroll steps in other tests.
2020-03-17 17:09:19 +01:00
dbabd4d7d3 tests/fprintd: Deduplicate enrollment code 2020-03-17 15:54:14 +01:00
db1865eb3e tests/fprintd: Deduplicate result wait code 2020-03-17 15:49:40 +01:00
10a3e75937 ci: Fix unknown keys in CI
Fix:
root config contains unknown keys: container_fedora_build
2020-03-17 15:31:08 +01:00
01ea517a97 ci: Fix CI syntax error
Fix CI syntax error:
container_fedora_build: unknown keys in `extends` (.fedora@container-build)
Caused by changes in the wayland CI templates:
4a73f030d0
2020-03-17 15:29:37 +01:00
3a98ef646b ci: Re-enable stable branch
Now that libfprint v2 has landed in rawhide.
2020-02-19 13:07:27 +01:00
750a815fdf ci: Use extends to repeat libfprint builds
This syntax is just nicer and more maintainable than the YAML anchors
2020-02-18 14:08:32 +01:00
53fcf52989 ci: Factorize the similar parameters in build jobs 2020-02-18 14:06:32 +01:00
52e12459df main: Improve comments on fprint manager creation
Explain why the manager creation is async better in both in the main file
and in the manager itself
2020-02-14 16:01:01 +01:00
554df2a8d9 utils: Fix memory leak when error is ignored in list
If we get a `NoEnrolledPrints` error while list, we don't consider it an
hard error and in such case we proceed to releasing the device, but without
clearing the previously set error first.
2020-02-14 16:00:20 +01:00
681bd1ed2a device: Fix leaked matched print on identify
When starting an identify operation we allocate a gallery of prints from the
gallery, although if we match one of them we get that back in the finish
callback but with a further reference added.

So, in order to clean it up, use an auto-pointer or we'd end up in leaking
it, and the address sanitizer was catching this in our tests already:

  Indirect leak of 12020 byte(s) in 5 object(s) allocated from:
    #0 0x7fe8bc638ce6 in calloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10dce6)
    #1 0x7fe8bc37ffd0 in g_malloc0 ../../glib/glib/gmem.c:132
    #2 0x55d100635c01 in load_from_file ../src/file_storage.c:159
    #3 0x55d100635c01 in file_storage_print_data_load ../src/file_storage.c:182
    #4 0x55d10063e950 in fprint_device_verify_start ../src/device.c:882
    #5 0x55d10064036b in dbus_glib_marshal_fprint_device_VOID__STRING_POINTER src/device-dbus-glue.h:96
    #6 0x7fe8bc50f6f5  (/usr/lib/x86_64-linux-gnu/libdbus-glib-1.so.2+0xd6f5)
2020-02-14 15:55:09 +01:00
8890732194 device: Don't leak the user on claim error while deleting prints
When using the delete method we check if the device was claimed, if this
fails because the device is already in use we return an error, but we don't
free the user.

While this could be fixed by just a further g_free call, let's just remove
remove the other manual free calls, and use an auto-pointer instead for this
function.
2020-02-14 15:55:09 +01:00
7dac81dcad device: Use g_clear_error instead of doing the same manually
We've now an utility function that can help us to free and unset an error
double pointer, so let's use it.
2020-02-14 15:55:09 +01:00
1ecae1d014 delete: Clear the error in case we ignore it
If we get a `NoEnrolledPrints` error during delete, we don't consider it an
hard error and in such case we proceed to releasing the device, but without
clearing the previously set error first.
2020-02-14 15:55:09 +01:00
ba7a45d3f8 device: Always free error in delete enrolled fingers2
During delete enrolled fingers2 call, if the check-claimed control fails, we
would return the error without freeing it.

While this could be fixed by just a further g_error_free call, let's just
remove the other manual free call, and use an auto-pointer instead for this
function.
2020-02-14 15:55:09 +01:00
49dced5566 device: Always free error in delete enrolled fingers
During delete enrolled fingers call, if the check-claimed control fails, and
we get an error different from FPRINT_ERROR_CLAIM_DEVICE, we would return
the error without freeing it.

While this could be fixed by just a further g_error_free call, let's just
remove all the manual free calls, and use an auto-pointer instead for this
function.
2020-02-14 15:55:09 +01:00
e25544a8f0 manager: Remove unused path variable 2020-02-14 15:55:09 +01:00
ee8589ec9d main: Ensure we always free context, loop and error
In case of early return we may not free them consistently, while this is not
a big problem in a main function, is better to have a cleaner management,
and we did get valgrind reports.
2020-02-14 15:55:09 +01:00
42 changed files with 6460 additions and 3139 deletions

7
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,7 @@
# The commits that did automated reformatting. You can ignore them
# during git-blame with `--ignore-rev` or `--ignore-revs-file`.
#
# $ git config --add 'blame.ignoreRevsFile' '.git-blame-ignore-revs'
#
f73429f06226f5423c92b1c504313657c9b6f9b5

View File

@ -8,9 +8,9 @@ include:
variables: variables:
extends: .libfprint_common_variables extends: .libfprint_common_variables
FEDORA_TAG: rawhide FDO_DISTRIBUTION_TAG: latest
FEDORA_VERSION: rawhide FDO_DISTRIBUTION_VERSION: rawhide
FEDORA_IMAGE: "$CI_REGISTRY/libfprint/$CI_PROJECT_NAME/fedora/$FEDORA_VERSION:$FEDORA_TAG" FEDORA_IMAGE: "$CI_REGISTRY/libfprint/$CI_PROJECT_NAME/fedora/$FDO_DISTRIBUTION_VERSION:$FDO_DISTRIBUTION_TAG"
DEPENDENCIES: dbus-glib-devel DEPENDENCIES: dbus-glib-devel
gcc gcc
gcovr gcovr
@ -18,6 +18,7 @@ variables:
git git
glibc-devel glibc-devel
gtk-doc gtk-doc
libasan
libfprint-devel libfprint-devel
meson meson
pam-devel pam-devel
@ -28,7 +29,17 @@ variables:
image: "$FEDORA_IMAGE" image: "$FEDORA_IMAGE"
.install_libfprint_dev: &install_libfprint_dev stages:
- check-source
- build
- test
.fprintd_build_preconditions:
except:
variables:
- $FPRINT_CRON_TASK == "BUILD_CI_IMAGES"
.install_libfprint_dev:
before_script: before_script:
# Make sure we don't build or link against the system libfprint # Make sure we don't build or link against the system libfprint
- dnf remove -y libfprint-devel - dnf remove -y libfprint-devel
@ -41,22 +52,27 @@ image: "$FEDORA_IMAGE"
# So we don't get error about this libfprint file # So we don't get error about this libfprint file
- echo "libfprint/demo/gtk-libfprint-test.ui" >> po/POTFILES.skip - echo "libfprint/demo/gtk-libfprint-test.ui" >> po/POTFILES.skip
test_indent:
stage: check-source
extends: .fprintd_build_preconditions
script:
- scripts/uncrustify.sh
- git diff
- "! git status -s | grep -q ."
build_stable: build_stable:
except: extends: .fprintd_build_preconditions
variables: stage: build
- $FPRINT_CRON_TASK == "BUILD_CI_IMAGES"
# FIXME: Stable builds will fail until libfprintv 2 reaches rawhide
allow_failure: true
script: script:
- meson _build - meson _build
- ninja -C _build -v - ninja -C _build -v
- ninja -C _build -v install - ninja -C _build -v install
build_dev: build_dev:
except: extends:
variables: - .fprintd_build_preconditions
- $FPRINT_CRON_TASK == "BUILD_CI_IMAGES" - .install_libfprint_dev
<<: *install_libfprint_dev stage: build
script: script:
- meson _build --werror -Dgtk_doc=true - meson _build --werror -Dgtk_doc=true
- ninja -C _build -v - ninja -C _build -v
@ -68,11 +84,10 @@ build_dev:
- _build/meson-logs/*.txt - _build/meson-logs/*.txt
test_dev: test_dev:
except: extends:
variables: - .fprintd_build_preconditions
- $FPRINT_CRON_TASK == "BUILD_CI_IMAGES" - .install_libfprint_dev
stage: test stage: test
<<: *install_libfprint_dev
script: script:
- meson _build -Db_coverage=true - meson _build -Db_coverage=true
- meson test -C _build --verbose --no-stdsplit --timeout-multiplier 3 - meson test -C _build --verbose --no-stdsplit --timeout-multiplier 3
@ -84,15 +99,29 @@ test_dev:
paths: paths:
- _build/meson-logs - _build/meson-logs
test_dev_with_sanitizer:
extends:
- .fprintd_build_preconditions
- .install_libfprint_dev
stage: test
script:
- meson _build -Db_sanitize=address
- meson test -C _build --verbose --no-stdsplit --timeout-multiplier 5
artifacts:
name: meson-logs
when: on_failure
paths:
- _build/meson-logs
# CONTAINERS creation stage # CONTAINERS creation stage
container_fedora_build: container_fedora_build:
extends: .fedora@container-build extends: .fdo.container-build@fedora
only: only:
variables: variables:
- $FPRINT_CRON_TASK == "BUILD_CI_IMAGES" - $FPRINT_CRON_TASK == "BUILD_CI_IMAGES"
variables: variables:
GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image
# a list of packages to install # a list of packages to install
FEDORA_RPMS: FDO_DISTRIBUTION_PACKAGES:
$DEPENDENCIES $DEPENDENCIES
$LIBFPRINT_DEPENDENCIES $LIBFPRINT_DEPENDENCIES

63
NEWS
View File

@ -1,6 +1,69 @@
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.8:
It seems that we are finally reaching the end of the tunnel with regard
to regressions. One more issue that cropped up was that a pam_fprintd fix
to avoid a possible authentication bypass caused issues when fprintd was
just started on demand.
Highlights:
- pam: Only listen to NameOwnerChanged after fprintd is known to run (#94)
- Place new ObjectManager DBus API at /net/reactivated/Fprint
Version 1.90.7:
While 1.90.6 fixed a number of issues, we did have a bad regression due
causing pam_fprintd to crash when there are no fingerprint devices
installed.
Highlights:
- pam: Guard strdup calls against NULL pointers
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:
The 1.90.4 release contained some bad errors, this release addresses those.
Highlights:
- Permit building with polkit older than 0.114
- Fix possible issues with PAM test
- Fix incorrect DBus policy
- Fix build so that CFLAGS enviroment is correctly used
- Skip hotplug test with older libfprint (which times out otherwise)
Version 1.90.4:
This fprintd release contains major core reworkings and improved testing.
As such, only the most important changes are listed here, focusing on
changes relevant to distributors.
Highlights:
- Authentication is now required to enroll a new print (#5)
- Add support for the libfprint early reporting mechanism
- Proper hotplug support together with libfprint 1.90.4
- Handle STATE_DIRECTORY containing multiple paths
version 1.90.1: version 1.90.1:
- Add support for prints saved on the fingerprint device itself - Add support for prints saved on the fingerprint device itself
- Add integration tests using the virtual image driver, and further - Add integration tests using the virtual image driver, and further

View File

@ -9,3 +9,6 @@
/* Define to the version of this package. */ /* Define to the version of this package. */
#mesondefine VERSION #mesondefine VERSION
/* Whether current polkit version supports autopointers */
#mesondefine POLKIT_HAS_AUTOPOINTERS

View File

@ -11,6 +11,7 @@ configure_file(
install_dir: dbus_service_dir, install_dir: dbus_service_dir,
) )
if get_option('systemd')
configure_file( configure_file(
configuration: configuration_data({ configuration: configuration_data({
'libexecdir': fprintd_installdir, 'libexecdir': fprintd_installdir,
@ -20,6 +21,7 @@ configure_file(
install: true, install: true,
install_dir: systemd_unit_dir, install_dir: systemd_unit_dir,
) )
endif
polkit_policy = 'net.reactivated.fprint.device.policy' polkit_policy = 'net.reactivated.fprint.device.policy'
polkit_policy_target = i18n.merge_file(polkit_policy, polkit_policy_target = i18n.merge_file(polkit_policy,

View File

@ -12,8 +12,18 @@
<!-- 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"
<allow 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 -->
<allow send_destination="net.reactivated.Fprint"
send_interface="org.freedesktop.DBus.Introspectable"/>
<allow send_destination="net.reactivated.Fprint"
send_interface="org.freedesktop.DBus.Properties"/>
<allow send_destination="net.reactivated.Fprint"
send_interface="org.freedesktop.DBus.ObjectManager"/>
</policy> </policy>
</busconfig> </busconfig>

View File

@ -25,7 +25,7 @@
<defaults> <defaults>
<allow_any>no</allow_any> <allow_any>no</allow_any>
<allow_inactive>no</allow_inactive> <allow_inactive>no</allow_inactive>
<allow_active>yes</allow_active> <allow_active>auth_self_keep</allow_active>
</defaults> </defaults>
</action> </action>

View File

@ -2,8 +2,8 @@ docbook_xml_header = custom_target('docbook_xml_header',
output: 'docbook-xml-header.xml', output: 'docbook-xml-header.xml',
command: [ command: [
'echo', '-n', 'echo', '-n',
'<?xml version="1.0"?>', '<?xml version="1.0"?>\n',
'<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">', '<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">\n',
], ],
capture: true, capture: true,
) )

View File

@ -1,9 +1,9 @@
project('fprintd', 'c', project('fprintd', 'c',
version: '1.90.1', version: '1.90.8',
license: 'GPLv2+', license: 'GPLv2+',
default_options: [ default_options: [
'buildtype=debugoptimized', 'buildtype=debugoptimized',
'warning_level=1', 'warning_level=3',
'c_std=gnu99', 'c_std=gnu99',
], ],
meson_version: '>= 0.50.0') meson_version: '>= 0.50.0')
@ -12,23 +12,8 @@ gnome = import('gnome')
i18n = import('i18n') i18n = import('i18n')
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
host_system = host_machine.system()
glib_min_version = '2.56'
libfprint_min_version = '1.90.0'
fprintd_installdir = get_option('prefix') / get_option('libexecdir')
fprintd_plugindir = get_option('prefix') / get_option('libdir') / meson.project_name() / 'modules'
storage_path = get_option('prefix') / get_option('localstatedir') / 'lib/fprint'
localedir = get_option('prefix') / get_option('localedir')
datadir = get_option('prefix') / get_option('datadir')
sysconfdir = get_option('sysconfdir')
if get_option('prefix') != '/usr'
sysconfdir = get_option('prefix') / sysconfdir
endif
common_cflags = cc.get_supported_arguments([ common_cflags = cc.get_supported_arguments([
'-fno-strict-aliasing', '-fno-strict-aliasing',
'-Wall',
'-Wcast-align', '-Wcast-align',
'-Werror=address', '-Werror=address',
'-Werror=array-bounds', '-Werror=array-bounds',
@ -67,14 +52,38 @@ common_cflags = cc.get_supported_arguments([
]) ])
add_project_arguments(common_cflags, language: 'c') add_project_arguments(common_cflags, language: 'c')
common_cflags = cc.get_supported_arguments([
# The stub passes a lot of params that we do not use, maybe a good idea to
# mark it appropriately, but this works well for now.
'-Wno-unused-parameter',
# We use g_signal_handlers_disconnect_* which is not compatible with -Wpedantic
'-Wno-pedantic',
])
add_project_arguments(common_cflags, language: 'c')
host_system = host_machine.system()
# NOTE: Bump gdbus-codegen min version once we can depend on 2.64!
glib_min_version = '2.56'
libfprint_min_version = '1.90.1'
fprintd_installdir = get_option('prefix') / get_option('libexecdir')
fprintd_plugindir = get_option('prefix') / get_option('libdir') / meson.project_name() / 'modules'
storage_path = get_option('prefix') / get_option('localstatedir') / 'lib/fprint'
localedir = get_option('prefix') / get_option('localedir')
datadir = get_option('prefix') / get_option('datadir')
sysconfdir = get_option('sysconfdir')
if get_option('prefix') != '/usr'
sysconfdir = get_option('prefix') / sysconfdir
endif
# Dependencies # Dependencies
glib_dep = dependency('glib-2.0', version: '>=' + glib_min_version) glib_dep = dependency('glib-2.0', version: '>=' + glib_min_version)
gio_dep = dependency('gio-2.0', version: '>=' + glib_min_version) gio_dep = dependency('gio-2.0', version: '>=' + glib_min_version)
gio_unix_dep = dependency('gio-unix-2.0', version: '>=' + glib_min_version)
gmodule_dep = dependency('gmodule-2.0', version: '>=' + glib_min_version) gmodule_dep = dependency('gmodule-2.0', version: '>=' + glib_min_version)
libfprint_dep = dependency('libfprint-2', version: '>=' + libfprint_min_version) libfprint_dep = dependency('libfprint-2', version: '>=' + libfprint_min_version)
polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.91') polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.91')
dbus_dep = dependency('dbus-1', required: false) dbus_dep = dependency('dbus-1', required: false)
dbus_glib_dep = dependency('dbus-glib-1')
libsystemd_dep = dependency('libsystemd', required: get_option('pam')) libsystemd_dep = dependency('libsystemd', required: get_option('pam'))
pam_dep = cc.find_library('pam', pam_dep = cc.find_library('pam',
required: get_option('pam'), required: get_option('pam'),
@ -85,13 +94,17 @@ pod2man = find_program('pod2man', required: get_option('man'))
xsltproc = find_program('xsltproc', required: get_option('gtk_doc')) xsltproc = find_program('xsltproc', required: get_option('gtk_doc'))
# StateDirectory was introduced in systemd 235 # StateDirectory was introduced in systemd 235
systemd_dep = dependency('systemd', version: '>= 235') systemd_dep = dependency('systemd', version: '>= 235', required: false)
systemd_unit_dir = get_option('systemd_system_unit_dir') systemd_unit_dir = get_option('systemd_system_unit_dir')
if systemd_unit_dir == '' if systemd_unit_dir == '' and systemd_dep.found()
systemd_unit_dir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir') systemd_unit_dir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir')
endif endif
if get_option('systemd') and systemd_unit_dir == ''
error('systemd development files or systemd_system_unit_dir is needed for systemd support.')
endif
dbus_service_dir = get_option('dbus_service_dir') dbus_service_dir = get_option('dbus_service_dir')
dbus_data_dir = datadir dbus_data_dir = datadir
dbus_interfaces_dir = '' dbus_interfaces_dir = ''
@ -125,6 +138,7 @@ python3_test_modules = {
'dbus': true, 'dbus': true,
'dbusmock': true, 'dbusmock': true,
'gi': true, 'gi': true,
'gi.repository.FPrint': true,
'pypamtest': get_option('pam'), 'pypamtest': get_option('pam'),
} }
python3_available_modules = [] python3_available_modules = []
@ -140,6 +154,7 @@ cdata.set_quoted('GETTEXT_PACKAGE', meson.project_name())
cdata.set_quoted('PACKAGE_VERSION', meson.project_version()) cdata.set_quoted('PACKAGE_VERSION', meson.project_version())
cdata.set_quoted('VERSION', meson.project_version()) cdata.set_quoted('VERSION', meson.project_version())
cdata.set_quoted('SYSCONFDIR', sysconfdir) cdata.set_quoted('SYSCONFDIR', sysconfdir)
cdata.set('POLKIT_HAS_AUTOPOINTERS', polkit_gobject_dep.version().version_compare('>= 0.114'))
config_h = configure_file( config_h = configure_file(
input: 'config.h.in', input: 'config.h.in',
@ -178,5 +193,7 @@ output += ' PAM module: ' + pam_dep.found().to_string()
output += ' Manuals: ' + get_option('man').to_string() output += ' Manuals: ' + get_option('man').to_string()
output += ' GTK Doc: ' + get_option('gtk_doc').to_string() output += ' GTK Doc: ' + get_option('gtk_doc').to_string()
output += ' XML Linter ' + xmllint.found().to_string() output += ' XML Linter ' + xmllint.found().to_string()
output += '\nTest setup:\n'
output += ' With address sanitizer: ' + address_sanitizer.to_string()
message('\n'+'\n'.join(output)+'\n') message('\n'+'\n'.join(output)+'\n')

View File

@ -6,6 +6,10 @@ option('man',
description: 'Generate the man files', description: 'Generate the man files',
type: 'boolean', type: 'boolean',
value: true) value: true)
option('systemd',
description: 'Install system service files',
type: 'boolean',
value: true)
option('systemd_system_unit_dir', option('systemd_system_unit_dir',
description: 'Directory for systemd service files', description: 'Directory for systemd service files',
type: 'string') type: 'string')

View File

@ -28,16 +28,18 @@
#define GNUC_UNUSED __attribute__((__unused__)) #define GNUC_UNUSED __attribute__((__unused__))
static bool str_equal (const char *a, const char *b) static bool
str_equal (const char *a, const char *b)
{ {
if (a == NULL && b == NULL) if (a == NULL && b == NULL)
return true; return true;
if (a == NULL || b == NULL) if (a == NULL || b == NULL)
return false; return false;
return (strcmp (a, b) == 0); return strcmp (a, b) == 0;
} }
struct { struct
{
const char *dbus_name; const char *dbus_name;
const char *place_str_generic; const char *place_str_generic;
const char *place_str_specific; const char *place_str_specific;
@ -105,33 +107,44 @@ struct {
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral" #pragma GCC diagnostic ignored "-Wformat-nonliteral"
GNUC_UNUSED static char *finger_str_to_msg(const char *finger_name, const char *driver_name, bool is_swipe) GNUC_UNUSED static char *
finger_str_to_msg (const char *finger_name, const char *driver_name, bool is_swipe)
{ {
int i; int i;
if (finger_name == NULL) if (finger_name == NULL)
return NULL; return NULL;
for (i = 0; fingers[i].dbus_name != NULL; i++) { for (i = 0; fingers[i].dbus_name != NULL; i++)
if (str_equal (fingers[i].dbus_name, finger_name)) { {
if (is_swipe == false) { if (!str_equal (fingers[i].dbus_name, finger_name))
if (driver_name) { continue;
if (is_swipe == false)
{
if (driver_name)
{
char *s; char *s;
int ret; int ret;
ret = asprintf (&s, TR (fingers[i].place_str_specific), driver_name); ret = asprintf (&s, TR (fingers[i].place_str_specific), driver_name);
return ret >= 0 ? s : NULL; return ret >= 0 ? s : NULL;
} else { }
else
{
return strdup (TR (fingers[i].place_str_generic)); return strdup (TR (fingers[i].place_str_generic));
} }
} else { }
if (driver_name) { else
{
if (driver_name)
{
char *s; char *s;
int ret; int ret;
ret = asprintf (&s, TR (fingers[i].swipe_str_specific), driver_name); ret = asprintf (&s, TR (fingers[i].swipe_str_specific), driver_name);
return ret >= 0 ? s : NULL; return ret >= 0 ? s : NULL;
} else {
return strdup (TR (fingers[i].swipe_str_generic));
} }
else
{
return strdup (TR (fingers[i].swipe_str_generic));
} }
} }
} }
@ -146,23 +159,25 @@ GNUC_UNUSED static char *finger_str_to_msg(const char *finger_name, const char *
* verify-match * verify-match
* verify-unknown-error * verify-unknown-error
*/ */
GNUC_UNUSED static const char *verify_result_str_to_msg(const char *result, bool is_swipe) GNUC_UNUSED static const char *
verify_result_str_to_msg (const char *result, bool is_swipe)
{ {
if (result == NULL) if (result == NULL)
return NULL; return NULL;
if (strcmp (result, "verify-retry-scan") == 0) { if (strcmp (result, "verify-retry-scan") == 0)
{
if (is_swipe == false) if (is_swipe == false)
return N_("Place your finger on the reader again"); return TR (N_("Place your finger on the reader again"));
else else
return N_("Swipe your finger again"); return TR (N_("Swipe your finger again"));
} }
if (strcmp (result, "verify-swipe-too-short") == 0) if (strcmp (result, "verify-swipe-too-short") == 0)
return N_("Swipe was too short, try again"); return TR (N_("Swipe was too short, try again"));
if (strcmp (result, "verify-finger-not-centered") == 0) if (strcmp (result, "verify-finger-not-centered") == 0)
return N_("Your finger was not centered, try swiping your finger again"); return TR (N_("Your finger was not centered, try swiping your finger again"));
if (strcmp (result, "verify-remove-and-retry") == 0) if (strcmp (result, "verify-remove-and-retry") == 0)
return N_("Remove your finger, and try swiping your finger again"); return TR (N_("Remove your finger, and try swiping your finger again"));
return NULL; return NULL;
} }
@ -172,24 +187,25 @@ GNUC_UNUSED static const char *verify_result_str_to_msg(const char *result, bool
* enroll-failed * enroll-failed
* enroll-unknown-error * enroll-unknown-error
*/ */
GNUC_UNUSED static const char *enroll_result_str_to_msg(const char *result, bool is_swipe) GNUC_UNUSED static const char *
enroll_result_str_to_msg (const char *result, bool is_swipe)
{ {
if (result == NULL) if (result == NULL)
return NULL; return NULL;
if (strcmp (result, "enroll-retry-scan") == 0 || strcmp (result, "enroll-stage-passed") == 0) { if (strcmp (result, "enroll-retry-scan") == 0 || strcmp (result, "enroll-stage-passed") == 0)
{
if (is_swipe == false) if (is_swipe == false)
return N_("Place your finger on the reader again"); return TR (N_("Place your finger on the reader again"));
else else
return N_("Swipe your finger again"); return TR (N_("Swipe your finger again"));
} }
if (strcmp (result, "enroll-swipe-too-short") == 0) if (strcmp (result, "enroll-swipe-too-short") == 0)
return N_("Swipe was too short, try again"); return TR (N_("Swipe was too short, try again"));
if (strcmp (result, "enroll-finger-not-centered") == 0) if (strcmp (result, "enroll-finger-not-centered") == 0)
return N_("Your finger was not centered, try swiping your finger again"); return TR (N_("Your finger was not centered, try swiping your finger again"));
if (strcmp (result, "enroll-remove-and-retry") == 0) if (strcmp (result, "enroll-remove-and-retry") == 0)
return N_("Remove your finger, and try swiping your finger again"); return TR (N_("Remove your finger, and try swiping your finger again"));
return NULL; return NULL;
} }

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
@ -63,18 +64,21 @@ static uint64_t
now (void) now (void)
{ {
struct timespec ts; struct timespec ts;
clock_gettime (CLOCK_MONOTONIC, &ts); clock_gettime (CLOCK_MONOTONIC, &ts);
return (uint64_t) ts.tv_sec * USEC_PER_SEC + (uint64_t) ts.tv_nsec / NSEC_PER_USEC; return (uint64_t) ts.tv_sec * USEC_PER_SEC + (uint64_t) ts.tv_nsec / NSEC_PER_USEC;
} }
static bool str_has_prefix (const char *s, const char *prefix) static bool
str_has_prefix (const char *s, const char *prefix)
{ {
if (s == NULL || prefix == NULL) if (s == NULL || prefix == NULL)
return false; return false;
return (strncmp (s, prefix, strlen (prefix)) == 0); return strncmp (s, prefix, strlen (prefix)) == 0;
} }
static bool send_msg(pam_handle_t *pamh, const char *msg, int style) static bool
send_msg (pam_handle_t *pamh, const char *msg, int style)
{ {
const struct pam_message mymsg = { const struct pam_message mymsg = {
.msg_style = style, .msg_style = style,
@ -83,24 +87,24 @@ 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)
return false; return false;
return (pc->conv(1, &msgp, &resp, pc->appdata_ptr) == PAM_SUCCESS); return pc->conv (1, &msgp, &resp, pc->appdata_ptr) == PAM_SUCCESS;
} }
static bool send_info_msg(pam_handle_t *pamh, const char *msg) static bool
send_info_msg (pam_handle_t *pamh, const char *msg)
{ {
return send_msg (pamh, msg, PAM_TEXT_INFO); return send_msg (pamh, msg, PAM_TEXT_INFO);
} }
static bool send_err_msg(pam_handle_t *pamh, const char *msg) static bool
send_err_msg (pam_handle_t *pamh, const char *msg)
{ {
return send_msg (pamh, msg, PAM_ERROR_MSG); return send_msg (pamh, msg, PAM_ERROR_MSG);
} }
@ -110,39 +114,37 @@ 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 +155,36 @@ open_device (pam_handle_t *pamh,
sd_bus_message_exit_container (m); sd_bus_message_exit_container (m);
out: return path ? strdup (path) : NULL;
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,
@ -181,27 +197,48 @@ verify_result (sd_bus_message *m,
uint64_t done = false; uint64_t done = false;
int r; int r;
if (!sd_bus_message_is_signal(m, "net.reactivated.Fprint.Device", "VerifyStatus")) { if (!sd_bus_message_is_signal (m, "net.reactivated.Fprint.Device", "VerifyStatus"))
{
pam_syslog (data->pamh, LOG_ERR, "Not the signal we expected (iface: %s, member: %s)", pam_syslog (data->pamh, LOG_ERR, "Not the signal we expected (iface: %s, member: %s)",
sd_bus_message_get_interface (m), sd_bus_message_get_interface (m),
sd_bus_message_get_member (m)); sd_bus_message_get_member (m));
return 0; return 0;
} }
if ((r = sd_bus_message_read (m, "sb", &result, &done)) < 0) { if ((r = sd_bus_message_read (m, "sb", &result, &done)) < 0)
{
pam_syslog (data->pamh, LOG_ERR, "Failed to parse VerifyResult signal: %d", r); pam_syslog (data->pamh, LOG_ERR, "Failed to parse VerifyResult signal: %d", r);
data->verify_ret = PAM_AUTHINFO_UNAVAIL;
return 0;
}
if (!data->verify_started)
{
pam_syslog (data->pamh, LOG_ERR, "Unexpected VerifyResult '%s', %" PRIu64 " signal", result, done);
return 0; 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 (done) { if (data->result)
{
free (data->result);
data->result = NULL;
}
if (done && result)
{
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 +251,30 @@ 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: %m"); {
pam_syslog (data->pamh, LOG_ERR, "Failed to parse VerifyFingerSelected signal: %d", errno);
data->verify_ret = PAM_AUTHINFO_UNAVAIL;
return 0;
}
if (!data->verify_started)
{
pam_syslog (data->pamh, LOG_ERR, "Unexpected VerifyFingerSelected %s signal", finger_name);
return 0; 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;
} }
@ -238,9 +287,10 @@ get_property_string (sd_bus *bus,
const char *interface, const char *interface,
const char *member, const char *member,
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,125 +301,146 @@ 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
sd_bus_message_unref (reply); verify_started_cb (sd_bus_message *m,
return sd_bus_error_set_errno(error, r); void *userdata,
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;
} }
for (;;) { for (;;)
{
int64_t wait_time; int64_t wait_time;
wait_time = verification_end - now (); wait_time = verification_end - now ();
@ -379,29 +450,40 @@ 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) { {
pam_syslog(pamh, LOG_DEBUG, "Waiting for %"PRId64" seconds (%"PRId64" usecs)", if (debug)
{
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 (now () >= verification_end) { if (data->verify_ret != PAM_INCOMPLETE)
return data->verify_ret;
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,
@ -409,46 +491,41 @@ do_verify (pam_handle_t *pamh,
NULL, NULL,
NULL); NULL);
if (data->timed_out) { if (data->timed_out)
ret = PAM_AUTHINFO_UNAVAIL; {
break; return PAM_AUTHINFO_UNAVAIL;
} else { }
if (str_equal (data->result, "verify-no-match")) { else
send_err_msg (data->pamh, "Failed to match fingerprint"); {
ret = PAM_AUTH_ERR; if (str_equal (data->result, "verify-no-match"))
} else if (str_equal (data->result, "verify-match")) { {
ret = PAM_SUCCESS; send_err_msg (data->pamh, "Failed to match fingerprint");
break; }
} else if (str_equal (data->result, "verify-unknown-error")) { else if (str_equal (data->result, "verify-match"))
ret = PAM_AUTHINFO_UNAVAIL; {
} else if (str_equal (data->result, "verify-disconnected")) { return PAM_SUCCESS;
ret = PAM_AUTHINFO_UNAVAIL; }
free (data->result); else if (str_equal (data->result, "verify-unknown-error"))
break; {
} else { return PAM_AUTHINFO_UNAVAIL;
send_info_msg (data->pamh, _("An unknown error occurred")); }
ret = PAM_AUTH_ERR; else if (str_equal (data->result, "verify-disconnected"))
free (data->result); {
break; return PAM_AUTHINFO_UNAVAIL;
}
else
{
send_err_msg (data->pamh, _("An unknown error occurred"));
return PAM_AUTH_ERR;
} }
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
@ -457,8 +534,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;
@ -472,32 +549,29 @@ user_has_prints (pam_handle_t *pamh,
&m, &m,
"s", "s",
username); username);
if (r < 0) { if (r < 0)
{
/* If ListEnrolledFingers fails then verification should /* If ListEnrolledFingers fails then verification should
* also fail (both use the same underlying call), so we * also fail (both use the same underlying call), so we
* report false here and bail out early. */ * report false here and bail out early. */
if (debug) { if (debug)
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: return num_fingers > 0;
sd_bus_message_unref (m);
return (num_fingers > 0);
} }
static void static void
@ -505,10 +579,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",
@ -516,11 +589,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);
}
} }
static bool static bool
@ -529,10 +599,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",
@ -540,53 +609,96 @@ 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 do_auth(pam_handle_t *pamh, const char *username) 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);
data->verify_ret = PAM_AUTHINFO_UNAVAIL;
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;
pam_syslog (data->pamh, LOG_WARNING, "fprintd name owner changed during operation!");
return 0;
}
static int
do_auth (pam_handle_t *pamh, const char *username)
{ {
char *dev;
bool have_prints; bool have_prints;
bool has_multiple_devices;
int ret = PAM_AUTHINFO_UNAVAIL;
sd_bus *bus = NULL;
if (sd_bus_open_system (&bus) < 0) { pf_autoptr (verify_data) data = NULL;
pam_syslog (pamh, LOG_ERR, "Error with getting the bus: %m"); pf_autoptr (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)
{
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); data->dev = open_device (pamh, bus, &data->has_multiple_devices);
if (dev == NULL) { if (data->dev == NULL)
sd_bus_unref (bus);
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)) { /* Only connect to NameOwnerChanged when needed. In case of automatic startup
ret = do_verify (pamh, bus, dev, has_multiple_devices); * we rely on the fact that we never see those signals.
release_device (pamh, bus, dev); */
name_owner_changed_slot = NULL;
sd_bus_match_signal (bus,
&name_owner_changed_slot,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"NameOwnerChanged",
name_owner_changed,
data);
if (claim_device (pamh, bus, data->dev, username))
{
int ret = do_verify (bus, data);
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
@ -601,9 +713,8 @@ is_remote (pam_handle_t *pamh)
* We want to not run for known remote hosts */ * We want to not run for known remote hosts */
if (rhost != NULL && if (rhost != NULL &&
*rhost != '\0' && *rhost != '\0' &&
strcmp (rhost, "localhost") != 0) { strcmp (rhost, "localhost") != 0)
return true; return true;
}
if (sd_session_is_remote (NULL) > 0) if (sd_session_is_remote (NULL) > 0)
return true; return true;
@ -611,12 +722,12 @@ is_remote (pam_handle_t *pamh)
return false; return false;
} }
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, PAM_EXTERN int
pam_sm_authenticate (pam_handle_t *pamh, int flags, int argc,
const char **argv) const char **argv)
{ {
const char *username; const char *username;
unsigned 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");
@ -624,72 +735,86 @@ 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++)
if (argv[i] != NULL) { {
if (str_equal (argv[i], "debug")) { if (argv[i] != NULL)
{
if (str_equal (argv[i], "debug"))
{
pam_syslog (pamh, LOG_DEBUG, "debug on"); pam_syslog (pamh, LOG_DEBUG, "debug on");
debug = true; debug = true;
} else if (str_has_prefix (argv[i], DEBUG_MATCH)) { }
else if (str_has_prefix (argv[i], DEBUG_MATCH))
{
pam_syslog (pamh, LOG_DEBUG, "debug on"); pam_syslog (pamh, LOG_DEBUG, "debug on");
const char *value; const char *value;
value = argv[i] + strlen (DEBUG_MATCH); value = argv[i] + strlen (DEBUG_MATCH);
if (str_equal (value, "on") || if (str_equal (value, "on") ||
str_equal (value, "true") || str_equal (value, "true") ||
str_equal (value, "1")) { str_equal (value, "1"))
{
pam_syslog (pamh, LOG_DEBUG, "debug on"); pam_syslog (pamh, LOG_DEBUG, "debug on");
debug = true; debug = true;
} else if (str_equal (value, "off") || }
else if (str_equal (value, "off") ||
str_equal (value, "false") || str_equal (value, "false") ||
str_equal (value, "0")) { str_equal (value, "0"))
{
debug = false; debug = false;
} else { }
else
{
pam_syslog (pamh, LOG_DEBUG, "invalid debug value '%s', disabling", value); pam_syslog (pamh, LOG_DEBUG, "invalid debug value '%s', disabling", value);
} }
} else if (str_has_prefix (argv[i], MAX_TRIES_MATCH) && strlen(argv[i]) == strlen (MAX_TRIES_MATCH) + 1) { }
else if (str_has_prefix (argv[i], MAX_TRIES_MATCH) && strlen (argv[i]) == strlen (MAX_TRIES_MATCH) + 1)
{
max_tries = atoi (argv[i] + strlen (MAX_TRIES_MATCH)); max_tries = atoi (argv[i] + strlen (MAX_TRIES_MATCH));
if (max_tries < 1) { if (max_tries < 1)
if (debug) { {
if (debug)
pam_syslog (pamh, LOG_DEBUG, "invalid max tries '%s', using %d", pam_syslog (pamh, LOG_DEBUG, "invalid max tries '%s', using %d",
argv[i] + strlen (MAX_TRIES_MATCH), DEFAULT_MAX_TRIES); argv[i] + strlen (MAX_TRIES_MATCH), DEFAULT_MAX_TRIES);
}
max_tries = DEFAULT_MAX_TRIES; max_tries = DEFAULT_MAX_TRIES;
} }
if (debug) if (debug)
pam_syslog (pamh, LOG_DEBUG, "max_tries specified as: %d", max_tries); pam_syslog (pamh, LOG_DEBUG, "max_tries specified as: %d", max_tries);
} else if (str_has_prefix (argv[i], TIMEOUT_MATCH) && strlen(argv[i]) <= strlen (TIMEOUT_MATCH) + 2) { }
else if (str_has_prefix (argv[i], TIMEOUT_MATCH) && strlen (argv[i]) <= strlen (TIMEOUT_MATCH) + 2)
{
timeout = atoi (argv[i] + strlen (TIMEOUT_MATCH)); timeout = atoi (argv[i] + strlen (TIMEOUT_MATCH));
if (timeout < MIN_TIMEOUT) { if (timeout < MIN_TIMEOUT)
if (debug) { {
if (debug)
pam_syslog (pamh, LOG_DEBUG, "timeout %d secs too low, using %d", pam_syslog (pamh, LOG_DEBUG, "timeout %d secs too low, using %d",
timeout, MIN_TIMEOUT); timeout, MIN_TIMEOUT);
}
timeout = MIN_TIMEOUT; timeout = MIN_TIMEOUT;
} else if (debug) { }
else if (debug)
{
pam_syslog (pamh, LOG_DEBUG, "timeout specified as: %d secs", timeout); pam_syslog (pamh, LOG_DEBUG, "timeout specified as: %d secs", timeout);
} }
} }
} }
} }
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,
const char **argv) const char **argv)
{ {
return PAM_SUCCESS; return PAM_SUCCESS;
} }
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, PAM_EXTERN int
pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc,
const char **argv) const char **argv)
{ {
return PAM_SUCCESS; return PAM_SUCCESS;
} }

View File

@ -0,0 +1,61 @@
/*
* 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)

137
scripts/uncrustify.cfg Normal file
View File

@ -0,0 +1,137 @@
newlines lf
input_tab_size 8
output_tab_size 8
string_escape_char 92
string_escape_char2 0
# indenting
indent_columns 2
indent_with_tabs 0
indent_align_string True
indent_brace 2
indent_braces false
indent_braces_no_func True
indent_func_call_param false
indent_func_def_param false
indent_func_proto_param false
indent_switch_case 0
indent_case_brace 2
indent_paren_close 1
# spacing
sp_arith Add
sp_assign Add
sp_enum_assign Add
sp_bool Add
sp_compare Add
sp_inside_paren Remove
sp_inside_fparens Remove
sp_func_def_paren Force
sp_func_proto_paren Force
sp_paren_paren Remove
sp_balance_nested_parens False
sp_paren_brace Remove
sp_before_square Remove
sp_before_squares Remove
sp_inside_square Remove
sp_before_ptr_star Add
sp_between_ptr_star Remove
sp_after_comma Add
sp_before_comma Remove
sp_after_cast Add
sp_sizeof_paren Add
sp_not Remove
sp_inv Remove
sp_addr Remove
sp_member Remove
sp_deref Remove
sp_sign Remove
sp_incdec Remove
sp_attribute_paren remove
sp_macro Force
sp_func_call_paren Force
sp_func_call_user_paren Remove
set func_call_user _ N_ C_ g_autoptr g_auto
sp_brace_typedef add
sp_cond_colon add
sp_cond_question add
sp_defined_paren remove
# alignment
align_keep_tabs False
align_with_tabs False
align_on_tabstop False
align_number_right False
align_func_params True
align_var_def_span 0
align_var_def_amp_style 1
align_var_def_colon true
align_enum_equ_span 0
align_var_struct_span 2
align_var_def_star_style 2
align_var_def_amp_style 2
align_typedef_span 2
align_typedef_func 0
align_typedef_star_style 2
align_typedef_amp_style 2
# newlines
nl_assign_leave_one_liners True
nl_enum_leave_one_liners False
nl_func_leave_one_liners False
nl_if_leave_one_liners False
nl_end_of_file Add
nl_assign_brace Remove
nl_func_var_def_blk 1
nl_fcall_brace Add
nl_enum_brace Remove
nl_struct_brace Force
nl_union_brace Force
nl_if_brace Force
nl_brace_else Force
nl_elseif_brace Force
nl_else_brace Add
nl_for_brace Force
nl_while_brace Force
nl_do_brace Force
nl_brace_while Force
nl_switch_brace Force
nl_before_case True
nl_after_case False
nl_func_type_name Force
nl_func_proto_type_name Remove
nl_func_paren Remove
nl_func_decl_start Remove
nl_func_decl_args Force
nl_func_decl_end Remove
nl_fdef_brace Force
nl_after_return False
nl_define_macro False
nl_create_if_one_liner False
nl_create_for_one_liner False
nl_create_while_one_liner False
nl_after_semicolon True
nl_multi_line_cond true
# mod
# I'd like these to be remove, but that removes brackets in if { if { foo } }, which i dislike
# Not clear what to do about that...
mod_full_brace_for Remove
mod_full_brace_if Remove
mod_full_brace_if_chain True
mod_full_brace_while Remove
mod_full_brace_do Remove
mod_full_brace_nl 3
mod_paren_on_return Remove
# line splitting
#code_width = 78
ls_for_split_full True
ls_func_split_full True
# positioning
pos_bool Trail
pos_conditional Trail

19
scripts/uncrustify.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
SRCROOT=`git rev-parse --show-toplevel`
CFG="$SRCROOT/scripts/uncrustify.cfg"
echo "srcroot: $SRCROOT"
case "$1" in
-c|--check)
OPTS="--check"
;;
*)
OPTS="--replace --no-backup"
;;
esac
pushd "$SRCROOT"
uncrustify -c "$CFG" $OPTS `git ls-tree --name-only -r HEAD | grep -E '.*\.[ch]$' | grep -v build/`
RES=$?
popd
exit $RES

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);

File diff suppressed because it is too large Load Diff

View File

@ -12,9 +12,6 @@
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> <node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<interface name="net.reactivated.Fprint.Device"> <interface name="net.reactivated.Fprint.Device">
<annotation name="org.freedesktop.DBus.GLib.CSymbol"
value="fprint_device" />
<doc:doc> <doc:doc>
<doc:title id="polkit-integration"> <doc:title id="polkit-integration">
PolicyKit integration PolicyKit integration
@ -282,8 +279,6 @@
<arg type="as" name="enrolled_fingers" direction="out"> <arg type="as" name="enrolled_fingers" direction="out">
<doc:doc><doc:summary>An array of strings representing the enrolled fingerprints. See <doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>.</doc:summary></doc:doc> <doc:doc><doc:summary>An array of strings representing the enrolled fingerprints. See <doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>.</doc:summary></doc:doc>
</arg> </arg>
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<doc:doc> <doc:doc>
<doc:description> <doc:description>
<doc:para> <doc:para>
@ -304,8 +299,6 @@
<arg type="s" name="username" direction="in"> <arg type="s" name="username" direction="in">
<doc:doc><doc:summary>The username for whom to delete the enrolled fingerprints. See <doc:ref type="description" to="usernames">Usernames</doc:ref>.</doc:summary></doc:doc> <doc:doc><doc:summary>The username for whom to delete the enrolled fingerprints. See <doc:ref type="description" to="usernames">Usernames</doc:ref>.</doc:summary></doc:doc>
</arg> </arg>
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<doc:doc> <doc:doc>
<doc:description> <doc:description>
<doc:para> <doc:para>
@ -327,8 +320,6 @@
<!-- ************************************************************ --> <!-- ************************************************************ -->
<method name="DeleteEnrolledFingers2"> <method name="DeleteEnrolledFingers2">
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<doc:doc> <doc:doc>
<doc:description> <doc:description>
<doc:para> <doc:para>
@ -348,8 +339,6 @@
<arg type="s" name="username" direction="in"> <arg type="s" name="username" direction="in">
<doc:doc><doc:summary>The username for whom to claim the device. See <doc:ref type="description" to="usernames">Usernames</doc:ref>.</doc:summary></doc:doc> <doc:doc><doc:summary>The username for whom to claim the device. See <doc:ref type="description" to="usernames">Usernames</doc:ref>.</doc:summary></doc:doc>
</arg> </arg>
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<doc:doc> <doc:doc>
<doc:description> <doc:description>
<doc:para> <doc:para>
@ -368,8 +357,6 @@
<!-- ************************************************************ --> <!-- ************************************************************ -->
<method name="Release"> <method name="Release">
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<doc:doc> <doc:doc>
<doc:description> <doc:description>
<doc:para> <doc:para>
@ -390,8 +377,6 @@
<arg type="s" name="finger_name" direction="in"> <arg type="s" name="finger_name" direction="in">
<doc:doc><doc:summary>A string representing the finger to verify. See <doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>.</doc:summary></doc:doc> <doc:doc><doc:summary>A string representing the finger to verify. See <doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>.</doc:summary></doc:doc>
</arg> </arg>
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<doc:doc> <doc:doc>
<doc:description> <doc:description>
<doc:para> <doc:para>
@ -415,8 +400,6 @@
<!-- ************************************************************ --> <!-- ************************************************************ -->
<method name="VerifyStop"> <method name="VerifyStop">
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<doc:doc> <doc:doc>
<doc:description> <doc:description>
<doc:para> <doc:para>
@ -486,8 +469,6 @@
<doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>. <doc:ref type="description" to="fingerprint-names">Fingerprint names</doc:ref>.
Note that "any" is not a valid finger name for this method.</doc:summary></doc:doc> Note that "any" is not a valid finger name for this method.</doc:summary></doc:doc>
</arg> </arg>
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<doc:doc> <doc:doc>
<doc:description> <doc:description>
<doc:para> <doc:para>
@ -511,8 +492,6 @@
<!-- ************************************************************ --> <!-- ************************************************************ -->
<method name="EnrollStop"> <method name="EnrollStop">
<annotation name="org.freedesktop.DBus.GLib.Async" value="" />
<doc:doc> <doc:doc>
<doc:description> <doc:description>
<doc:para> <doc:para>

View File

@ -42,29 +42,49 @@
#define FILE_STORAGE_PATH "/var/lib/fprint" #define FILE_STORAGE_PATH "/var/lib/fprint"
#define DIR_PERMS 0700 #define DIR_PERMS 0700
static const char *get_storage_path(void) static char *storage_path = NULL;
static const char *
get_storage_path (void)
{ {
const char *path; const char *path = NULL;
if (storage_path != NULL)
return storage_path;
/* set by systemd >= 240 to an absolute path /* set by systemd >= 240 to an absolute path
* taking into account the StateDirectory * taking into account the StateDirectory
* unit file setting */ * unit file setting */
path = g_getenv ("STATE_DIRECTORY"); path = g_getenv ("STATE_DIRECTORY");
if (path != NULL) if (path != NULL)
return path; {
/* If multiple directories are set, then in the environment variable
return FILE_STORAGE_PATH; * the paths are concatenated with colon (":"). */
if (strchr (path, ':'))
{
g_auto(GStrv) elems = NULL;
elems = g_strsplit (path, ":", -1);
storage_path = g_strdup (elems[0]);
}
} }
static char *get_path_to_storedir(const char *driver, const char * device_id, char *base_store) if (storage_path == NULL)
storage_path = g_strdup (FILE_STORAGE_PATH);
return storage_path;
}
static char *
get_path_to_storedir (const char *driver, const char * device_id, char *base_store)
{ {
return g_build_filename (base_store, driver, device_id, NULL); return g_build_filename (base_store, driver, device_id, NULL);
} }
static char *__get_path_to_print(const char *driver, const char * device_id, static char *
__get_path_to_print (const char *driver, const char * device_id,
FpFinger finger, char *base_store) FpFinger finger, char *base_store)
{ {
char *dirpath; g_autofree char *dirpath = NULL;
char *path; char *path;
char fingername[2]; char fingername[2];
@ -72,11 +92,11 @@ static char *__get_path_to_print(const char *driver, const char * device_id,
dirpath = get_path_to_storedir (driver, device_id, base_store); dirpath = get_path_to_storedir (driver, device_id, base_store);
path = g_build_filename (dirpath, fingername, NULL); path = g_build_filename (dirpath, fingername, NULL);
g_free(dirpath);
return path; return path;
} }
static char *get_path_to_print(FpDevice *dev, FpFinger finger, char *base_store) static char *
get_path_to_print (FpDevice *dev, FpFinger finger, char *base_store)
{ {
return __get_path_to_print (fp_device_get_driver (dev), return __get_path_to_print (fp_device_get_driver (dev),
fp_device_get_device_id (dev), fp_device_get_device_id (dev),
@ -84,7 +104,8 @@ static char *get_path_to_print(FpDevice *dev, FpFinger finger, char *base_store)
base_store); base_store);
} }
static char *get_path_to_print_dscv(FpDevice *dev, FpFinger finger, char *base_store) static char *
get_path_to_print_dscv (FpDevice *dev, FpFinger finger, char *base_store)
{ {
return __get_path_to_print (fp_device_get_driver (dev), return __get_path_to_print (fp_device_get_driver (dev),
fp_device_get_device_id (dev), fp_device_get_device_id (dev),
@ -92,12 +113,14 @@ static char *get_path_to_print_dscv(FpDevice *dev, FpFinger finger, char *base_s
base_store); base_store);
} }
static char *file_storage_get_basestore_for_username(const char *username) static char *
file_storage_get_basestore_for_username (const char *username)
{ {
return g_build_filename (get_storage_path (), username, NULL); return g_build_filename (get_storage_path (), username, NULL);
} }
int file_storage_print_data_save(FpPrint *print) int
file_storage_print_data_save (FpPrint *print)
{ {
g_autoptr(GError) err = NULL; g_autoptr(GError) err = NULL;
g_autofree char *path = NULL; g_autofree char *path = NULL;
@ -109,7 +132,8 @@ int file_storage_print_data_save(FpPrint *print)
base_store = file_storage_get_basestore_for_username (fp_print_get_username (print)); base_store = file_storage_get_basestore_for_username (fp_print_get_username (print));
if (!fp_print_serialize (print, (guchar **) &buf, &len, &err)) { if (!fp_print_serialize (print, (guchar **) &buf, &len, &err))
{
g_warning ("Error serializing data: %s", err->message); g_warning ("Error serializing data: %s", err->message);
return -ENOMEM; return -ENOMEM;
} }
@ -120,7 +144,8 @@ int file_storage_print_data_save(FpPrint *print)
base_store); base_store);
dirpath = g_path_get_dirname (path); dirpath = g_path_get_dirname (path);
r = g_mkdir_with_parents (dirpath, DIR_PERMS); r = g_mkdir_with_parents (dirpath, DIR_PERMS);
if (r < 0) { if (r < 0)
{
g_debug ("file_storage_print_data_save(): could not mkdir(\"%s\"): %s", g_debug ("file_storage_print_data_save(): could not mkdir(\"%s\"): %s",
dirpath, g_strerror (r)); dirpath, g_strerror (r));
return r; return r;
@ -128,7 +153,8 @@ int file_storage_print_data_save(FpPrint *print)
//fp_dbg("saving to %s", path); //fp_dbg("saving to %s", path);
g_file_set_contents (path, buf, len, &err); g_file_set_contents (path, buf, len, &err);
if (err) { if (err)
{
g_debug ("file_storage_print_data_save(): could not save '%s': %s", g_debug ("file_storage_print_data_save(): could not save '%s': %s",
path, err->message); path, err->message);
/* FIXME interpret error codes */ /* FIXME interpret error codes */
@ -138,7 +164,8 @@ int file_storage_print_data_save(FpPrint *print)
return 0; return 0;
} }
static int load_from_file(char *path, FpPrint **print) static int
load_from_file (char *path, FpPrint **print)
{ {
g_autoptr(GError) err = NULL; g_autoptr(GError) err = NULL;
gsize length; gsize length;
@ -147,7 +174,8 @@ static int load_from_file(char *path, FpPrint **print)
//fp_dbg("from %s", path); //fp_dbg("from %s", path);
g_file_get_contents (path, &contents, &length, &err); g_file_get_contents (path, &contents, &length, &err);
if (err) { if (err)
{
int r = err->code; int r = err->code;
/* FIXME interpret more error codes */ /* FIXME interpret more error codes */
if (r == G_FILE_ERROR_NOENT) if (r == G_FILE_ERROR_NOENT)
@ -157,7 +185,8 @@ static int load_from_file(char *path, FpPrint **print)
} }
new = fp_print_deserialize ((guchar *) contents, length, &err); new = fp_print_deserialize ((guchar *) contents, length, &err);
if (!new) { if (!new)
{
g_print ("Error deserializing data: %s", err->message); g_print ("Error deserializing data: %s", err->message);
return -EIO; return -EIO;
} }
@ -166,14 +195,16 @@ static int load_from_file(char *path, FpPrint **print)
return 0; return 0;
} }
int file_storage_print_data_load(FpDevice *dev, int
file_storage_print_data_load (FpDevice *dev,
FpFinger finger, FpFinger finger,
const char *username, const char *username,
FpPrint **print) FpPrint **print)
{ {
g_autofree gchar *path = NULL; g_autofree gchar *path = NULL;
g_autofree gchar *base_store = NULL; g_autofree gchar *base_store = NULL;
FpPrint *new = NULL;
g_autoptr(FpPrint) new = NULL;
int r; int r;
base_store = file_storage_get_basestore_for_username (username); base_store = file_storage_get_basestore_for_username (username);
@ -185,16 +216,15 @@ int file_storage_print_data_load(FpDevice *dev,
if (r) if (r)
return r; return r;
if (!fp_print_compatible (new, dev)) { if (!fp_print_compatible (new, dev))
g_object_unref (new);
return -EINVAL; return -EINVAL;
}
*print = new; *print = g_steal_pointer (&new);
return 0; return 0;
} }
int file_storage_print_data_delete(FpDevice *dev, FpFinger finger, const char *username) int
file_storage_print_data_delete (FpDevice *dev, FpFinger finger, const char *username)
{ {
g_autofree gchar *base_store = NULL; g_autofree gchar *base_store = NULL;
g_autofree gchar *path = NULL; g_autofree gchar *path = NULL;
@ -212,19 +242,23 @@ int file_storage_print_data_delete(FpDevice *dev, FpFinger finger, const char *u
return g_unlink (path); return g_unlink (path);
} }
static GSList *scan_dev_storedir(char *devpath, static GSList *
scan_dev_storedir (char *devpath,
GSList *list) GSList *list)
{ {
g_autoptr(GError) err = NULL; g_autoptr(GError) err = NULL;
const gchar *ent; const gchar *ent;
GDir *dir = g_dir_open (devpath, 0, &err); GDir *dir = g_dir_open (devpath, 0, &err);
if (!dir) {
if (!dir)
{
g_debug ("scan_dev_storedir(): opendir(\"%s\") failed: %s", devpath, err->message); g_debug ("scan_dev_storedir(): opendir(\"%s\") failed: %s", devpath, err->message);
return list; return list;
} }
while ((ent = g_dir_read_name(dir))) { while ((ent = g_dir_read_name (dir)))
{
/* ent is an 1 hex character fp_finger code */ /* ent is an 1 hex character fp_finger code */
guint64 val; guint64 val;
gchar *endptr; gchar *endptr;
@ -233,19 +267,21 @@ static GSList *scan_dev_storedir(char *devpath,
continue; continue;
val = g_ascii_strtoull (ent, &endptr, 16); val = g_ascii_strtoull (ent, &endptr, 16);
if (endptr == ent || !FP_FINGER_IS_VALID(val)) { if (endptr == ent || !FP_FINGER_IS_VALID (val))
{
g_debug ("scan_dev_storedir(): skipping print file '%s'", ent); g_debug ("scan_dev_storedir(): skipping print file '%s'", ent);
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);
return list; return list;
} }
GSList *file_storage_discover_prints(FpDevice *dev, const char *username) GSList *
file_storage_discover_prints (FpDevice *dev, const char *username)
{ {
GSList *list = NULL; GSList *list = NULL;
g_autofree gchar *base_store = NULL; g_autofree gchar *base_store = NULL;
@ -265,18 +301,19 @@ GSList *file_storage_discover_prints(FpDevice *dev, const char *username)
return list; return list;
} }
GSList *file_storage_discover_users(void) GSList *
file_storage_discover_users (void)
{ {
g_autoptr(GError) err = NULL; g_autoptr(GError) err = NULL;
GSList *list = NULL; GSList *list = NULL;
const gchar *ent; const gchar *ent;
GDir *dir = g_dir_open (get_storage_path (), 0, &err); GDir *dir = g_dir_open (get_storage_path (), 0, &err);
if (!dir) { if (!dir)
return list; return list;
}
while ((ent = g_dir_read_name(dir))) { while ((ent = g_dir_read_name (dir)))
{
/* ent is a username */ /* ent is a username */
if (*ent == 0) if (*ent == 0)
continue; continue;
@ -288,14 +325,16 @@ GSList *file_storage_discover_users(void)
return list; return list;
} }
int file_storage_init(void) int
file_storage_init (void)
{ {
/* Nothing to do */ /* Nothing to do */
return 0; return 0;
} }
int file_storage_deinit(void) int
file_storage_deinit (void)
{ {
/* Nothing to do */ g_clear_pointer (&storage_path, g_free);
return 0; return 0;
} }

View File

@ -35,5 +35,6 @@ int file_storage_init(void);
int file_storage_deinit (void); int file_storage_deinit (void);
GSList *file_storage_discover_prints(FpDevice *dev, const char *username); GSList *file_storage_discover_prints (FpDevice *dev,
const char *username);
GSList *file_storage_discover_users (void); GSList *file_storage_discover_users (void);

View File

@ -1 +0,0 @@
VOID:STRING,BOOLEAN

View File

@ -20,56 +20,74 @@
#pragma once #pragma once
#include <glib.h> #include <glib.h>
#include <dbus/dbus-glib.h> #include <gio/gio.h>
#include <fprint.h> #include <fprint.h>
#include "fprintd-enums.h"
#include "fprintd-dbus.h"
/* General */ /* General */
#define TIMEOUT 30 #define TIMEOUT 30
#define FPRINT_SERVICE_NAME "net.reactivated.Fprint" #define FPRINT_SERVICE_NAME "net.reactivated.Fprint"
#define FPRINT_SERVICE_PATH "/net/reactivated/Fprint"
/* Errors */ /* Errors */
GQuark fprint_error_quark (void); GQuark fprint_error_quark (void);
GType fprint_error_get_type(void);
#define FPRINT_ERROR fprint_error_quark () #define FPRINT_ERROR fprint_error_quark ()
#define FPRINT_TYPE_ERROR fprint_error_get_type()
#define FPRINT_ERROR_DBUS_INTERFACE "net.reactivated.Fprint.Error"
typedef enum { typedef enum {
FPRINT_ERROR_CLAIM_DEVICE, /* developer didn't claim the device */ /* developer didn't claim the device */
FPRINT_ERROR_ALREADY_IN_USE, /* device is already claimed by somebody else */ FPRINT_ERROR_CLAIM_DEVICE, /*< nick=net.reactivated.Fprint.Error.ClaimDevice >*/
FPRINT_ERROR_INTERNAL, /* internal error occurred */ /* device is already claimed by somebody else */
FPRINT_ERROR_PERMISSION_DENIED, /* PolicyKit refused the action */ FPRINT_ERROR_ALREADY_IN_USE, /*< nick=net.reactivated.Fprint.Error.AlreadyInUse >*/
FPRINT_ERROR_NO_ENROLLED_PRINTS, /* No prints are enrolled */ /* internal error occurred */
FPRINT_ERROR_NO_ACTION_IN_PROGRESS, /* No actions currently in progress */ FPRINT_ERROR_INTERNAL, /*< nick=net.reactivated.Fprint.Error.Internal >*/
FPRINT_ERROR_INVALID_FINGERNAME, /* the finger name passed was invalid */ /* PolicyKit refused the action */
FPRINT_ERROR_NO_SUCH_DEVICE, /* device does not exist */ FPRINT_ERROR_PERMISSION_DENIED, /*< nick=net.reactivated.Fprint.Error.PermissionDenied >*/
/* No prints are enrolled */
FPRINT_ERROR_NO_ENROLLED_PRINTS, /*< nick=net.reactivated.Fprint.Error.NoEnrolledPrints >*/
/* No actions currently in progress */
FPRINT_ERROR_NO_ACTION_IN_PROGRESS, /*< nick=net.reactivated.Fprint.Error.NoActionInProgress >*/
/* the finger name passed was invalid */
FPRINT_ERROR_INVALID_FINGERNAME, /*< nick=net.reactivated.Fprint.Error.InvalidFingername >*/
/* device does not exist */
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 {
FPRINT_DEVICE_PERMISSION_NONE = 0,
FPRINT_DEVICE_PERMISSION_VERIFY = (1 << 0), /*< nick=net.reactivated.fprint.device.verify >*/
FPRINT_DEVICE_PERMISSION_ENROLL = (1 << 1), /*< nick=net.reactivated.fprint.device.enroll >*/
FPRINT_DEVICE_PERMISSION_SETUSERNAME = (1 << 2), /*< nick=net.reactivated.fprint.device.setusername >*/
} FprintDevicePermission;
/* Manager */ /* Manager */
#define FPRINT_TYPE_MANAGER (fprint_manager_get_type ()) #define FPRINT_TYPE_MANAGER (fprint_manager_get_type ())
G_DECLARE_FINAL_TYPE (FprintManager, fprint_manager, FPRINT, MANAGER, GObject) G_DECLARE_FINAL_TYPE (FprintManager, fprint_manager, FPRINT, MANAGER, GObject)
struct _FprintManager { struct _FprintManager
{
GObject parent; GObject parent;
}; };
FprintManager *fprint_manager_new(gboolean no_timeout); FprintManager *fprint_manager_new (GDBusConnection *connection,
gboolean no_timeout);
/* Device */ /* Device */
#define FPRINT_TYPE_DEVICE (fprint_device_get_type ()) #define FPRINT_TYPE_DEVICE (fprint_device_get_type ())
G_DECLARE_FINAL_TYPE (FprintDevice, fprint_device, FPRINT, DEVICE, GObject) G_DECLARE_FINAL_TYPE (FprintDevice, fprint_device, FPRINT, DEVICE,
FprintDBusDeviceSkeleton)
struct _FprintDevice { struct _FprintDevice
GObject parent; {
FprintDBusDeviceSkeleton parent;
}; };
FprintDevice *fprint_device_new (FpDevice *dev); FprintDevice *fprint_device_new (FpDevice *dev);
guint32 _fprint_device_get_id (FprintDevice *rdev); guint32 _fprint_device_get_id (FprintDevice *rdev);
/* Print */ /* Print */
/* TODO */ /* TODO */
/* Binding data included in main.c through server-bindings.h which individual
* class implementations need to access.
*/
extern const DBusGObjectInfo dbus_glib_fprint_manager_object_info;
extern const DBusGObjectInfo dbus_glib_fprint_device_object_info;

View File

@ -1,6 +1,7 @@
/* /*
* fprint D-Bus daemon * fprint D-Bus daemon
* Copyright (C) 2008 Daniel Drake <dsd@gentoo.org> * Copyright (C) 2008 Daniel Drake <dsd@gentoo.org>
* Copyright (C) 2020 Marco Trevisan <marco.trevisan@canonical.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -19,10 +20,11 @@
#include "config.h" #include "config.h"
#include <locale.h>
#include <poll.h> #include <poll.h>
#include <stdlib.h> #include <stdlib.h>
#include <dbus/dbus-glib-bindings.h> #include <gio/gio.h>
#include <glib.h> #include <glib.h>
#include <glib/gi18n.h> #include <glib/gi18n.h>
#include <fprint.h> #include <fprint.h>
@ -36,7 +38,6 @@
fp_storage store; fp_storage store;
DBusGConnection *fprintd_dbus_conn = NULL;
static gboolean no_timeout = FALSE; static gboolean no_timeout = FALSE;
static gboolean g_fatal_warnings = FALSE; static gboolean g_fatal_warnings = FALSE;
@ -56,11 +57,10 @@ static gboolean
load_storage_module (const char *module_name) load_storage_module (const char *module_name)
{ {
GModule *module; GModule *module;
char *filename; g_autofree char *filename = NULL;
filename = g_module_build_path (PLUGINDIR, module_name); filename = g_module_build_path (PLUGINDIR, module_name);
module = g_module_open (filename, 0); module = g_module_open (filename, 0);
g_free (filename);
if (module == NULL) if (module == NULL)
return FALSE; return FALSE;
@ -69,7 +69,8 @@ load_storage_module (const char *module_name)
!g_module_symbol (module, "print_data_save", (gpointer *) &store.print_data_save) || !g_module_symbol (module, "print_data_save", (gpointer *) &store.print_data_save) ||
!g_module_symbol (module, "print_data_load", (gpointer *) &store.print_data_load) || !g_module_symbol (module, "print_data_load", (gpointer *) &store.print_data_load) ||
!g_module_symbol (module, "print_data_delete", (gpointer *) &store.print_data_delete) || !g_module_symbol (module, "print_data_delete", (gpointer *) &store.print_data_delete) ||
!g_module_symbol (module, "discover_prints", (gpointer *) &store.discover_prints)) { !g_module_symbol (module, "discover_prints", (gpointer *) &store.discover_prints))
{
g_module_close (module); g_module_close (module);
return FALSE; return FALSE;
} }
@ -82,46 +83,32 @@ load_storage_module (const char *module_name)
static gboolean static gboolean
load_conf (void) load_conf (void)
{ {
GKeyFile *file; g_autofree char *filename = NULL;
char *filename; g_autofree char *module_name = NULL;
char *module_name;
GError *error = NULL; g_autoptr(GKeyFile) file = NULL;
gboolean ret; g_autoptr(GError) error = NULL;
filename = g_build_filename (SYSCONFDIR, "fprintd.conf", NULL); filename = g_build_filename (SYSCONFDIR, "fprintd.conf", NULL);
file = g_key_file_new (); file = g_key_file_new ();
g_debug ("About to load configuration file '%s'", filename); g_debug ("About to load configuration file '%s'", filename);
if (!g_key_file_load_from_file (file, filename, G_KEY_FILE_NONE, &error)) { if (!g_key_file_load_from_file (file, filename, G_KEY_FILE_NONE, &error))
{
g_warning ("Could not open \"%s\": %s\n", filename, error->message); g_warning ("Could not open \"%s\": %s\n", filename, error->message);
goto bail; return FALSE;
} }
g_free (filename);
filename = NULL;
module_name = g_key_file_get_string (file, "storage", "type", &error); module_name = g_key_file_get_string (file, "storage", "type", &error);
if (module_name == NULL) if (module_name == NULL)
goto bail; return FALSE;
g_key_file_free (file); if (g_str_equal (module_name, "file"))
{
if (g_str_equal (module_name, "file")) {
g_free (module_name);
set_storage_file (); set_storage_file ();
return TRUE; return TRUE;
} }
ret = load_storage_module (module_name); return load_storage_module (module_name);
g_free (module_name);
return ret;
bail:
g_key_file_free (file);
g_free (filename);
g_error_free (error);
return FALSE;
} }
static const GOptionEntry entries[] = { static const GOptionEntry entries[] = {
@ -130,7 +117,8 @@ static const GOptionEntry entries[] = {
{ NULL } { NULL }
}; };
static gboolean sigterm_callback(gpointer data) static gboolean
sigterm_callback (gpointer data)
{ {
GMainLoop *loop = data; GMainLoop *loop = data;
@ -138,13 +126,34 @@ static gboolean sigterm_callback(gpointer data)
return FALSE; return FALSE;
} }
int main(int argc, char **argv) static void
on_name_acquired (GDBusConnection *connection,
const char *name,
gpointer user_data)
{ {
GOptionContext *context; g_debug ("D-Bus service launched with name: %s", name);
GMainLoop *loop; }
GError *error = NULL;
FprintManager *manager; static void
DBusGProxy *driver_proxy; on_name_lost (GDBusConnection *connection,
const char *name,
gpointer user_data)
{
GMainLoop *loop = user_data;
g_warning ("Failed to get name: %s", name);
g_main_loop_quit (loop);
}
int
main (int argc, char **argv)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GMainLoop) loop = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(FprintManager) manager = NULL;
g_autoptr(GDBusConnection) connection = NULL;
guint32 request_name_ret; guint32 request_name_ret;
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
@ -156,13 +165,14 @@ int main(int argc, char **argv)
context = g_option_context_new ("Fingerprint handler daemon"); context = g_option_context_new ("Fingerprint handler daemon");
g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) { if (g_option_context_parse (context, &argc, &argv, &error) == FALSE)
{
g_warning ("couldn't parse command-line options: %s\n", error->message); g_warning ("couldn't parse command-line options: %s\n", error->message);
g_error_free (error);
return 1; return 1;
} }
if (g_fatal_warnings) { if (g_fatal_warnings)
{
GLogLevelFlags fatal_mask; GLogLevelFlags fatal_mask;
fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK);
@ -170,16 +180,14 @@ int main(int argc, char **argv)
g_log_set_always_fatal (fatal_mask); g_log_set_always_fatal (fatal_mask);
} }
/* Obtain a connection to the session bus */ /* Obtain a connection to the system bus */
fprintd_dbus_conn = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
if (fprintd_dbus_conn == NULL) { if (!G_IS_DBUS_CONNECTION (connection))
{
g_warning ("Failed to open connection to bus: %s", error->message); g_warning ("Failed to open connection to bus: %s", error->message);
return 1; return 1;
} }
driver_proxy = dbus_g_proxy_new_for_name(fprintd_dbus_conn,
DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS);
/* Load the configuration file, /* Load the configuration file,
* and the default storage plugin */ * and the default storage plugin */
if (!load_conf ()) if (!load_conf ())
@ -192,33 +200,23 @@ int main(int argc, char **argv)
g_debug ("Launching FprintObject"); g_debug ("Launching FprintObject");
/* create the one instance of the Manager object to be shared between /* create the one instance of the Manager object to be shared between
* all fprintd users */ * all fprintd users. This blocks until all the devices are enumerated */
manager = fprint_manager_new(no_timeout); manager = fprint_manager_new (connection, no_timeout);
/* Obtain the well-known name after the manager has been initialized. /* Obtain the well-known name after the manager has been initialized.
* Otherwise a client immediately enumerating the devices will not see * Otherwise a client immediately enumerating the devices will not see
* any. */ * any. */
if (!org_freedesktop_DBus_request_name(driver_proxy, FPRINT_SERVICE_NAME, request_name_ret = g_bus_own_name_on_connection (connection,
0, &request_name_ret, &error)) { FPRINT_SERVICE_NAME,
g_warning("Failed to get name: %s", error->message); G_BUS_NAME_OWNER_FLAGS_NONE,
g_object_unref (manager); on_name_acquired,
return 1; on_name_lost,
} loop, NULL);
if (request_name_ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
g_warning ("Got result code %u from requesting name", request_name_ret);
g_object_unref (manager);
return 1;
}
g_debug("D-Bus service launched with name: %s", FPRINT_SERVICE_NAME);
g_debug ("entering main loop"); g_debug ("entering main loop");
g_main_loop_run (loop); g_main_loop_run (loop);
g_bus_unown_name (request_name_ret);
g_debug ("main loop completed"); g_debug ("main loop completed");
g_object_unref (manager);
return 0; return 0;
} }

View File

@ -1,6 +1,7 @@
/* /*
* /net/reactivated/Fprint/Manager object implementation * /net/reactivated/Fprint/Manager object implementation
* Copyright (C) 2008 Daniel Drake <dsd@gentoo.org> * Copyright (C) 2008 Daniel Drake <dsd@gentoo.org>
* Copyright (C) 2020 Marco Trevisan <marco.trevisan@canonical.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -19,7 +20,6 @@
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
#include <dbus/dbus-glib-bindings.h>
#include <glib.h> #include <glib.h>
#include <glib/gi18n.h> #include <glib/gi18n.h>
#include <fprint.h> #include <fprint.h>
@ -27,47 +27,119 @@
#include "fprintd.h" #include "fprintd.h"
extern DBusGConnection *fprintd_dbus_conn; static void fprint_manager_constructed (GObject *object);
static gboolean fprint_manager_get_devices (FprintManager *manager, static gboolean fprint_manager_get_devices (FprintManager *manager,
GPtrArray **devices, GError **error); GPtrArray **devices,
GError **error);
static gboolean fprint_manager_get_default_device (FprintManager *manager, static gboolean fprint_manager_get_default_device (FprintManager *manager,
const char **device, GError **error); const char **device,
#include "manager-dbus-glue.h" GError **error);
typedef struct typedef struct
{ {
GDBusConnection *connection;
GDBusObjectManager *object_manager;
FprintDBusManager *dbus_manager;
FpContext *context; FpContext *context;
GSList *dev_registry;
gboolean no_timeout; gboolean no_timeout;
guint timeout_id; guint timeout_id;
} FprintManagerPrivate; } FprintManagerPrivate;
G_DEFINE_TYPE_WITH_CODE (FprintManager, fprint_manager, G_TYPE_OBJECT, G_ADD_PRIVATE (FprintManager)) G_DEFINE_TYPE_WITH_CODE (FprintManager, fprint_manager, G_TYPE_OBJECT, G_ADD_PRIVATE (FprintManager))
static void fprint_manager_finalize(GObject *object) enum {
PROP_0,
FPRINT_MANAGER_CONNECTION,
N_PROPS
};
static GParamSpec *properties[N_PROPS];
static void
fprint_manager_finalize (GObject *object)
{ {
FprintManagerPrivate *priv = fprint_manager_get_instance_private (FPRINT_MANAGER (object)); FprintManagerPrivate *priv = fprint_manager_get_instance_private (FPRINT_MANAGER (object));
g_clear_object (&priv->object_manager);
g_clear_object (&priv->dbus_manager);
g_clear_object (&priv->connection);
g_clear_object (&priv->context); g_clear_object (&priv->context);
g_slist_free(priv->dev_registry);
G_OBJECT_CLASS (fprint_manager_parent_class)->finalize (object); G_OBJECT_CLASS (fprint_manager_parent_class)->finalize (object);
} }
static void fprint_manager_class_init(FprintManagerClass *klass) static FprintDevice *
fprint_dbus_object_skeleton_get_device (FprintDBusObjectSkeleton *object)
{ {
dbus_g_object_type_install_info(FPRINT_TYPE_MANAGER, FprintDevice *rdev;
&dbus_glib_fprint_manager_object_info);
dbus_g_error_domain_register (FPRINT_ERROR, FPRINT_ERROR_DBUS_INTERFACE, FPRINT_TYPE_ERROR);
G_OBJECT_CLASS(klass)->finalize = fprint_manager_finalize; g_object_get (object, "device", &rdev, NULL);
return rdev;
} }
static gchar *get_device_path(FprintDevice *rdev) static void
fprint_manager_set_property (GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec)
{ {
return g_strdup_printf("/net/reactivated/Fprint/Device/%d", FprintManager *self = FPRINT_MANAGER (object);
FprintManagerPrivate *priv = fprint_manager_get_instance_private (self);
switch (property_id)
{
case FPRINT_MANAGER_CONNECTION:
priv->connection = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
fprint_manager_get_property (GObject *object, guint property_id,
GValue *value, GParamSpec *pspec)
{
FprintManager *self = FPRINT_MANAGER (object);
FprintManagerPrivate *priv = fprint_manager_get_instance_private (self);
switch (property_id)
{
case FPRINT_MANAGER_CONNECTION:
g_value_set_object (value, priv->connection);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
fprint_manager_class_init (FprintManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = fprint_manager_constructed;
object_class->set_property = fprint_manager_set_property;
object_class->get_property = fprint_manager_get_property;
object_class->finalize = fprint_manager_finalize;
properties[FPRINT_MANAGER_CONNECTION] =
g_param_spec_object ("connection",
"Connection",
"Set GDBus connection property",
G_TYPE_DBUS_CONNECTION,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static gchar *
get_device_path (FprintDevice *rdev)
{
return g_strdup_printf (FPRINT_SERVICE_PATH "/Device/%d",
_fprint_device_get_id (rdev)); _fprint_device_get_id (rdev));
} }
@ -84,19 +156,27 @@ fprint_manager_in_use_notified (FprintDevice *rdev, GParamSpec *spec, FprintMana
{ {
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
guint num_devices_used = 0; guint num_devices_used = 0;
GSList *l;
g_autolist (GDBusObject) devices = NULL;
GList *l;
gboolean in_use; gboolean in_use;
if (priv->timeout_id > 0) { if (priv->timeout_id > 0)
{
g_source_remove (priv->timeout_id); g_source_remove (priv->timeout_id);
priv->timeout_id = 0; priv->timeout_id = 0;
} }
if (priv->no_timeout) if (priv->no_timeout)
return; return;
for (l = priv->dev_registry; l != NULL; l = l->next) { devices = g_dbus_object_manager_get_objects (priv->object_manager);
FprintDevice *dev = l->data;
for (l = devices; l != NULL; l = l->next)
{
g_autoptr(FprintDevice) dev = NULL;
FprintDBusObjectSkeleton *object = l->data;
dev = fprint_dbus_object_skeleton_get_device (object);
g_object_get (G_OBJECT (dev), "in-use", &in_use, NULL); g_object_get (G_OBJECT (dev), "in-use", &in_use, NULL);
if (in_use != FALSE) if (in_use != FALSE)
num_devices_used++; num_devices_used++;
@ -106,45 +186,97 @@ fprint_manager_in_use_notified (FprintDevice *rdev, GParamSpec *spec, FprintMana
priv->timeout_id = g_timeout_add_seconds (TIMEOUT, (GSourceFunc) fprint_manager_timeout_cb, manager); priv->timeout_id = g_timeout_add_seconds (TIMEOUT, (GSourceFunc) fprint_manager_timeout_cb, manager);
} }
static gboolean
handle_get_devices (FprintManager *manager, GDBusMethodInvocation *invocation,
FprintDBusManager *skeleton)
{
g_autoptr(GPtrArray) devices = NULL;
g_autoptr(GError) error = NULL;
if (!fprint_manager_get_devices (manager, &devices, &error))
{
g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE;
}
fprint_dbus_manager_complete_get_devices (skeleton, invocation,
(const gchar *const *)
devices->pdata);
return TRUE;
}
static gboolean
handle_get_default_device (FprintManager *manager,
GDBusMethodInvocation *invocation,
FprintDBusManager *skeleton)
{
const gchar *device;
g_autoptr(GError) error = NULL;
if (!fprint_manager_get_default_device (manager, &device, &error))
{
g_dbus_method_invocation_return_gerror (invocation, error);
return TRUE;
}
fprint_dbus_manager_complete_get_default_device (skeleton, invocation,
device);
return TRUE;
}
static void static void
device_added_cb (FprintManager *manager, FpDevice *device, FpContext *context) device_added_cb (FprintManager *manager, FpDevice *device, FpContext *context)
{ {
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
FprintDevice *rdev = fprint_device_new(device);
g_autoptr(FprintDBusObjectSkeleton) object = NULL;
g_autoptr(FprintDevice) rdev = NULL;
g_autofree gchar *path = NULL; g_autofree gchar *path = NULL;
rdev = fprint_device_new (device);
g_signal_connect (G_OBJECT (rdev), "notify::in-use", g_signal_connect (G_OBJECT (rdev), "notify::in-use",
G_CALLBACK (fprint_manager_in_use_notified), manager); G_CALLBACK (fprint_manager_in_use_notified), manager);
priv->dev_registry = g_slist_prepend (priv->dev_registry, rdev);
path = get_device_path (rdev); path = get_device_path (rdev);
dbus_g_connection_register_g_object(fprintd_dbus_conn, path,
G_OBJECT(rdev)); object = fprint_dbus_object_skeleton_new (path);
fprint_dbus_object_skeleton_set_device (object,
FPRINT_DBUS_DEVICE (rdev));
g_dbus_object_manager_server_export (
G_DBUS_OBJECT_MANAGER_SERVER (priv->object_manager),
G_DBUS_OBJECT_SKELETON (object));
} }
static void static void
device_removed_cb (FprintManager *manager, FpDevice *device, FpContext *context) device_removed_cb (FprintManager *manager, FpDevice *device, FpContext *context)
{ {
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
GSList *item;
g_autofree gchar *path = NULL;
for (item = priv->dev_registry; item; item = item->next) { g_autolist (FprintDBusObjectSkeleton) objects = NULL;
FprintDevice *rdev; GList *item;
objects = g_dbus_object_manager_get_objects (priv->object_manager);
for (item = objects; item; item = item->next)
{
g_autoptr(FprintDevice) rdev = NULL;
g_autoptr(FpDevice) dev = NULL; g_autoptr(FpDevice) dev = NULL;
FprintDBusObjectSkeleton *object = item->data;
rdev = item->data; rdev = fprint_dbus_object_skeleton_get_device (object);
g_object_get (rdev, "dev", &dev, NULL); g_object_get (rdev, "dev", &dev, NULL);
if (dev != device) if (dev != device)
continue; continue;
priv->dev_registry = g_slist_delete_link (priv->dev_registry, item); g_dbus_object_manager_server_unexport (
G_DBUS_OBJECT_MANAGER_SERVER (priv->object_manager),
dbus_g_connection_unregister_g_object(fprintd_dbus_conn, G_OBJECT(rdev)); g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (rdev)));
g_signal_handlers_disconnect_by_data (rdev, manager); g_signal_handlers_disconnect_by_data (rdev, manager);
g_object_unref (rdev);
/* We cannot continue to iterate at this point, but we don't need to either */ /* We cannot continue to iterate at this point, but we don't need to either */
break; break;
@ -156,12 +288,37 @@ device_removed_cb (FprintManager *manager, FpDevice *device, FpContext *context)
} }
static void static void
fprint_manager_init (FprintManager *manager) fprint_manager_constructed (GObject *object)
{ {
FprintManager *manager = FPRINT_MANAGER (object);
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
GDBusObjectManagerServer *object_manager_server;
object_manager_server =
g_dbus_object_manager_server_new (FPRINT_SERVICE_PATH);
priv->object_manager = G_DBUS_OBJECT_MANAGER (object_manager_server);
priv->dbus_manager = fprint_dbus_manager_skeleton_new ();
priv->context = fp_context_new (); priv->context = fp_context_new ();
g_signal_connect_object (priv->dbus_manager,
"handle-get-devices",
G_CALLBACK (handle_get_devices),
manager,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->dbus_manager,
"handle-get-default-device",
G_CALLBACK (handle_get_default_device),
manager,
G_CONNECT_SWAPPED);
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (priv->dbus_manager),
priv->connection,
FPRINT_SERVICE_PATH "/Manager", NULL);
g_dbus_object_manager_server_set_connection (object_manager_server,
priv->connection);
/* And register the signals for initial enumeration and hotplug. */ /* And register the signals for initial enumeration and hotplug. */
g_signal_connect_object (priv->context, g_signal_connect_object (priv->context,
"device-added", "device-added",
@ -175,19 +332,26 @@ fprint_manager_init (FprintManager *manager)
manager, manager,
G_CONNECT_SWAPPED); G_CONNECT_SWAPPED);
/* Prepare everything by enumerating all devices. */ /* Prepare everything by enumerating all devices.
* This blocks the main loop until the existing devices are enumerated
*/
fp_context_enumerate (priv->context); fp_context_enumerate (priv->context);
dbus_g_connection_register_g_object(fprintd_dbus_conn, G_OBJECT_CLASS (fprint_manager_parent_class)->constructed (object);
"/net/reactivated/Fprint/Manager", G_OBJECT(manager));
} }
FprintManager *fprint_manager_new(gboolean no_timeout) static void
fprint_manager_init (FprintManager *manager)
{
}
FprintManager *
fprint_manager_new (GDBusConnection *connection, gboolean no_timeout)
{ {
FprintManagerPrivate *priv; FprintManagerPrivate *priv;
GObject *object; GObject *object;
object = g_object_new(FPRINT_TYPE_MANAGER, NULL); object = g_object_new (FPRINT_TYPE_MANAGER, "connection", connection, NULL);
priv = fprint_manager_get_instance_private (FPRINT_MANAGER (object)); priv = fprint_manager_get_instance_private (FPRINT_MANAGER (object));
priv->no_timeout = no_timeout; priv->no_timeout = no_timeout;
@ -197,46 +361,67 @@ FprintManager *fprint_manager_new(gboolean no_timeout)
return FPRINT_MANAGER (object); return FPRINT_MANAGER (object);
} }
static gboolean fprint_manager_get_devices(FprintManager *manager, static gboolean
fprint_manager_get_devices (FprintManager *manager,
GPtrArray **devices, GError **error) GPtrArray **devices, GError **error)
{ {
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
GSList *elem;
GSList *l; g_autolist (FprintDBusObjectSkeleton) objects = NULL;
GList *l;
int num_open; int num_open;
GPtrArray *devs; GPtrArray *devs;
elem = g_slist_reverse(g_slist_copy(priv->dev_registry)); objects = g_dbus_object_manager_get_objects (priv->object_manager);
num_open = g_slist_length(elem); objects = g_list_reverse (objects);
num_open = g_list_length (objects);
devs = g_ptr_array_sized_new (num_open); devs = g_ptr_array_sized_new (num_open);
if (num_open > 0) { if (num_open > 0)
for (l = elem; l != NULL; l = l->next) { {
FprintDevice *rdev = l->data; for (l = objects; l != NULL; l = l->next)
g_ptr_array_add(devs, get_device_path(rdev)); {
} g_autoptr(FprintDevice) rdev = NULL;
} FprintDBusObjectSkeleton *object = l->data;
const char *path;
g_slist_free(elem); rdev = fprint_dbus_object_skeleton_get_device (object);
path = g_dbus_interface_skeleton_get_object_path (
G_DBUS_INTERFACE_SKELETON (rdev));
g_ptr_array_add (devs, (char *) path);
}
}
g_ptr_array_add (devs, NULL);
*devices = devs; *devices = devs;
return TRUE; return TRUE;
} }
static gboolean fprint_manager_get_default_device(FprintManager *manager, static gboolean
fprint_manager_get_default_device (FprintManager *manager,
const char **device, GError **error) const char **device, GError **error)
{ {
FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager);
GSList *elem;;
g_autolist (FprintDBusObjectSkeleton) objects = NULL;
int num_open; int num_open;
elem = priv->dev_registry; objects = g_dbus_object_manager_get_objects (priv->object_manager);
num_open = g_slist_length(elem); num_open = g_list_length (objects);
if (num_open > 0) { if (num_open > 0)
*device = get_device_path (g_slist_last (elem)->data); {
g_autoptr(FprintDevice) rdev = NULL;
FprintDBusObjectSkeleton *object = g_list_last (objects)->data;
rdev = fprint_dbus_object_skeleton_get_device (object);
*device = g_dbus_interface_skeleton_get_object_path (
G_DBUS_INTERFACE_SKELETON (rdev));
return TRUE; return TRUE;
} else { }
else
{
g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_NO_SUCH_DEVICE, g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_NO_SUCH_DEVICE,
"No devices available"); "No devices available");
*device = NULL; *device = NULL;
@ -244,34 +429,29 @@ static gboolean fprint_manager_get_default_device(FprintManager *manager,
} }
} }
GQuark fprint_error_quark(void) GQuark
fprint_error_quark (void)
{ {
static GQuark quark = 0; static volatile gsize quark = 0;
if (!quark)
quark = g_quark_from_static_string("fprintd-error-quark"); if (g_once_init_enter (&quark))
return quark; {
g_autoptr(GEnumClass) errors_enum = NULL;
GQuark domain;
unsigned i;
domain = g_quark_from_static_string ("fprintd-error-quark");
errors_enum = g_type_class_ref (FPRINT_TYPE_ERROR);
for (i = 0; i < errors_enum->n_values; ++i)
{
GEnumValue *value = &errors_enum->values[i];
g_dbus_error_register_error (domain, value->value,
value->value_nick);
} }
#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } g_once_init_leave (&quark, domain);
GType
fprint_error_get_type (void)
{
static GType etype = 0;
if (etype == 0) {
static const GEnumValue values[] =
{
ENUM_ENTRY (FPRINT_ERROR_CLAIM_DEVICE, "ClaimDevice"),
ENUM_ENTRY (FPRINT_ERROR_ALREADY_IN_USE, "AlreadyInUse"),
ENUM_ENTRY (FPRINT_ERROR_INTERNAL, "Internal"),
ENUM_ENTRY (FPRINT_ERROR_PERMISSION_DENIED, "PermissionDenied"),
ENUM_ENTRY (FPRINT_ERROR_NO_ENROLLED_PRINTS, "NoEnrolledPrints"),
ENUM_ENTRY (FPRINT_ERROR_NO_ACTION_IN_PROGRESS, "NoActionInProgress"),
ENUM_ENTRY (FPRINT_ERROR_INVALID_FINGERNAME, "InvalidFingername"),
ENUM_ENTRY (FPRINT_ERROR_NO_SUCH_DEVICE, "NoSuchDevice"),
{ 0, 0, 0 }
};
etype = g_enum_register_static ("FprintError", values);
} }
return etype; return (GQuark) quark;
} }

View File

@ -5,8 +5,6 @@
]> ]>
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> <node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<interface name="net.reactivated.Fprint.Manager"> <interface name="net.reactivated.Fprint.Manager">
<annotation name="org.freedesktop.DBus.GLib.CSymbol"
value="fprint_manager" />
<!-- ************************************************************ --> <!-- ************************************************************ -->

View File

@ -1,30 +1,10 @@
fprintd_marshal = gnome.genmarshal('fprintd-marshal',
prefix: 'fprintd_marshal',
sources: 'fprintd-marshal.list',
valist_marshallers: true,
)
bash = find_program('bash') bash = find_program('bash')
dbus_binding_tool = find_program('dbus-binding-tool')
dbus_interfaces = ['Manager', 'Device'] dbus_interfaces = ['Manager', 'Device']
dbus_interfaces_files = [] dbus_interfaces_files = []
dbus_server_glue_sources = []
foreach interface_name: dbus_interfaces foreach interface_name: dbus_interfaces
interface = interface_name.to_lower() interface = interface_name.to_lower()
interface_file = interface + '.xml' interface_file = interface + '.xml'
glue_name = interface + '-dbus-glue.h'
dbus_server_glue_sources += custom_target(glue_name,
input: interface_file,
output: glue_name,
command: [
dbus_binding_tool,
'--prefix=fprint_' + interface,
'--mode=glib-server',
'--output=@OUTPUT@',
'@INPUT@',
])
dbus_interfaces_files += custom_target('dbus_interface_' + interface, dbus_interfaces_files += custom_target('dbus_interface_' + interface,
input: interface_file, input: interface_file,
output: 'net.reactivated.Fprint.@0@.xml'.format(interface_name), output: 'net.reactivated.Fprint.@0@.xml'.format(interface_name),
@ -34,14 +14,48 @@ foreach interface_name: dbus_interfaces
) )
endforeach endforeach
# NOTE: We should pass "--glib-min-required 2.64" but cannot
fprintd_dbus_sources_base = gnome.gdbus_codegen('fprintd-dbus',
sources: dbus_interfaces_files,
autocleanup: 'all',
interface_prefix: 'net.reactivated.Fprint.',
namespace: 'FprintDBus',
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',
sources: 'fprintd.h',
)
fprintd_deps = declare_dependency( fprintd_deps = declare_dependency(
include_directories: [ include_directories: [
include_directories('..'), include_directories('..'),
], ],
sources: [
fprintd_enum_files,
fprintd_dbus_sources,
],
dependencies: [ dependencies: [
dbus_glib_dep,
glib_dep, glib_dep,
gio_dep, gio_dep,
gio_unix_dep,
gmodule_dep, gmodule_dep,
libfprint_dep, libfprint_dep,
polkit_gobject_dep, polkit_gobject_dep,
@ -58,8 +72,6 @@ libfprintd_private = static_library('fprintd-private',
'device.c', 'device.c',
'fprintd.h', 'fprintd.h',
'manager.c', 'manager.c',
dbus_server_glue_sources,
fprintd_marshal,
], ],
dependencies: fprintd_deps, dependencies: fprintd_deps,
gnu_symbol_visibility: 'hidden', gnu_symbol_visibility: 'hidden',

View File

@ -28,12 +28,14 @@ typedef int (*storage_print_data_load)(FpDevice *dev,
typedef int (*storage_print_data_delete)(FpDevice *dev, typedef int (*storage_print_data_delete)(FpDevice *dev,
FpFinger finger, FpFinger finger,
const char *username); const char *username);
typedef GSList *(*storage_discover_prints)(FpDevice *dev, const char *username); typedef GSList *(*storage_discover_prints)(FpDevice *dev,
const char *username);
typedef GSList *(*storage_discover_users)(void); typedef GSList *(*storage_discover_users)(void);
typedef int (*storage_init)(void); typedef int (*storage_init)(void);
typedef int (*storage_deinit)(void); typedef int (*storage_deinit)(void);
struct storage { struct storage
{
storage_init init; storage_init init;
storage_deinit deinit; storage_deinit deinit;
storage_print_data_save print_data_save; storage_print_data_save print_data_save;

View File

@ -0,0 +1,4 @@
leak:initialize_device
leak:usbi_alloc_device
leak:libusb-1.0.so.*
leak:PyMem_RawMalloc

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
@ -93,8 +95,9 @@ def GetDefaultDevice(self):
return devices[0] return devices[0]
@dbus.service.method(MANAGER_MOCK_IFACE, @dbus.service.method(MANAGER_MOCK_IFACE,
in_signature='sis', out_signature='s') in_signature='sisb', out_signature='s')
def AddDevice(self, device_name, num_enroll_stages, scan_type): def AddDevice(self, device_name, num_enroll_stages, scan_type,
has_identification=False):
'''Convenience method to add a fingerprint reader device '''Convenience method to add a fingerprint reader device
You have to specify a device name, the number of enrollment You have to specify a device name, the number of enrollment
@ -139,12 +142,27 @@ def AddDevice(self, device_name, num_enroll_stages, scan_type):
device = mockobject.objects[path] device = mockobject.objects[path]
device.fingers = {} device.fingers = {}
device.has_identification = has_identification
device.claimed_user = None device.claimed_user = None
device.action = None device.action = None
device.selected_finger = None
device.verify_script = [] device.verify_script = []
return path return path
@dbus.service.method(MANAGER_MOCK_IFACE,
in_signature='o')
def RemoveDevice(self, path):
# This isn't compatible with hotplugging devices, which fprintd doesn't
# support yet, but it's meant to remove devices added to the mock for
# testing purposes.
if not path:
raise dbus.exceptions.DBusException(
'Invalid empty path.',
name='org.freedesktop.DBus.Error.InvalidArgs')
self.RemoveObject(path)
@dbus.service.method(DEVICE_IFACE, @dbus.service.method(DEVICE_IFACE,
in_signature='s', out_signature='as') in_signature='s', out_signature='as')
def ListEnrolledFingers(device, user): def ListEnrolledFingers(device, user):
@ -186,6 +204,8 @@ def Release(device):
'Device was not claimed before use', 'Device was not claimed before use',
name='net.reactivated.Fprint.Error.ClaimDevice') name='net.reactivated.Fprint.Error.ClaimDevice')
device.claimed_user = None device.claimed_user = None
device.action = None
device.selected_finger = None
def can_verify_finger(device, finger_name): def can_verify_finger(device, finger_name):
# We should already have checked that there are enrolled fingers # We should already have checked that there are enrolled fingers
@ -195,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
@ -228,15 +261,47 @@ def VerifyStart(device, finger_name):
name='net.reactivated.Fprint.Error.AlreadyInUse') name='net.reactivated.Fprint.Error.AlreadyInUse')
device.action = 'verify' device.action = 'verify'
if finger_name == 'any': 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.EmitSignal(DEVICE_IFACE, 'VerifyFingerSelected', 's', [ device.selected_finger = finger_name
finger_name # Needs to happen after method return
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='')
@ -262,6 +327,7 @@ def VerifyStop(device):
'No verification to stop', 'No verification to stop',
name='net.reactivated.Fprint.Error.NoActionInProgress') name='net.reactivated.Fprint.Error.NoActionInProgress')
device.action = None device.action = None
device.selected_finger = None
@dbus.service.method(DEVICE_IFACE, @dbus.service.method(DEVICE_IFACE,
in_signature='s', out_signature='') in_signature='s', out_signature='')
@ -317,11 +383,6 @@ def SetEnrolledFingers(device, user, fingers):
Returns nothing. Returns nothing.
''' '''
if len(fingers) < 1:
raise dbus.exceptions.DBusException(
'Fingers array must not be empty',
name='org.freedesktop.DBus.Error.InvalidArgs')
for k in fingers: for k in fingers:
if k not in VALID_FINGER_NAMES: if k not in VALID_FINGER_NAMES:
raise dbus.exceptions.DBusException( raise dbus.exceptions.DBusException(
@ -330,6 +391,30 @@ def SetEnrolledFingers(device, user, fingers):
device.fingers[user] = fingers device.fingers[user] = fingers
@dbus.service.method(DEVICE_MOCK_IFACE,
in_signature='', out_signature='s')
def GetSelectedFinger(device):
'''Convenience method to get the finger under verification
Returns the finger name that the user has selected for verifying
'''
if not device.selected_finger:
raise dbus.exceptions.DBusException(
'Device is not verifying',
name='net.reactivated.Fprint.Error.NoActionInProgress')
return device.selected_finger
@dbus.service.method(DEVICE_MOCK_IFACE,
in_signature='', out_signature='b')
def HasIdentification(device):
'''Convenience method to get if a device supports identification
Returns whether identification is supported.
'''
return device.has_identification
@dbus.service.method(DEVICE_MOCK_IFACE, @dbus.service.method(DEVICE_MOCK_IFACE,
in_signature='a(sbi)', out_signature='') in_signature='a(sbi)', out_signature='')
def SetVerifyScript(device, script): def SetVerifyScript(device, script):
@ -344,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

113
tests/dbusmock/polkitd.py Normal file
View File

@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
'''polkit mock template
This creates the basic methods and properties of the
org.freedesktop.PolicyKit1.Authority object, so that we can use it async
'''
# 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 3 of the License, or (at your option) any
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
# of the license.
__author__ = 'Marco Trevisan'
__email__ = 'marco.trevisan@canonical.com'
__copyright__ = '(c) 2020 Canonical Ltd.'
__license__ = 'LGPL 3+'
import dbus
import time
from dbusmock import MOCK_IFACE, mockobject
BUS_NAME = 'org.freedesktop.PolicyKit1'
MAIN_OBJ = '/org/freedesktop/PolicyKit1/Authority'
MAIN_IFACE = 'org.freedesktop.PolicyKit1.Authority'
SYSTEM_BUS = True
IS_OBJECT_MANAGER = False
def load(mock, parameters):
polkitd = mockobject.objects[MAIN_OBJ]
# default state
polkitd.allow_unknown = False
polkitd.allowed = []
polkitd.delay = 0
polkitd.simulate_hang = False
polkitd.hanging_actions = []
polkitd.hanging_calls = []
mock.AddProperties(MAIN_IFACE,
dbus.Dictionary({
'BackendName': 'local',
'BackendVersion': '0.8.15',
'BackendFeatures': dbus.UInt32(1, variant_level=1),
}, signature='sv'))
@dbus.service.method(MAIN_IFACE,
in_signature='(sa{sv})sa{ss}us', out_signature='(bba{ss})',
async_callbacks=('ok_cb', 'err_cb'))
def CheckAuthorization(self, subject, action_id, details, flags, cancellation_id,
ok_cb, err_cb):
time.sleep(self.delay)
allowed = action_id in self.allowed or self.allow_unknown
ret = (allowed, False, {'test': 'test'})
if self.simulate_hang or action_id in self.hanging_actions:
self.hanging_calls.append((ok_cb, ret))
else:
ok_cb(ret)
@dbus.service.method(MOCK_IFACE, in_signature='b', out_signature='')
def AllowUnknown(self, default):
'''Control whether unknown actions are allowed
This controls the return value of CheckAuthorization for actions which were
not explicitly allowed by SetAllowed().
'''
self.allow_unknown = default
@dbus.service.method(MOCK_IFACE, in_signature='d', out_signature='')
def SetDelay(self, delay):
'''Makes the CheckAuthorization() method to delay'''
self.delay = delay
@dbus.service.method(MOCK_IFACE, in_signature='b', out_signature='')
def SimulateHang(self, hang):
'''Makes the CheckAuthorization() method to hang'''
self.simulate_hang = hang
@dbus.service.method(MOCK_IFACE, in_signature='as', out_signature='')
def SimulateHangActions(self, actions):
'''Makes the CheckAuthorization() method to hang on such actions'''
self.hanging_actions = actions
@dbus.service.method(MOCK_IFACE, in_signature='', out_signature='')
def ReleaseHangingCalls(self):
'''Calls all the hanging callbacks'''
for (cb, ret) in self.hanging_calls:
cb(ret)
self.hanging_calls = []
@dbus.service.method(MOCK_IFACE, in_signature='', out_signature='b')
def HaveHangingCalls(self):
'''Check if we've hangling calls'''
return len(self.hanging_calls)
@dbus.service.method(MOCK_IFACE, in_signature='as', out_signature='')
def SetAllowed(self, actions):
'''Set allowed actions'''
self.allowed = actions
@dbus.service.method(MAIN_IFACE,
in_signature='', out_signature='o')
def GetDefaultDevice(self):
devices = self.GetDevices()
if len(devices) < 1:
raise dbus.exceptions.DBusException(
'No devices available',
name='net.reactivated.Fprint.Error.NoSuchDevice')
return devices[0]

1209
tests/fprintd.py Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,118 @@
# Add a way to discover and run python unit tests separately
# https://github.com/mesonbuild/meson/issues/6851
python_tests = [
# List all the python tests, must be in the form:
# {
# 'name': 'test name',
# 'file': 'full test file path, use files('path')[0]',
# Fields below are optional:
# 'workdir': '',
# 'env': [],
# 'depends': [],
# 'suite': [],
# 'extra_args': [],
# 'timeout': 30,
# 'is_parallel': true,
# }
]
address_sanitizer = get_option('b_sanitize') == 'address'
tests = [ tests = [
'fprintd', 'fprintd',
'test_fprintd_utils', 'test_fprintd_utils',
] ]
foreach t: tests foreach t: tests
test(t, python_tests += [
python3, {
args: meson.current_source_dir() / t + '.py', 'name': t,
suite: ['daemon'], 'file': files(meson.current_source_dir() / t + '.py')[0],
depends: [ 'env': [
fprintd,
fprintd_utils,
],
env: [
'G_DEBUG=fatal-criticals', 'G_DEBUG=fatal-criticals',
'G_MESSAGES_DEBUG=all', 'G_MESSAGES_DEBUG=all',
'FPRINT_BUILD_DIR=' + meson.build_root() / 'src', 'FPRINT_BUILD_DIR=' + meson.build_root() / 'src',
'TOPSRCDIR=' + meson.source_root(), 'TOPSRCDIR=' + meson.source_root(),
], ],
'depends': [
fprintd,
fprintd_utils,
],
'suite': [t == 'fprintd' ? 'daemon' : ''],
}
]
endforeach
if get_option('pam')
subdir('pam')
endif
# Add a way to discover and run python unit tests separately
# https://github.com/mesonbuild/meson/issues/6851
unittest_inspector = find_program('unittest_inspector.py')
foreach pt: python_tests
r = run_command(unittest_inspector, pt.get('file'))
unit_tests = r.stdout().strip().split('\n')
base_args = [ pt.get('file') ] + pt.get('extra_args', [])
suite = pt.get('suite', [])
if r.returncode() == 0 and unit_tests.length() > 0
suite += pt.get('name')
else
unit_tests = [pt.get('name')]
endif
foreach ut: unit_tests
ut_suite = suite
ut_args = base_args
if unit_tests.length() > 1
ut_args += ut
ut_suite += ut.split('.')[0]
endif
test(ut,
python3,
args: ut_args,
suite: ut_suite,
depends: pt.get('depends', []),
workdir: pt.get('workdir', meson.build_root()),
env: pt.get('env', []),
timeout: pt.get('timeout', 30),
is_parallel: pt.get('is_parallel', true),
) )
endforeach endforeach
endforeach
timeout_multiplier = 1
test_envs = [
'G_SLICE=always-malloc',
'MALLOC_CHECK_=2',
]
if address_sanitizer
timeout_multiplier = 3
test_envs += [
'ADDRESS_SANITIZER=true',
'ASAN_OPTIONS=@0@'.format(':'.join([
'abort_on_error=true',
'symbolize=true',
])),
'LSAN_OPTIONS=@0@'.format(':'.join([
'exitcode=0',
'strict_string_checks=true',
'suppressions=@0@'.format(
files(meson.current_source_dir() / 'LSAN-leaks-suppress.txt')[0]),
])),
]
endif
add_test_setup('default_setup', add_test_setup('default_setup',
is_default: true, is_default: true,
env: [ env: test_envs,
'G_SLICE=always-malloc', timeout_multiplier: timeout_multiplier
'MALLOC_CHECK_=2',
'MALLOC_PERTURB_=55',
],
) )
if find_program('valgrind', required: false).found() if not address_sanitizer and find_program('valgrind', required: false).found()
glib_share = glib_dep.get_pkgconfig_variable('prefix') / 'share' / glib_dep.name() glib_share = glib_dep.get_pkgconfig_variable('prefix') / 'share' / glib_dep.name()
glib_suppressions = glib_share + '/valgrind/glib.supp' glib_suppressions = glib_share + '/valgrind/glib.supp'
add_test_setup('valgrind', add_test_setup('valgrind',
@ -42,6 +124,3 @@ if find_program('valgrind', required: false).found()
) )
endif endif
if get_option('pam')
subdir('pam')
endif

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

@ -4,24 +4,56 @@ tests = [
'test_pam_fprintd', 'test_pam_fprintd',
] ]
preloaded_libs = []
pam_tests_ld_preload = []
if address_sanitizer
# ASAN has to be the first in list
preloaded_libs += 'asan'
endif
preloaded_libs += 'pam_wrapper'
foreach libname: preloaded_libs
lib = run_command(meson.get_compiler('c'),
'-print-file-name=lib@0@.so'.format(libname)
).stdout().strip()
# Support linker script files
if run_command('grep', '-qI', '^INPUT', files(lib)).returncode() == 0
out = run_command('cat', lib).stdout()
lib = out.split('(')[1].split(')')[0].strip()
endif
if lib != '' and lib[0] == '/'
message('Found library @0@ as @1@'.format(libname, lib))
pam_tests_ld_preload += '@0@'.format(files(lib)[0])
else
tests = []
warning('No library found for ' + libname + ', skipping PAM tests')
endif
endforeach
foreach t: tests foreach t: tests
test(t, python_tests += [
python3, {
args: meson.current_source_dir() / t + '.py', 'name': t,
suite: ['PAM'], 'file': files(meson.current_source_dir() / t + '.py')[0],
depends: [ 'is_parallel': false,
pam_fprintd, 'env': [
pam_service_file,
],
env: [
'TOPBUILDDIR=' + meson.build_root(), 'TOPBUILDDIR=' + meson.build_root(),
'TOPSRCDIR=' + meson.source_root(), 'TOPSRCDIR=' + meson.source_root(),
'LD_PRELOAD=libpam_wrapper.so', 'LD_PRELOAD=' + ' '.join(pam_tests_ld_preload),
'PAM_WRAPPER=1', 'PAM_WRAPPER=1',
'PAM_WRAPPER_DEBUGLEVEL=2', 'PAM_WRAPPER_DEBUGLEVEL=2',
'PAM_WRAPPER_SERVICE_DIR=' + meson.current_build_dir() / 'services', 'PAM_WRAPPER_SERVICE_DIR=' + meson.current_build_dir() / 'services',
'G_DEBUG=fatal-warnings', 'G_DEBUG=fatal-warnings',
], ],
timeout: 60, 'depends': [
) pam_fprintd,
pam_service_file,
],
'suite': ['PAM'],
}
]
endforeach endforeach

View File

@ -16,15 +16,13 @@ import unittest
import sys import sys
import subprocess import subprocess
import dbus import dbus
import dbus.mainloop.glib
import dbusmock import dbusmock
import fcntl import glob
import os import os
import shutil
import time import time
import pypamtest import pypamtest
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
PAM_SUCCESS = 0 PAM_SUCCESS = 0
PAM_AUTH_ERR = 7 PAM_AUTH_ERR = 7
PAM_AUTHINFO_UNAVAIL = 9 PAM_AUTHINFO_UNAVAIL = 9
@ -71,12 +69,12 @@ class TestPamFprintd(dbusmock.DBusTestCase):
def tearDownClass(klass): def tearDownClass(klass):
klass.stop_monitor() klass.stop_monitor()
# Remove pam wrapper files, as they may break other tests
[shutil.rmtree(i) for i in glob.glob('/tmp/pam.[0-9A-z]')]
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):
@ -88,6 +86,50 @@ class TestPamFprintd(dbusmock.DBusTestCase):
self.device_mock = self.dbus_con.get_object('net.reactivated.Fprint', device_path) self.device_mock = self.dbus_con.get_object('net.reactivated.Fprint', device_path)
self.device_mock.SetEnrolledFingers('toto', ['left-little-finger', 'right-little-finger']) self.device_mock.SetEnrolledFingers('toto', ['left-little-finger', 'right-little-finger'])
def test_pam_no_device(self):
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_identify_error(self):
self.setup_device()
script = [
( 'verify-unknown-error', True, 2 )
]
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.assertRegex(res.info[0], r'Swipe your left little finger across the fingerprint reader')
self.assertEqual(len(res.errors), 0)
def test_pam_fprintd_identify_error2(self):
self.setup_device()
script = [
( 'verify-disconnected', True, 2 )
]
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.assertRegex(res.info[0], r'Swipe your left little finger across the fingerprint reader')
self.assertEqual(len(res.errors), 0)
def test_pam_fprintd_identify_error3(self):
self.setup_device()
script = [
( 'verify-INVALID', True, 2 )
]
self.device_mock.SetVerifyScript(script)
tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_AUTH_ERR)
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), 1)
self.assertRegex(res.errors[0], r'An unknown error occurred')
def test_pam_fprintd_auth(self): def test_pam_fprintd_auth(self):
self.setup_device() self.setup_device()
script = [ script = [
@ -101,6 +143,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)
@ -154,6 +256,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()
@ -161,6 +277,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,13 +18,27 @@ 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 = [
'left-thumb',
'left-index-finger',
'left-middle-finger',
'left-ring-finger',
'left-little-finger',
'right-thumb',
'right-index-finger',
'right-middle-finger',
'right-ring-finger',
'right-little-finger'
]
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
class TestFprintd(dbusmock.DBusTestCase): class TestFprintdUtilsBase(dbusmock.DBusTestCase):
'''Test fprintd utilities''' '''Test fprintd utilities'''
@classmethod @classmethod
@ -57,130 +71,231 @@ class TestFprintd(dbusmock.DBusTestCase):
if os.path.exists(valgrind): if os.path.exists(valgrind):
klass.wrapper_args += ['--suppressions={}'.format(valgrind)] klass.wrapper_args += ['--suppressions={}'.format(valgrind)]
if 'ADDRESS_SANITIZER' in os.environ:
klass.sleep_time *= 2
def setUp(self): def setUp(self):
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):
self.p_mock.terminate() self.p_mock.terminate()
self.p_mock.wait() self.p_mock.wait()
super().tearDown()
def setup_device(self): def setup_device(self):
device_path = self.obj_fprintd_mock.AddDevice('FDO Trigger Finger Laser Reader', 3, 'swipe') self.device_path = self.obj_fprintd_mock.AddDevice(
self.device_mock = self.dbus_con.get_object('net.reactivated.Fprint', device_path) 'FDO Trigger Finger Laser Reader', 3, 'swipe')
self.device_mock.SetEnrolledFingers('toto', ['left-little-finger', 'right-little-finger']) self.device_mock = self.dbus_con.get_object('net.reactivated.Fprint',
self.device_path)
self.set_enrolled_fingers(['left-little-finger', 'right-little-finger'])
def set_enrolled_fingers(self, fingers, user='toto'):
self.enrolled_fingers = fingers
self.device_mock.SetEnrolledFingers('toto', self.enrolled_fingers,
signature='sas')
def start_utility_process(self, utility_name, args=[], sleep=True):
utility = [ os.path.join(self.tools_prefix, 'fprintd-{}'.format(utility_name)) ]
output = OutputChecker()
process = subprocess.Popen(self.wrapper_args + utility + args,
stdout=output.fd,
stderr=subprocess.STDOUT)
output.writer_attached()
self.addCleanup(output.assert_closed)
self.addCleanup(self.try_stop_utility_process, process)
if sleep:
time.sleep(self.sleep_time)
return process, output
def stop_utility_process(self, process):
process.terminate()
process.wait()
def try_stop_utility_process(self, process):
try:
self.stop_utility_process(process)
except:
pass
def run_utility_process(self, utility_name, args=[], sleep=True, timeout=None):
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)
self.assertLessEqual(ret, 128)
return b''.join(output.clear()), ret
class TestFprintdUtils(TestFprintdUtilsBase):
def setUp(self):
super().setUp()
self.setup_device()
def test_fprintd_enroll(self): def test_fprintd_enroll(self):
self.setup_device() process, out = self.start_utility_process('enroll', ['-f', 'right-index-finger', 'toto'])
mock_log = tempfile.NamedTemporaryFile() out.check_line(rb'right-index-finger', 0)
process = subprocess.Popen(self.wrapper_args + [self.tools_prefix + 'fprintd-enroll', '-f', 'right-index-finger', 'toto'],
stdout=mock_log,
stderr=subprocess.STDOUT,
universal_newlines=True)
time.sleep(self.sleep_time)
with open(mock_log.name) as f:
out = f.read()
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)
with open(mock_log.name) as f: out.check_line(rb'Enroll result: enroll-completed', self.sleep_time)
out = f.read()
self.assertRegex(out, 'Enroll result: enroll-completed') def test_fprintd_list(self):
# Rick has no fingerprints enrolled
out, ret = self.run_utility_process('list', ['rick'])
self.assertRegex(out, rb'has no fingers enrolled for')
self.assertEqual(ret, 0)
# Toto does
out, ret = self.run_utility_process('list', ['toto'])
self.assertRegex(out, rb'right-little-finger')
self.assertEqual(ret, 0)
def test_fprintd_delete(self):
# Has fingerprints enrolled
out, ret = self.run_utility_process('list', ['toto'])
self.assertRegex(out, rb'left-little-finger')
self.assertEqual(ret, 0)
self.assertRegex(out, rb'right-little-finger')
# Delete fingerprints
out, ret = self.run_utility_process('delete', ['toto'])
self.assertRegex(out, rb'Fingerprints deleted')
self.assertEqual(ret, 0)
# Doesn't have fingerprints
out, ret = self.run_utility_process('list', ['toto'])
self.assertRegex(out, rb'has no fingers enrolled for')
self.assertEqual(ret, 0)
class TestFprintdUtilsNoDeviceTests(TestFprintdUtilsBase):
def test_fprintd_enroll(self):
out, ret = self.run_utility_process('enroll', ['toto'])
self.assertIn(b'No devices available', out)
self.assertEqual(ret, 1)
def test_fprintd_list(self):
out, ret = self.run_utility_process('list', ['toto'])
self.assertIn(b'No devices available', out)
self.assertEqual(ret, 1)
def test_fprintd_delete(self):
out, ret = self.run_utility_process('delete', ['toto'])
self.assertIn(b'No devices available', out)
self.assertEqual(ret, 1)
def test_fprintd_verify(self): def test_fprintd_verify(self):
out, ret = self.run_utility_process('verify', ['toto'])
self.assertIn(b'No devices available', out)
self.assertEqual(ret, 1)
class TestFprintdUtilsVerify(TestFprintdUtilsBase):
def setUp(self):
super().setUp()
self.setup_device() self.setup_device()
mock_log = tempfile.NamedTemporaryFile() def start_verify_process(self, user='toto', finger=None, nowait=False):
process = subprocess.Popen(self.wrapper_args + [self.tools_prefix + 'fprintd-verify', 'toto'], args = [user]
stdout=mock_log, if finger:
stderr=subprocess.STDOUT, args += ['-f', finger]
universal_newlines=True)
time.sleep(self.sleep_time) self.process, self.output = self.start_utility_process('verify', args)
if nowait:
return
with open(mock_log.name) as f: preamble = self.output.check_line(b'Verify started!')
out = f.read()
self.assertRegex(out, r'left-little-finger') out = b''.join(preamble)
self.assertNotRegex(out, 'Verify result: verify-match \(done\)')
self.assertNotIn(b'Verify result:', out)
if finger:
expected_finger = finger
if finger == 'any' and not self.device_mock.HasIdentification():
expected_finger = self.enrolled_fingers[0]
self.assertEqual(self.device_mock.GetSelectedFinger(), expected_finger)
def assertVerifyMatch(self, match):
self.output.check_line(r'Verify result: {} (done)'.format(
'verify-match' if match else 'verify-no-match'))
def test_fprintd_verify(self):
self.start_verify_process()
self.device_mock.EmitVerifyStatus('verify-match', True) self.device_mock.EmitVerifyStatus('verify-match', True)
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
self.assertVerifyMatch(True)
with open(mock_log.name) as f: def test_fprintd_verify_enrolled_fingers(self):
out = f.read() for finger in self.enrolled_fingers:
self.assertRegex(out, 'Verify result: verify-match \(done\)') self.start_verify_process(finger=finger)
self.device_mock.EmitVerifyStatus('verify-match', True)
time.sleep(self.sleep_time)
self.assertVerifyMatch(True)
def test_fprintd_verify_any_finger_no_identification(self):
self.start_verify_process(finger='any')
self.device_mock.EmitVerifyStatus('verify-match', True)
time.sleep(self.sleep_time)
self.assertVerifyMatch(True)
def test_fprintd_verify_any_finger_identification(self):
self.obj_fprintd_mock.RemoveDevice(self.device_path)
self.device_path = self.obj_fprintd_mock.AddDevice('Full powered device',
3, 'press', True)
self.device_mock = self.dbus_con.get_object('net.reactivated.Fprint',
self.device_path)
self.set_enrolled_fingers(VALID_FINGER_NAMES)
self.start_verify_process(finger='any')
self.device_mock.EmitVerifyStatus('verify-match', True)
time.sleep(self.sleep_time)
self.assertVerifyMatch(True)
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]:
self.start_verify_process(finger=finger, nowait=True)
regex = r'Finger \'{}\' not enrolled'.format(finger)
self.output.check_line_re(regex, timeout=self.sleep_time)
self.device_mock.Release()
def test_fprintd_verify_no_enrolled_fingers(self):
self.set_enrolled_fingers([])
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)
def test_fprintd_list_all_fingers(self):
self.set_enrolled_fingers(VALID_FINGER_NAMES)
self.start_verify_process()
def test_fprintd_verify_script(self): def test_fprintd_verify_script(self):
self.setup_device()
script = [ script = [
( 'verify-match', True, 2 ) ( 'verify-match', True, 2 )
] ]
self.device_mock.SetVerifyScript(script) self.device_mock.SetVerifyScript(script)
mock_log = tempfile.NamedTemporaryFile()
process = subprocess.Popen(self.wrapper_args + [self.tools_prefix + 'fprintd-verify', 'toto'],
stdout=mock_log,
stderr=subprocess.STDOUT,
universal_newlines=True)
time.sleep(self.sleep_time)
with open(mock_log.name) as f:
out = f.read()
self.assertRegex(out, r'left-little-finger')
self.assertNotRegex(out, 'Verify result: verify-match \(done\)')
time.sleep(2) time.sleep(2)
with open(mock_log.name) as f: self.start_verify_process()
out = f.read() time.sleep(2 + self.sleep_time)
self.assertRegex(out, 'Verify result: verify-match \(done\)') self.assertVerifyMatch(True)
def test_fprintd_list(self): def test_fprintd_multiple_verify_fails(self):
self.setup_device() self.start_verify_process()
# Rick has no fingerprints enrolled self.start_verify_process(nowait=True)
out = subprocess.check_output(self.wrapper_args + [self.tools_prefix + 'fprintd-list', 'rick'], self.output.check_line_re(rb'Device already in use by [A-z]+', timeout=self.sleep_time)
stderr=subprocess.STDOUT,
universal_newlines=True)
self.assertRegex(out, r'has no fingers enrolled for')
# Toto does
out = subprocess.check_output(self.wrapper_args + [self.tools_prefix + 'fprintd-list', 'toto'],
universal_newlines=True)
self.assertRegex(out, r'left-little-finger')
self.assertRegex(out, r'right-little-finger')
def test_fprintd_delete(self):
self.setup_device()
# Has fingerprints enrolled
out = subprocess.check_output(self.wrapper_args + [self.tools_prefix + 'fprintd-list', 'toto'],
stderr=subprocess.STDOUT,
universal_newlines=True)
self.assertRegex(out, r'left-little-finger')
self.assertRegex(out, r'right-little-finger')
# Delete fingerprints
out = subprocess.check_output(self.wrapper_args + [self.tools_prefix + 'fprintd-delete', 'toto'],
stderr=subprocess.STDOUT,
universal_newlines=True)
self.assertRegex(out, r'Fingerprints deleted')
# Doesn't have fingerprints
out = subprocess.check_output(self.wrapper_args + [self.tools_prefix + 'fprintd-list', 'toto'],
stderr=subprocess.STDOUT,
universal_newlines=True)
self.assertRegex(out, r'has no fingers enrolled for')
if __name__ == '__main__': if __name__ == '__main__':
# avoid writing to stderr # avoid writing to stderr

46
tests/unittest_inspector.py Executable file
View File

@ -0,0 +1,46 @@
#! /usr/bin/env python3
# Copyright © 2020, Canonical Ltd
#
# 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:
# Marco Trevisan <marco.trevisan@canonical.com>
import argparse
import importlib.util
import inspect
import os
import unittest
def list_tests(module):
tests = []
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, unittest.TestCase):
cases = unittest.defaultTestLoader.getTestCaseNames(obj)
tests += [ (obj, '{}.{}'.format(name, t)) for t in cases ]
return tests
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('unittest_source', type=argparse.FileType('r'))
args = parser.parse_args()
source_path = args.unittest_source.name
spec = importlib.util.spec_from_file_location(
os.path.basename(source_path), source_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
for machine, human in list_tests(module):
print(human)

View File

@ -1,6 +1,7 @@
/* /*
* fprintd example to delete fingerprints * fprintd example to delete fingerprints
* Copyright (C) 2008 Daniel Drake <dsd@gentoo.org> * Copyright (C) 2008 Daniel Drake <dsd@gentoo.org>
* Copyright (C) 2020 Marco Trevisan <marco.trevisan@canonical.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -20,117 +21,146 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <locale.h> #include <locale.h>
#include <dbus/dbus-glib-bindings.h> #include "fprintd-dbus.h"
#include "manager-dbus-glue.h"
#include "device-dbus-glue.h"
static DBusGProxy *manager = NULL; static FprintDBusManager *manager = NULL;
static DBusGConnection *connection = NULL; static GDBusConnection *connection = NULL;
static void create_manager(void) static void
create_manager (void)
{ {
GError *error = NULL; g_autoptr(GError) error = NULL;
connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
if (connection == NULL) { if (connection == NULL)
{
g_print ("Failed to connect to session bus: %s\n", error->message); g_print ("Failed to connect to session bus: %s\n", error->message);
exit (1); exit (1);
} }
manager = dbus_g_proxy_new_for_name(connection, manager = fprint_dbus_manager_proxy_new_sync (connection,
"net.reactivated.Fprint", "/net/reactivated/Fprint/Manager", G_DBUS_PROXY_FLAGS_NONE,
"net.reactivated.Fprint.Manager"); "net.reactivated.Fprint",
} "/net/reactivated/Fprint/Manager",
NULL, &error);
static void delete_fingerprints(DBusGProxy *dev, const char *username) if (manager == NULL)
{ {
GError *error = NULL; g_print ("Failed to get Fprintd manager: %s\n", error->message);
GHashTable *props;
DBusGProxy *p;
p = dbus_g_proxy_new_from_proxy(dev, "org.freedesktop.DBus.Properties", NULL);
if (!dbus_g_proxy_call (p, "GetAll", &error, G_TYPE_STRING, "net.reactivated.Fprint.Device", G_TYPE_INVALID,
dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), &props, G_TYPE_INVALID)) {
g_print("GetAll on the Properties interface failed: %s\n", error->message);
exit (1); exit (1);
} }
}
if (!net_reactivated_Fprint_Device_claim(dev, username, &error)) { static void
delete_fingerprints (FprintDBusDevice *dev, const char *username)
{
g_autoptr(GError) error = NULL;
if (!fprint_dbus_device_call_claim_sync (dev, username, NULL, &error))
{
g_print ("failed to claim device: %s\n", error->message); g_print ("failed to claim device: %s\n", error->message);
exit (1); exit (1);
} }
if (!net_reactivated_Fprint_Device_delete_enrolled_fingers2(dev, &error)) { if (!fprint_dbus_device_call_delete_enrolled_fingers2_sync (dev, NULL,
if (dbus_g_error_has_name (error, "net.reactivated.Fprint.Error.NoEnrolledPrints") == FALSE) { &error))
g_print("ListEnrolledFingers failed: %s\n", error->message); {
gboolean ignore_error = FALSE;
if (g_dbus_error_is_remote_error (error))
{
g_autofree char *dbus_error =
g_dbus_error_get_remote_error (error);
if (g_str_equal (dbus_error,
"net.reactivated.Fprint.Error.NoEnrolledPrints"))
{
g_print ("No fingerprints to delete on %s\n",
fprint_dbus_device_get_name (dev));
ignore_error = TRUE;
}
}
if (!ignore_error)
{
g_print ("ListEnrolledFingers failed: %s\n",
error->message);
exit (1); exit (1);
} else {
g_print ("No fingerprints to delete on %s\n", g_value_get_string (g_hash_table_lookup (props, "name")));
} }
} else { else
g_print ("Fingerprints deleted on %s\n", g_value_get_string (g_hash_table_lookup (props, "name"))); {
g_print ("No fingerprints to delete on %s\n",
fprint_dbus_device_get_name (dev));
} }
}
else
{
g_print ("Fingerprints deleted on %s\n",
fprint_dbus_device_get_name (dev));
}
g_clear_error (&error);
if (!net_reactivated_Fprint_Device_release(dev, &error)) { if (!fprint_dbus_device_call_release_sync (dev, NULL, &error))
{
g_print ("ReleaseDevice failed: %s\n", error->message); g_print ("ReleaseDevice failed: %s\n", error->message);
exit (1); exit (1);
} }
g_hash_table_destroy (props);
g_object_unref (p);
} }
static void process_devices(char **argv) static void
process_devices (char **argv)
{ {
GError *error = NULL; g_autoptr(GError) error = NULL;
GPtrArray *devices; g_auto(GStrv) devices = NULL;
char *path; char *path;
guint num_devices;
guint i; guint i;
if (!net_reactivated_Fprint_Manager_get_devices(manager, &devices, &error)) { if (!fprint_dbus_manager_call_get_devices_sync (manager, &devices,
g_print("list_devices failed: %s\n", error->message); NULL, &error))
{
g_print ("Impossible to get devices: %s\n", error->message);
exit (1); exit (1);
} }
if (devices->len == 0) { num_devices = g_strv_length (devices);
g_print("No devices found\n"); if (num_devices == 0)
{
g_print ("No devices available\n");
exit (1); exit (1);
} }
g_print("found %d devices\n", devices->len); g_print ("found %u devices\n", num_devices);
for (i = 0; i < devices->len; i++) { for (i = 0; devices[i] != NULL; i++)
path = g_ptr_array_index(devices, i); {
path = devices[i];
g_print ("Device at %s\n", path); g_print ("Device at %s\n", path);
} }
for (i = 0; i < devices->len; i++) { for (i = 0; devices[i] != NULL; i++)
{
g_autoptr(FprintDBusDevice) dev = NULL;
guint j; guint j;
DBusGProxy *dev;
path = g_ptr_array_index(devices, i); path = devices[i];
g_print ("Using device %s\n", path); g_print ("Using device %s\n", path);
/* FIXME use for_name_owner?? */ /* NOTE: We should handle error cases! */
dev = dbus_g_proxy_new_for_name(connection, "net.reactivated.Fprint", dev = fprint_dbus_device_proxy_new_sync (connection,
path, "net.reactivated.Fprint.Device"); G_DBUS_PROXY_FLAGS_NONE,
"net.reactivated.Fprint",
path, NULL, NULL);
for (j = 1; argv[j] != NULL; j++) for (j = 1; argv[j] != NULL; j++)
delete_fingerprints (dev, argv[j]); delete_fingerprints (dev, argv[j]);
}
g_object_unref (dev);
} }
g_ptr_array_foreach(devices, (GFunc) g_free, NULL); int
g_ptr_array_free(devices, TRUE); main (int argc, char **argv)
}
int main(int argc, char **argv)
{ {
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
create_manager (); create_manager ();
if (argc < 2) { if (argc < 2)
{
g_print ("Usage: %s <username> [usernames...]\n", argv[0]); g_print ("Usage: %s <username> [usernames...]\n", argv[0]);
return 1; return 1;
} }
@ -139,4 +169,3 @@ int main(int argc, char **argv)
return 0; return 0;
} }

View File

@ -1,6 +1,7 @@
/* /*
* fprintd example to enroll right index finger * fprintd example to enroll right index finger
* Copyright (C) 2008 Daniel Drake <dsd@gentoo.org> * Copyright (C) 2008 Daniel Drake <dsd@gentoo.org>
* Copyright (C) 2020 Marco Trevisan <marco.trevisan@canonical.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -21,109 +22,143 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <locale.h> #include <locale.h>
#include <dbus/dbus-glib-bindings.h> #include "fprintd-dbus.h"
#include "manager-dbus-glue.h"
#include "device-dbus-glue.h"
#include "marshal.h"
#define N_(x) x #define N_(x) x
#define TR(x) x #define TR(x) x
#include "fingerprint-strings.h" #include "fingerprint-strings.h"
static DBusGProxy *manager = NULL; static FprintDBusManager *manager = NULL;
static DBusGConnection *connection = NULL; static GDBusConnection *connection = NULL;
static char *finger_name = NULL; static char *finger_name = NULL;
static char **usernames = NULL; static char **usernames = NULL;
static void create_manager(void) static void
create_manager (void)
{ {
GError *error = NULL; g_autoptr(GError) error = NULL;
connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
if (connection == NULL) { if (connection == NULL)
{
g_print ("Failed to connect to session bus: %s\n", error->message); g_print ("Failed to connect to session bus: %s\n", error->message);
exit (1); exit (1);
} }
manager = dbus_g_proxy_new_for_name(connection, manager = fprint_dbus_manager_proxy_new_sync (connection,
"net.reactivated.Fprint", "/net/reactivated/Fprint/Manager", G_DBUS_PROXY_FLAGS_NONE,
"net.reactivated.Fprint.Manager"); "net.reactivated.Fprint",
} "/net/reactivated/Fprint/Manager",
NULL, &error);
static DBusGProxy *open_device(const char *username) if (manager == NULL)
{ {
GError *error = NULL; g_print ("Failed to get Fprintd manager: %s\n", error->message);
gchar *path;
DBusGProxy *dev;
if (!net_reactivated_Fprint_Manager_get_default_device(manager, &path, &error)) {
g_print("list_devices failed: %s\n", error->message);
exit (1); exit (1);
} }
}
if (path == NULL) { static FprintDBusDevice *
g_print("No devices found\n"); open_device (const char *username)
{
g_autoptr(FprintDBusDevice) dev = NULL;
g_autoptr(GError) error = NULL;
g_autofree char *path = NULL;
if (!fprint_dbus_manager_call_get_default_device_sync (manager, &path,
NULL, &error))
{
g_print ("Impossible to enroll: %s\n", error->message);
exit (1); exit (1);
} }
g_print ("Using device %s\n", path); g_print ("Using device %s\n", path);
/* FIXME use for_name_owner?? */ dev = fprint_dbus_device_proxy_new_sync (connection,
dev = dbus_g_proxy_new_for_name(connection, "net.reactivated.Fprint", G_DBUS_PROXY_FLAGS_NONE,
path, "net.reactivated.Fprint.Device"); "net.reactivated.Fprint",
path, NULL, &error);
g_free (path); if (error)
{
g_print ("failed to connect to device: %s\n", error->message);
exit (1);
}
if (!net_reactivated_Fprint_Device_claim(dev, username, &error)) { if (!fprint_dbus_device_call_claim_sync (dev, username, NULL, &error))
{
g_print ("failed to claim device: %s\n", error->message); g_print ("failed to claim device: %s\n", error->message);
exit (1); exit (1);
} }
return dev; return g_steal_pointer (&dev);
} }
static void enroll_result(GObject *object, const char *result, gboolean done, void *user_data) static void
enroll_result (GObject *object, const char *result, gboolean done, void *user_data)
{ {
gboolean *enroll_completed = user_data; gboolean *enroll_completed = user_data;
g_print ("Enroll result: %s\n", result); g_print ("Enroll result: %s\n", result);
if (done != FALSE) if (done != FALSE)
*enroll_completed = TRUE; *enroll_completed = TRUE;
} }
static void do_enroll(DBusGProxy *dev) static void
proxy_signal_cb (GDBusProxy *proxy,
const gchar *sender_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{ {
GError *error = NULL; if (g_str_equal (signal_name, "EnrollStatus"))
{
const gchar *result;
gboolean done;
g_variant_get (parameters, "(&sb)", &result, &done);
enroll_result (G_OBJECT (proxy), result, done, user_data);
}
}
static void
do_enroll (FprintDBusDevice *dev)
{
g_autoptr(GError) error = NULL;
gboolean enroll_completed = FALSE; gboolean enroll_completed = FALSE;
gboolean found; gboolean found;
guint i; guint i;
dbus_g_proxy_add_signal(dev, "EnrollStatus", G_TYPE_STRING, G_TYPE_BOOLEAN, NULL); g_signal_connect (dev, "g-signal", G_CALLBACK (proxy_signal_cb),
dbus_g_proxy_connect_signal(dev, "EnrollStatus", G_CALLBACK(enroll_result), &enroll_completed);
&enroll_completed, NULL);
found = FALSE; found = FALSE;
for (i = 0; fingers[i].dbus_name != NULL; i++) { for (i = 0; fingers[i].dbus_name != NULL; i++)
if (g_strcmp0 (fingers[i].dbus_name, finger_name) == 0) { {
if (g_strcmp0 (fingers[i].dbus_name, finger_name) == 0)
{
found = TRUE; found = TRUE;
break; break;
} }
} }
if (!found) { if (!found)
GString *s; {
g_autoptr(GString) s = NULL;
s = g_string_new (NULL); s = g_string_new (NULL);
g_string_append_printf (s, "Invalid finger name '%s'. Name must be one of ", finger_name); g_string_append_printf (s, "Invalid finger name '%s'. Name must be one of ", finger_name);
for (i = 0; fingers[i].dbus_name != NULL; i++) { for (i = 0; fingers[i].dbus_name != NULL; i++)
{
g_string_append_printf (s, "%s", fingers[i].dbus_name); g_string_append_printf (s, "%s", fingers[i].dbus_name);
if (fingers[i + 1].dbus_name != NULL) if (fingers[i + 1].dbus_name != NULL)
g_string_append (s, ", "); g_string_append (s, ", ");
} }
g_warning ("%s", s->str); g_warning ("%s", s->str);
g_string_free (s, TRUE);
exit (1); exit (1);
} }
g_print ("Enrolling %s finger.\n", finger_name); g_print ("Enrolling %s finger.\n", finger_name);
if (!net_reactivated_Fprint_Device_enroll_start(dev, finger_name, &error)) { if (!fprint_dbus_device_call_enroll_start_sync (dev, finger_name, NULL,
&error))
{
g_print ("EnrollStart failed: %s\n", error->message); g_print ("EnrollStart failed: %s\n", error->message);
exit (1); exit (1);
} }
@ -131,19 +166,21 @@ static void do_enroll(DBusGProxy *dev)
while (!enroll_completed) while (!enroll_completed)
g_main_context_iteration (NULL, TRUE); g_main_context_iteration (NULL, TRUE);
dbus_g_proxy_disconnect_signal(dev, "EnrollStatus", g_signal_handlers_disconnect_by_func (dev, proxy_signal_cb, &enroll_result);
G_CALLBACK(enroll_result), &enroll_completed);
if (!net_reactivated_Fprint_Device_enroll_stop(dev, &error)) { if (!fprint_dbus_device_call_enroll_stop_sync (dev, NULL, &error))
{
g_print ("VerifyStop failed: %s\n", error->message); g_print ("VerifyStop failed: %s\n", error->message);
exit (1); exit (1);
} }
} }
static void release_device(DBusGProxy *dev) static void
release_device (FprintDBusDevice *dev)
{
g_autoptr(GError) error = NULL;
if (!fprint_dbus_device_call_release_sync (dev, NULL, &error))
{ {
GError *error = NULL;
if (!net_reactivated_Fprint_Device_release(dev, &error)) {
g_print ("ReleaseDevice failed: %s\n", error->message); g_print ("ReleaseDevice failed: %s\n", error->message);
exit (1); exit (1);
} }
@ -155,23 +192,22 @@ static const GOptionEntry entries[] = {
{ NULL } { NULL }
}; };
int main(int argc, char **argv) int
main (int argc, char **argv)
{ {
g_autoptr(FprintDBusDevice) dev = NULL;
GOptionContext *context; GOptionContext *context;
GError *err = NULL;
DBusGProxy *dev; g_autoptr(GError) err = NULL;
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
dbus_g_object_register_marshaller (fprintd_marshal_VOID__STRING_BOOLEAN,
G_TYPE_NONE, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INVALID);
context = g_option_context_new ("Enroll a fingerprint"); context = g_option_context_new ("Enroll a fingerprint");
g_option_context_add_main_entries (context, entries, NULL); g_option_context_add_main_entries (context, entries, NULL);
if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) { if (g_option_context_parse (context, &argc, &argv, &err) == FALSE)
{
g_print ("couldn't parse command-line options: %s\n", err->message); g_print ("couldn't parse command-line options: %s\n", err->message);
g_error_free (err);
return 1; return 1;
} }
@ -180,11 +216,10 @@ int main(int argc, char **argv)
create_manager (); create_manager ();
dev = open_device(usernames ? usernames[0] : NULL); dev = open_device (usernames ? usernames[0] : "");
do_enroll (dev); do_enroll (dev);
release_device (dev); release_device (dev);
g_free (finger_name); g_free (finger_name);
g_strfreev (usernames); g_strfreev (usernames);
return 0; return 0;
} }

View File

@ -1,6 +1,7 @@
/* /*
* fprintd example to list enrolled fingerprints * fprintd example to list enrolled fingerprints
* Copyright (C) 2008 Daniel Drake <dsd@gentoo.org> * Copyright (C) 2008 Daniel Drake <dsd@gentoo.org>
* Copyright (C) 2020 Marco Trevisan <marco.trevisan@canonical.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -20,122 +21,137 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <locale.h> #include <locale.h>
#include <dbus/dbus-glib-bindings.h> #include "fprintd-dbus.h"
#include "manager-dbus-glue.h"
#include "device-dbus-glue.h"
static DBusGProxy *manager = NULL; static FprintDBusManager *manager = NULL;
static DBusGConnection *connection = NULL; static GDBusConnection *connection = NULL;
static void create_manager(void) static void
create_manager (void)
{ {
GError *error = NULL; g_autoptr(GError) error = NULL;
connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
if (connection == NULL) { if (connection == NULL)
{
g_print ("Failed to connect to session bus: %s\n", error->message); g_print ("Failed to connect to session bus: %s\n", error->message);
exit (1); exit (1);
} }
manager = dbus_g_proxy_new_for_name(connection, manager = fprint_dbus_manager_proxy_new_sync (connection,
"net.reactivated.Fprint", "/net/reactivated/Fprint/Manager", G_DBUS_PROXY_FLAGS_NONE,
"net.reactivated.Fprint.Manager"); "net.reactivated.Fprint",
"/net/reactivated/Fprint/Manager",
NULL, &error);
if (manager == NULL)
{
g_print ("Failed to get Fprintd manager: %s\n", error->message);
exit (1);
}
} }
static void list_fingerprints(DBusGProxy *dev, const char *username) static void
list_fingerprints (FprintDBusDevice *dev, const char *username)
{ {
GError *error = NULL; g_autoptr(GError) error = NULL;
char **fingers; g_auto(GStrv) fingers = NULL;
GHashTable *props;
DBusGProxy *p;
guint i; guint i;
if (!net_reactivated_Fprint_Device_list_enrolled_fingers(dev, username, &fingers, &error)) { if (!fprint_dbus_device_call_list_enrolled_fingers_sync (dev, username,
if (dbus_g_error_has_name (error, "net.reactivated.Fprint.Error.NoEnrolledPrints") == FALSE) { &fingers, NULL,
&error))
{
gboolean ignore_error = FALSE;
if (g_dbus_error_is_remote_error (error))
{
g_autofree char *dbus_error =
g_dbus_error_get_remote_error (error);
if (g_str_equal (dbus_error,
"net.reactivated.Fprint.Error.NoEnrolledPrints"))
ignore_error = TRUE;
}
if (!ignore_error)
{
g_print ("ListEnrolledFingers failed: %s\n", error->message); g_print ("ListEnrolledFingers failed: %s\n", error->message);
exit (1); exit (1);
} else {
fingers = NULL;
} }
} }
p = dbus_g_proxy_new_from_proxy(dev, "org.freedesktop.DBus.Properties", NULL); if (fingers == NULL || g_strv_length (fingers) == 0)
if (!dbus_g_proxy_call (p, "GetAll", &error, G_TYPE_STRING, "net.reactivated.Fprint.Device", G_TYPE_INVALID, {
dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), &props, G_TYPE_INVALID)) { g_print ("User %s has no fingers enrolled for %s.\n", username,
g_print("GetAll on the Properties interface failed: %s\n", error->message); fprint_dbus_device_get_name (dev));
exit (1);
}
if (fingers == NULL || g_strv_length (fingers) == 0) {
g_print("User %s has no fingers enrolled for %s.\n", username, g_value_get_string (g_hash_table_lookup (props, "name")));
return; return;
} }
g_print ("Fingerprints for user %s on %s (%s):\n", g_print ("Fingerprints for user %s on %s (%s):\n",
username, username,
g_value_get_string (g_hash_table_lookup (props, "name")), fprint_dbus_device_get_name (dev),
g_value_get_string (g_hash_table_lookup (props, "scan-type"))); fprint_dbus_device_get_scan_type (dev));
g_hash_table_destroy (props);
g_object_unref (p);
for (i = 0; fingers[i] != NULL; i++) { for (i = 0; fingers[i] != NULL; i++)
g_print (" - #%d: %s\n", i, fingers[i]); g_print (" - #%d: %s\n", i, fingers[i]);
} }
g_strfreev (fingers); static void
} process_devices (char **argv)
static void process_devices(char **argv)
{ {
GError *error = NULL; g_auto(GStrv) devices = NULL;
GPtrArray *devices; g_autoptr(GError) error = NULL;
char *path; char *path;
guint num_devices;
guint i; guint i;
if (!net_reactivated_Fprint_Manager_get_devices(manager, &devices, &error)) { if (!fprint_dbus_manager_call_get_devices_sync (manager, &devices, NULL,
g_print("list_devices failed: %s\n", error->message); &error))
{
g_print ("Impossible to get devices: %s\n", error->message);
exit (1); exit (1);
} }
if (devices->len == 0) { num_devices = g_strv_length (devices);
g_print("No devices found\n"); if (num_devices == 0)
{
g_print ("No devices available\n");
exit (1); exit (1);
} }
g_print("found %d devices\n", devices->len); g_print ("found %u devices\n", num_devices);
for (i = 0; i < devices->len; i++) { for (i = 0; devices[i] != NULL; i++)
path = g_ptr_array_index(devices, i); {
path = devices[i];
g_print ("Device at %s\n", path); g_print ("Device at %s\n", path);
} }
for (i = 0; i < devices->len; i++) { for (i = 0; devices[i] != NULL; i++)
{
g_autoptr(FprintDBusDevice) dev = NULL;
guint j; guint j;
DBusGProxy *dev;
path = g_ptr_array_index(devices, i); path = devices[i];
g_print ("Using device %s\n", path); g_print ("Using device %s\n", path);
/* FIXME use for_name_owner?? */ /* NOTE: We should handle error cases! */
dev = dbus_g_proxy_new_for_name(connection, "net.reactivated.Fprint", dev = fprint_dbus_device_proxy_new_sync (connection,
path, "net.reactivated.Fprint.Device"); G_DBUS_PROXY_FLAGS_NONE,
"net.reactivated.Fprint",
path, NULL, NULL);
for (j = 1; argv[j] != NULL; j++) for (j = 1; argv[j] != NULL; j++)
list_fingerprints (dev, argv[j]); list_fingerprints (dev, argv[j]);
}
g_object_unref (dev);
} }
g_ptr_array_foreach(devices, (GFunc) g_free, NULL); int
g_ptr_array_free(devices, TRUE); main (int argc, char **argv)
}
int main(int argc, char **argv)
{ {
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
create_manager (); create_manager ();
if (argc < 2) { if (argc < 2)
{
g_print ("Usage: %s <username> [usernames...]\n", argv[0]); g_print ("Usage: %s <username> [usernames...]\n", argv[0]);
return 1; return 1;
} }
@ -144,4 +160,3 @@ int main(int argc, char **argv)
return 0; return 0;
} }

View File

@ -1,52 +1,19 @@
dbus_client_glue_sources = []
foreach interface_name: dbus_interfaces
interface = interface_name.to_lower()
interface_file = meson.source_root() / 'src' / interface + '.xml'
glue_name = interface + '-dbus-glue.h'
dbus_client_glue_sources += custom_target(glue_name,
input: interface_file,
output: glue_name,
command: [
dbus_binding_tool,
'--prefix=fprint_' + interface,
'--mode=glib-client',
'--output=@OUTPUT@',
'@INPUT@',
])
endforeach
utils_marshal = custom_target('utils_marshal',
depends: fprintd_marshal,
input: fprintd_marshal,
output: ['marshal.c', 'marshal.h'],
command: [bash, '-c',
'cp @INPUT0@ @OUTPUT0@;' +
'cp @INPUT1@ @OUTPUT1@;' +
'sed s/fprintd-//g -i ' + meson.current_build_dir() / 'marshal*.{h,c}']
)
libfprintd_utils_dep = declare_dependency( libfprintd_utils_dep = declare_dependency(
include_directories: [ include_directories: [
include_directories('../src'),
include_directories('../pam'), include_directories('../pam'),
], ],
dependencies: [ dependencies: [
glib_dep, glib_dep,
dbus_glib_dep, gio_dep,
gio_unix_dep,
], ],
sources: [ sources: [
utils_marshal, fprintd_dbus_sources,
dbus_client_glue_sources,
], ],
link_with: static_library('fprintd_utils', link_with: [
sources: [ libfprintd_private
dbus_client_glue_sources,
utils_marshal,
], ],
dependencies: [
glib_dep,
]
),
) )
utils = [ utils = [

View File

@ -1,6 +1,7 @@
/* /*
* fprintd example to verify a fingerprint * fprintd example to verify a fingerprint
* Copyright (C) 2008 Daniel Drake <dsd@gentoo.org> * Copyright (C) 2008 Daniel Drake <dsd@gentoo.org>
* Copyright (C) 2020 Marco Trevisan <marco.trevisan@canonical.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -21,139 +22,235 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <locale.h> #include <locale.h>
#include <dbus/dbus-glib-bindings.h> #include <gio/gio.h>
#include "manager-dbus-glue.h" #include "fprintd-dbus.h"
#include "device-dbus-glue.h"
#include "marshal.h"
static DBusGProxy *manager = NULL; static FprintDBusManager *manager = NULL;
static DBusGConnection *connection = NULL; static GDBusConnection *connection = NULL;
static char *finger_name = NULL; static char *finger_name = NULL;
static gboolean g_fatal_warnings = FALSE; static gboolean g_fatal_warnings = FALSE;
static char **usernames = NULL; static char **usernames = NULL;
static void create_manager(void) static void
create_manager (void)
{ {
GError *error = NULL; g_autoptr(GError) error = NULL;
connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
if (connection == NULL) { if (connection == NULL)
{
g_print ("Failed to connect to session bus: %s\n", error->message); g_print ("Failed to connect to session bus: %s\n", error->message);
exit (1); exit (1);
} }
manager = dbus_g_proxy_new_for_name(connection, manager = fprint_dbus_manager_proxy_new_sync (connection,
"net.reactivated.Fprint", "/net/reactivated/Fprint/Manager", G_DBUS_PROXY_FLAGS_NONE,
"net.reactivated.Fprint.Manager"); "net.reactivated.Fprint",
} "/net/reactivated/Fprint/Manager",
NULL, &error);
static DBusGProxy *open_device(const char *username) if (manager == NULL)
{ {
GError *error = NULL; g_print ("Failed to get Fprintd manager: %s\n", error->message);
gchar *path;
DBusGProxy *dev;
if (!net_reactivated_Fprint_Manager_get_default_device(manager, &path, &error)) {
g_print("list_devices failed: %s\n", error->message);
exit (1); exit (1);
} }
}
if (path == NULL) { static FprintDBusDevice *
g_print("No devices found\n"); open_device (const char *username)
{
g_autoptr(FprintDBusDevice) dev = NULL;
g_autoptr(GError) error = NULL;
g_autofree char *path = NULL;
if (!fprint_dbus_manager_call_get_default_device_sync (manager, &path,
NULL, &error))
{
g_print ("Impossible to verify: %s\n", error->message);
exit (1); exit (1);
} }
g_print ("Using device %s\n", path); g_print ("Using device %s\n", path);
/* FIXME use for_name_owner?? */ dev = fprint_dbus_device_proxy_new_sync (connection,
dev = dbus_g_proxy_new_for_name(connection, "net.reactivated.Fprint", G_DBUS_PROXY_FLAGS_NONE,
path, "net.reactivated.Fprint.Device"); "net.reactivated.Fprint",
path, NULL, &error);
g_free (path); if (error)
{
g_print ("failed to connect to device: %s\n", error->message);
exit (1);
}
if (!net_reactivated_Fprint_Device_claim(dev, username, &error)) { if (!fprint_dbus_device_call_claim_sync (dev, username, NULL, &error))
{
g_print ("failed to claim device: %s\n", error->message); g_print ("failed to claim device: %s\n", error->message);
exit (1); exit (1);
} }
return dev; return g_steal_pointer (&dev);
} }
static void find_finger(DBusGProxy *dev, const char *username) static void
find_finger (FprintDBusDevice *dev, const char *username)
{ {
GError *error = NULL; g_autoptr(GError) error = NULL;
char **fingers; g_auto(GStrv) fingers = NULL;
guint i; guint i;
if (!net_reactivated_Fprint_Device_list_enrolled_fingers(dev, username, &fingers, &error)) { if (!fprint_dbus_device_call_list_enrolled_fingers_sync (dev, username,
&fingers,
NULL, &error))
{
g_print ("ListEnrolledFingers failed: %s\n", error->message); g_print ("ListEnrolledFingers failed: %s\n", error->message);
exit (1); exit (1);
} }
if (fingers == NULL || g_strv_length (fingers) == 0) { if (fingers == NULL || g_strv_length (fingers) == 0)
{
g_print ("No fingers enrolled for this device.\n"); g_print ("No fingers enrolled for this device.\n");
exit (1); exit (1);
} }
g_print ("Listing enrolled fingers:\n"); g_print ("Listing enrolled fingers:\n");
for (i = 0; fingers[i] != NULL; i++) { for (i = 0; fingers[i] != NULL; i++)
g_print (" - #%d: %s\n", i, fingers[i]); g_print (" - #%d: %s\n", i, fingers[i]);
if (finger_name && !g_str_equal (finger_name, "any") &&
!g_strv_contains ((const char **) fingers, finger_name))
{
g_print ("Finger '%s' not enrolled for user %s.\n", finger_name,
username);
g_free (finger_name);
exit (1);
} }
if (finger_name == NULL || strcmp (finger_name, "any") == 0) { if (finger_name == NULL)
g_free (finger_name);
finger_name = g_strdup (fingers[0]); finger_name = g_strdup (fingers[0]);
} }
g_strfreev (fingers); struct VerifyState
}
static void verify_result(GObject *object, const char *result, gboolean done, void *user_data)
{ {
gboolean *verify_completed = user_data; GError *error;
gboolean started;
gboolean completed;
};
static void
verify_result (GObject *object, const char *result, gboolean done, void *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)
{ {
g_print ("Verifying: %s\n", name); g_print ("Verifying: %s\n", name);
} }
static void do_verify(DBusGProxy *dev) static void
verify_started_cb (GObject *obj,
GAsyncResult *res,
gpointer user_data)
{ {
GError *error = NULL; struct VerifyState *verify_state = user_data;
gboolean verify_completed = FALSE;
dbus_g_proxy_add_signal(dev, "VerifyStatus", G_TYPE_STRING, G_TYPE_BOOLEAN, NULL); if (fprint_dbus_device_call_verify_start_finish (FPRINT_DBUS_DEVICE (obj), res, &verify_state->error))
dbus_g_proxy_add_signal(dev, "VerifyFingerSelected", G_TYPE_INT, NULL); {
dbus_g_proxy_connect_signal(dev, "VerifyStatus", G_CALLBACK(verify_result), g_print ("Verify started!\n");
&verify_completed, NULL); verify_state->started = TRUE;
dbus_g_proxy_connect_signal(dev, "VerifyFingerSelected", G_CALLBACK(verify_finger_selected), }
NULL, NULL); }
if (!net_reactivated_Fprint_Device_verify_start(dev, finger_name, &error)) { static void
g_print("VerifyStart failed: %s\n", error->message); proxy_signal_cb (GDBusProxy *proxy,
const gchar *sender_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
struct VerifyState *verify_state = user_data;
if (!verify_state->started)
return;
if (g_str_equal (signal_name, "VerifyStatus"))
{
const gchar *result;
gboolean done;
g_variant_get (parameters, "(&sb)", &result, &done);
verify_result (G_OBJECT (proxy), result, done, user_data);
}
else if (g_str_equal (signal_name, "VerifyFingerSelected"))
{
const gchar *name;
g_variant_get (parameters, "(&s)", &name);
verify_finger_selected (G_OBJECT (proxy), name, user_data);
}
}
static void
do_verify (FprintDBusDevice *dev)
{
g_autoptr(GError) error = NULL;
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),
&verify_state);
fprint_dbus_device_call_verify_start (dev, finger_name, NULL,
verify_started_cb,
&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);
} }
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);
dbus_g_proxy_disconnect_signal(dev, "VerifyStatus", G_CALLBACK(verify_result), &verify_completed);
dbus_g_proxy_disconnect_signal(dev, "VerifyFingerSelected", G_CALLBACK(verify_finger_selected), NULL);
if (!net_reactivated_Fprint_Device_verify_stop(dev, &error)) { g_signal_handlers_disconnect_by_func (dev, proxy_signal_cb,
&verify_state);
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);
exit (1); exit (1);
} }
} }
static void release_device(DBusGProxy *dev) static void
release_device (FprintDBusDevice *dev)
{
g_autoptr(GError) error = NULL;
if (!fprint_dbus_device_call_release_sync (dev, NULL, &error))
{ {
GError *error = NULL;
if (!net_reactivated_Fprint_Device_release(dev, &error)) {
g_print ("ReleaseDevice failed: %s\n", error->message); g_print ("ReleaseDevice failed: %s\n", error->message);
exit (1); exit (1);
} }
@ -166,34 +263,32 @@ static const GOptionEntry entries[] = {
{ NULL } { NULL }
}; };
int main(int argc, char **argv) int
main (int argc, char **argv)
{ {
g_autoptr(FprintDBusDevice) dev = NULL;
g_autoptr(GError) err = NULL;
GOptionContext *context; GOptionContext *context;
GError *err = NULL;
DBusGProxy *dev;
const char *username = NULL; const char *username = NULL;
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
dbus_g_object_register_marshaller (fprintd_marshal_VOID__STRING_BOOLEAN,
G_TYPE_NONE, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INVALID);
context = g_option_context_new ("Verify a fingerprint"); context = g_option_context_new ("Verify a fingerprint");
g_option_context_add_main_entries (context, entries, NULL); g_option_context_add_main_entries (context, entries, NULL);
if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) { if (g_option_context_parse (context, &argc, &argv, &err) == FALSE)
{
g_print ("couldn't parse command-line options: %s\n", err->message); g_print ("couldn't parse command-line options: %s\n", err->message);
g_error_free (err);
return 1; return 1;
} }
if (usernames == NULL) { if (usernames == NULL)
username = ""; username = "";
} else { else
username = usernames[0]; username = usernames[0];
}
if (g_fatal_warnings) { if (g_fatal_warnings)
{
GLogLevelFlags fatal_mask; GLogLevelFlags fatal_mask;
fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK);
@ -209,4 +304,3 @@ int main(int argc, char **argv)
release_device (dev); release_device (dev);
return 0; return 0;
} }