From 8ebd8e888850680af132fdc60369aefe18188c6a Mon Sep 17 00:00:00 2001 From: Packit Service Date: Dec 16 2020 22:38:58 +0000 Subject: fprintd-1.90.8 base --- diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..a6e5f20 --- /dev/null +++ b/.git-blame-ignore-revs @@ -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 diff --git a/.gitignore b/.gitignore index c32a846..3a53be3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1,11 @@ /*.bak -/*.lo /*.o /*.orig /*.rej /*.tab.c /*~ /.*.sw[nop] -/.deps /.dirstamp /.gitignore -/.libs -/GPATH -/GRTAGS -/GSYMS -/GTAGS -/ID -/Makefile -/Makefile.in -/TAGS -/_libs -/autom4te.cache -/config.cache -/config.h -/config.log -/config.lt -/config.status -/config.status.lineno -/configure -/configure.lineno -/intltool-extract.in -/intltool-merge.in -/intltool-update.in -/libtool -/po/*.gmo -/po/*.mo -/po/.intltool-merge-cache -/po/Makefile -/po/Makefile.in -/po/Makefile.in.in -/po/POTFILES -/po/fprintd.pot -/po/stamp-it -/so_locations -/stamp-h1 +/_build /tags diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fe3e677..d286c48 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,59 +1,127 @@ -image: fedora:rawhide +include: + - project: 'libfprint/libfprint' + ref: master + file: '/.gitlab-ci/libfprint-templates.yaml' + - project: 'wayland/ci-templates' + ref: master + file: '/templates/fedora.yml' variables: - DEPENDENCIES: dbus-glib-devel pam-devel polkit-devel - gtk-doc meson intltool autoconf automake libtool - gcc gcc-c++ glibc-devel make python3-dbusmock - DEPENDENCIES_STABLE: $DEPENDENCIES libfprint-devel - DEPENDENCIES_DEV: $DEPENDENCIES git - # Sync'ed up with https://gitlab.freedesktop.org/libfprint/libfprint/blob/master/.gitlab-ci.yml - DEPENDENCIES_LIBFPRINT: libgusb-devel glib2-devel nss-devel pixman-devel systemd meson gtk-doc - gcc gcc-c++ glibc-devel libX11-devel libXv-devel gtk3-devel flatpak-builder - gobject-introspection-devel python3-cairo python3-gobject umockdev - -# Stable builds can be re-enabled when libfprint v2 reaches rawhide -#build_stable: -# before_script: -# - dnf update -y --nogpgcheck && dnf install -y --nogpgcheck $DEPENDENCIES_STABLE -# script: -# - ./autogen.sh --disable-dependency-tracking -# - make -# - make install + extends: .libfprint_common_variables + FDO_DISTRIBUTION_TAG: latest + FDO_DISTRIBUTION_VERSION: rawhide + FEDORA_IMAGE: "$CI_REGISTRY/libfprint/$CI_PROJECT_NAME/fedora/$FDO_DISTRIBUTION_VERSION:$FDO_DISTRIBUTION_TAG" + DEPENDENCIES: dbus-glib-devel + gcc + gcovr + gettext + git + glibc-devel + gtk-doc + libasan + libfprint-devel + meson + pam-devel + polkit-devel + python3-dbusmock + python3-libpamtest + systemd-devel -build_dev: +image: "$FEDORA_IMAGE" + +stages: + - check-source + - build + - test + +.fprintd_build_preconditions: + except: + variables: + - $FPRINT_CRON_TASK == "BUILD_CI_IMAGES" + +.install_libfprint_dev: before_script: - - dnf update -y --nogpgcheck && dnf install -y --nogpgcheck $DEPENDENCIES_LIBFPRINT $DEPENDENCIES_DEV - - git clone -b wip/benzea/v2 https://gitlab.freedesktop.org/libfprint/libfprint.git + # Make sure we don't build or link against the system libfprint + - dnf remove -y libfprint-devel + - git clone https://gitlab.freedesktop.org/libfprint/libfprint.git - cd libfprint - - meson . _build --prefix=/usr + - meson . _build --prefix=/usr -Ddrivers=virtual_image -Ddoc=false - ninja -C _build - ninja -C _build install - cd .. # So we don't get error about this libfprint file - 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: + extends: .fprintd_build_preconditions + stage: build script: - - ./autogen.sh --disable-dependency-tracking - - make - - make install + - meson _build + - ninja -C _build -v + - ninja -C _build -v install + +build_dev: + extends: + - .fprintd_build_preconditions + - .install_libfprint_dev + stage: build + script: + - meson _build --werror -Dgtk_doc=true + - ninja -C _build -v + - ninja -C _build -v install + artifacts: + name: log + when: on_failure + paths: + - _build/meson-logs/*.txt test_dev: + extends: + - .fprintd_build_preconditions + - .install_libfprint_dev stage: test - before_script: - - dnf update -y --nogpgcheck && dnf install -y --nogpgcheck $DEPENDENCIES_LIBFPRINT $DEPENDENCIES_DEV - - git clone -b wip/benzea/v2 https://gitlab.freedesktop.org/libfprint/libfprint.git - - cd libfprint - - meson . _build --prefix=/usr -Ddrivers=virtual_image - - ninja -C _build - - ninja -C _build install - - cd .. - # So we don't get error about this libfprint file - - echo "libfprint/demo/gtk-libfprint-test.ui" >> po/POTFILES.skip script: - - ./autogen.sh --disable-dependency-tracking - - make - - make check + - meson _build -Db_coverage=true + - meson test -C _build --verbose --no-stdsplit --timeout-multiplier 3 + - ninja -C _build coverage + - cat _build/meson-logs/coverage.txt artifacts: - name: log + name: log-and-coverage when: always paths: - - tests/*.log + - _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 +container_fedora_build: + extends: .fdo.container-build@fedora + only: + variables: + - $FPRINT_CRON_TASK == "BUILD_CI_IMAGES" + variables: + GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image + # a list of packages to install + FDO_DISTRIBUTION_PACKAGES: + $DEPENDENCIES + $LIBFPRINT_DEPENDENCIES diff --git a/NEWS b/NEWS index 6e8fd07..43e8ddc 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,84 @@ This file lists notable changes in each release. For the full history of all 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: +- Add support for prints saved on the fingerprint device itself +- Add integration tests using the virtual image driver, and further + tests for the utilities +- Port build system to meson +- Loads of build warnings and memory leak fixes + +- PAM module: + - Port PAM module to sd-bus from dbus-glib + - Use systemd to not ask for a fingerprint scan on remote logins + - Add man page for PAM module + - Add tests + +This version requires libfprint 1.90.1, a 2.0 pre-release. + version 0.9.0: - Fix hangs when there the verification error was "retry" - Update for fp_get_pollfds() changes diff --git a/README.transifex b/README.transifex index 02a2858..c391636 100644 --- a/README.transifex +++ b/README.transifex @@ -1,7 +1,13 @@ -Transifex.net Token Verification -================================= +Updating translations +===================== -The list of tokens bellow guarantee the respective users to be able to enable +The update-transifex.sh script should be run regularly to both pull +translations from the Transifex service, and push new strings to translate. + +Transifex.net Token Verification +================================ + +The list of tokens below guarantee the respective users to be able to enable submission on components using the following repository url: https://gitlab.freedesktop.org/libfprint/fprintd/ diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..d45da15 --- /dev/null +++ b/config.h.in @@ -0,0 +1,14 @@ +/* Define to the Gettext package name */ +#mesondefine GETTEXT_PACKAGE + +/* Version number of package */ +#mesondefine PACKAGE_VERSION + +/* Where the configuration file will be located */ +#mesondefine SYSCONFDIR + +/* Define to the version of this package. */ +#mesondefine VERSION + +/* Whether current polkit version supports autopointers */ +#mesondefine POLKIT_HAS_AUTOPOINTERS diff --git a/data/fprintd.1 b/data/fprintd.1 index 93d9afd..ddf0097 100644 --- a/data/fprintd.1 +++ b/data/fprintd.1 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 2.23 (Pod::Simple 3.14) +.\" Automatically generated by Pod::Man 4.12 (Pod::Simple 3.39) .\" .\" Standard preamble: .\" ======================================================================== @@ -38,27 +38,36 @@ . ds PI \(*p . ds L" `` . ds R" '' +. ds C` +. ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" -.\" If the F register is turned on, we'll generate index entries on stderr for +.\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. -.ie \nF \{\ -. de IX -. tm Index:\\$1\t\\n%\t"\\$2" +.\" +.\" Avoid warning from groff about undefined register 'F'. +.de IX .. -. nr % 0 -. rr F -.\} -.el \{\ -. de IX +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" .. +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} +. \} .\} +.rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. @@ -124,16 +133,16 @@ .\" ======================================================================== .\" .IX Title "fprintd 1" -.TH fprintd 1 "2010-08-16" "freedesktop" "" +.TH fprintd 1 "2020-01-24" "freedesktop" "" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" fprintd \- Fingerprint management daemon, and test applications -.SH "SYNOPSYS" -.IX Header "SYNOPSYS" -\&\fBfprintd-enroll\fR [username] +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +\&\fBfprintd-enroll\fR [\-f finger] [usename] .PP \&\fBfprintd-list\fR username [usernames...] .PP @@ -185,7 +194,7 @@ Will list the user's enrolled fingerprints. .SS "fprintd-verify" .IX Subsection "fprintd-verify" .RS 8 -Will enroll the user's right index finger into the database. +Will verify the user's fingerprints against the database. .RE .SS "fprintd-enroll" .IX Subsection "fprintd-enroll" @@ -200,5 +209,5 @@ Will enroll the user's right index finger into the database. By default, fprintd stores the fingerprints in \fB/var/lib/fprint/\fR .SH "SEE ALSO" .IX Header "SEE ALSO" -.IP "\fBdbus-daemon\fR, \fBgnome-about-me\fR" 8 -.IX Item "dbus-daemon, gnome-about-me" +.IP "\fBgnome-control-center\fR" 8 +.IX Item "gnome-control-center" diff --git a/data/fprintd.pod b/data/fprintd.pod index 7085920..279aca7 100644 --- a/data/fprintd.pod +++ b/data/fprintd.pod @@ -2,7 +2,7 @@ fprintd - Fingerprint management daemon, and test applications -=head1 SYNOPSYS +=head1 SYNOPSIS B [-f finger] [usename] @@ -75,7 +75,7 @@ Will list the user's enrolled fingerprints. =over 8 -Will enroll the user's right index finger into the database. +Will verify the user's fingerprints against the database. =back @@ -99,7 +99,7 @@ By default, fprintd stores the fingerprints in B =over 8 -=item B, B +=item B =back diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..92bfa2e --- /dev/null +++ b/data/meson.build @@ -0,0 +1,72 @@ +install_data('net.reactivated.Fprint.conf', + install_dir: dbus_conf_dir) + +configure_file( + configuration: configuration_data({ + 'LIBEXECDIR': fprintd_installdir, + }), + input: 'net.reactivated.Fprint.service.in', + output: 'net.reactivated.Fprint.service', + install: true, + install_dir: dbus_service_dir, +) + +if get_option('systemd') + configure_file( + configuration: configuration_data({ + 'libexecdir': fprintd_installdir, + }), + input: 'fprintd.service.in', + output: 'fprintd.service', + install: true, + install_dir: systemd_unit_dir, + ) +endif + +polkit_policy = 'net.reactivated.fprint.device.policy' +polkit_policy_target = i18n.merge_file(polkit_policy, + input: '@0@.in'.format(polkit_policy), + output: polkit_policy, + po_dir: meson.source_root() / 'po', + install: true, + install_dir: polkit_policy_directory, +) + +if xmllint.found() + test(polkit_policy, + xmllint, + depends: polkit_policy_target, + args: [ + '--noout', + polkit_policy_target.full_path(), + ]) +endif + +install_data('fprintd.conf', + install_dir: sysconfdir) + +if get_option('man') + manfiles = { + 'fprintd': 1, + 'pam_fprintd': 8, + } + + foreach man_name, man_section: manfiles + custom_target('man_' + man_name + '.' + man_section.to_string(), + input: man_name + '.pod', + output: man_name + '.' + man_section.to_string(), + command: [ + pod2man, + '-c', '', + '-s', man_section.to_string(), + '-q', 'none', + '-n', man_name, + '-r', 'freedesktop', + '@INPUT@', + '@OUTPUT@', + ], + install: true, + install_dir: datadir / 'man' / 'man' + man_section.to_string(), + ) + endforeach +endif diff --git a/data/net.reactivated.Fprint.conf b/data/net.reactivated.Fprint.conf index 1fd0c06..633867c 100644 --- a/data/net.reactivated.Fprint.conf +++ b/data/net.reactivated.Fprint.conf @@ -12,8 +12,18 @@ - - + + + + + + + diff --git a/data/net.reactivated.fprint.device.policy.in b/data/net.reactivated.fprint.device.policy.in index bf49192..90d18c6 100644 --- a/data/net.reactivated.fprint.device.policy.in +++ b/data/net.reactivated.fprint.device.policy.in @@ -10,8 +10,8 @@ fprint - <_description>Verify a fingerprint - <_message>Privileges are required to verify fingerprints. + Verify a fingerprint + Privileges are required to verify fingerprints. no no @@ -20,18 +20,18 @@ - <_description>Enroll new fingerprints - <_message>Privileges are required to enroll new fingerprints. + Enroll new fingerprints + Privileges are required to enroll new fingerprints. no no - yes + auth_self_keep - <_description>Select a user to enroll - <_message>Privileges are required to enroll new fingerprints for other users. + Select a user to enroll + Privileges are required to enroll new fingerprints for other users. no no diff --git a/data/pam_fprintd.8 b/data/pam_fprintd.8 new file mode 100644 index 0000000..9b000d4 --- /dev/null +++ b/data/pam_fprintd.8 @@ -0,0 +1,184 @@ +.\" Automatically generated by Pod::Man 4.12 (Pod::Simple 3.39) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. \*(C+ will +.\" give a nicer C++. Capital omega is used to do unbreakable dashes and +.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, +.\" nothing in troff, for use with C<>. +.tr \(*W- +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` +. ds C' +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +. ds C` +. ds C' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is >0, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.\" +.\" Avoid warning from groff about undefined register 'F'. +.de IX +.. +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} +. \} +.\} +.rr rF +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "pam_fprintd 8" +.TH pam_fprintd 8 "2020-01-24" "freedesktop" "" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH "NAME" +pam_fprintd \- PAM module to authenticate against fprintd, the fingerprint daemon +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +\&\fBpam_fprintd.so\fR [debug|debug=[\fIon\fR|\fIoff\fR|\fItrue\fR|\fIfalse\fR|\fI1\fR|\fI0\fR]] [max\-tries=\fI\s-1MAX_TRIES\s0\fR] [timeout=\fI\s-1TIMEOUT\s0\fR] +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +The pam_fprintd module is used to verify a user's fingerprints against fingerprints +enrolled using fprintd, the fingerprint management daemon. +.SH "OPTIONS" +.IX Header "OPTIONS" +.IP "\fBdebug\fR" 8 +.IX Item "debug" +.PD 0 +.IP "\fBdebug=[\f(BIon\fB|\f(BIoff\fB|\f(BItrue\fB|\f(BIfalse\fB|\f(BI1\fB|\f(BI0\fB]\fR" 8 +.IX Item "debug=[on|off|true|false|1|0]" +.PD +Whether debug should be turned on or off. Debug messages will be generated using +pam_syslog which means that they will be saved in the systemd journal by default. +.IP "\fBmax\-tries=\f(BI\s-1MAX_TRIES\s0\fB\fR" 8 +.IX Item "max-tries=MAX_TRIES" +The number of attempts at fingerprint authentication to try before returning an +authentication failure. The minimum, and default, number of tries is 3. +.IP "\fBtimeout=\f(BI\s-1TIMEOUT\s0\fB\fR" 8 +.IX Item "timeout=TIMEOUT" +The amount of time before returning an authentication failure. The default timeout +is 30 seconds, with 10 seconds being the minimum. +.SH "LIMITATIONS" +.IX Header "LIMITATIONS" +The \s-1PAM\s0 stack is by design a serialised authentication, so it is not +possible for pam_fprintd to allow authentication through passwords and +fingerprints at the same time. +.PP +It is up to the application using the \s-1PAM\s0 services to implement separate +\&\s-1PAM\s0 processes and run separate authentication stacks separately. This +is the way multiple authentication methods are made available to users +of gdm for example. +.SH "AUTHOR" +.IX Header "AUTHOR" +\&\fBfprintd\fR was written by Bastien Nocera. +.SH "SEE ALSO" +.IX Header "SEE ALSO" +.IP "\fBfprintd\fR, \fB\s-1PAM\s0\fR" 8 +.IX Item "fprintd, PAM" diff --git a/data/pam_fprintd.pod b/data/pam_fprintd.pod new file mode 100644 index 0000000..d73ec4f --- /dev/null +++ b/data/pam_fprintd.pod @@ -0,0 +1,63 @@ +=head1 NAME + +pam_fprintd - PAM module to authenticate against fprintd, the fingerprint daemon + +=head1 SYNOPSIS + +B [debug|debug=[I|I|I|I|I<1>|I<0>]] [max-tries=I] [timeout=I] + +=head1 DESCRIPTION + +The pam_fprintd module is used to verify a user's fingerprints against fingerprints +enrolled using fprintd, the fingerprint management daemon. + +=head1 OPTIONS + +=over 8 + +=item B + +=item B|I|I|I|I<1>|I<0>]> + +Whether debug should be turned on or off. Debug messages will be generated using +pam_syslog which means that they will be saved in the systemd journal by default. + +=item B> + +The number of attempts at fingerprint authentication to try before returning an +authentication failure. The minimum, and default, number of tries is 3. + +=item B> + +The amount of time before returning an authentication failure. The default timeout +is 30 seconds, with 10 seconds being the minimum. + +=back + +=head1 LIMITATIONS + +=over 8 + +=back + +The PAM stack is by design a serialised authentication, so it is not +possible for pam_fprintd to allow authentication through passwords and +fingerprints at the same time. + +It is up to the application using the PAM services to implement separate +PAM processes and run separate authentication stacks separately. This +is the way multiple authentication methods are made available to users +of gdm for example. + +=head1 AUTHOR + +B was written by Bastien Nocera. + +=head1 SEE ALSO + +=over 8 + +=item B, B + +=back + diff --git a/doc/dbus/meson.build b/doc/dbus/meson.build new file mode 100644 index 0000000..802938e --- /dev/null +++ b/doc/dbus/meson.build @@ -0,0 +1,29 @@ +docbook_xml_header = custom_target('docbook_xml_header', + output: 'docbook-xml-header.xml', + command: [ + 'echo', '-n', + '\n', + '\n', + ], + capture: true, +) + +dbus_interfaces_refs = [] +foreach interface_file: dbus_interfaces_files + basename = run_command('basename', interface_file.full_path(), '.xml').stdout().strip() + dbus_interfaces_refs += custom_target(basename + '_ref', + input: docbook_xml_header, + output: basename + '.ref.xml', + build_by_default: true, + depends: interface_file, + capture: true, + command: [ + bash, '-c', + 'cat @INPUT@;' + + xsltproc.path() + ' @0@/@1@ '.format( + meson.source_root(), + files('spec-to-docbook.xsl')[0]) + + interface_file.full_path() + '| tail -n +2;', + ], + ) +endforeach diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 0000000..27432b5 --- /dev/null +++ b/doc/meson.build @@ -0,0 +1,27 @@ +subdir('dbus') + +version_file = configure_file( + input: 'version.xml.in', + output: 'version.xml', + configuration: configuration_data({ + 'VERSION': meson.project_version(), + }), +) + +gnome.gtkdoc(meson.project_name(), + main_xml: 'fprintd-docs.xml', + src_dir: meson.source_root() / 'src', + dependencies: [ + declare_dependency( + sources: dbus_interfaces_refs, + link_with: libfprintd_private, + ), + ], + content_files: [ + version_file, + dbus_interfaces_refs, + ], + ignore_headers: [ + 'config.h', + ], + install: true) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..78ebe84 --- /dev/null +++ b/meson.build @@ -0,0 +1,199 @@ +project('fprintd', 'c', + version: '1.90.8', + license: 'GPLv2+', + default_options: [ + 'buildtype=debugoptimized', + 'warning_level=3', + 'c_std=gnu99', + ], + meson_version: '>= 0.50.0') + +gnome = import('gnome') +i18n = import('i18n') + +cc = meson.get_compiler('c') +common_cflags = cc.get_supported_arguments([ + '-fno-strict-aliasing', + '-Wcast-align', + '-Werror=address', + '-Werror=array-bounds', + '-Werror=empty-body', + '-Werror=implicit', + '-Werror=init-self', + '-Werror=int-to-pointer-cast', + '-Werror=main', + '-Werror=missing-braces', + '-Werror=nonnull', + '-Werror=pointer-to-int-cast', + '-Werror=redundant-decls', + '-Werror=return-type', + '-Werror=sequence-point', + '-Werror=trigraphs', + '-Werror=write-strings', + '-Wformat-nonliteral', + '-Wformat-security', + '-Wformat=2', + '-Wignored-qualifiers', + '-Wimplicit-function-declaration', + '-Wlogical-op', + '-Wmissing-declarations', + '-Wmissing-format-attribute', + '-Wmissing-include-dirs', + '-Wmissing-noreturn', + '-Wmissing-prototypes', + '-Wnested-externs', + '-Wold-style-definition', + '-Wpointer-arith', + '-Wshadow', + '-Wstrict-prototypes', + '-Wtype-limits', + '-Wundef', + '-Wunused', +]) +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 +glib_dep = dependency('glib-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) +libfprint_dep = dependency('libfprint-2', version: '>=' + libfprint_min_version) +polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.91') +dbus_dep = dependency('dbus-1', required: false) +libsystemd_dep = dependency('libsystemd', required: get_option('pam')) +pam_dep = cc.find_library('pam', + required: get_option('pam'), + has_headers: 'security/pam_modules.h', +) + +pod2man = find_program('pod2man', required: get_option('man')) +xsltproc = find_program('xsltproc', required: get_option('gtk_doc')) + +# StateDirectory was introduced in systemd 235 +systemd_dep = dependency('systemd', version: '>= 235', required: false) +systemd_unit_dir = get_option('systemd_system_unit_dir') + +if systemd_unit_dir == '' and systemd_dep.found() + systemd_unit_dir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir') +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_data_dir = datadir +dbus_interfaces_dir = '' + +if dbus_dep.found() + if dbus_service_dir == '' + dbus_service_dir = dbus_dep.get_pkgconfig_variable('system_bus_services_dir') + endif + dbus_interfaces_dir = dbus_dep.get_pkgconfig_variable('interfaces_dir') + dbus_data_dir = dbus_dep.get_pkgconfig_variable('datadir') +endif + +dbus_conf_dir = dbus_data_dir / 'dbus-1/system.d' + +if dbus_service_dir == '' + dbus_service_dir = datadir / 'dbus-1/system-services' +endif +if dbus_interfaces_dir == '' + dbus_interfaces_dir = datadir / 'dbus-1/interfaces' +endif + +polkit_policy_directory = polkit_gobject_dep.get_pkgconfig_variable('policydir') + +# Tests dependencies +pam_wrapper_dep = dependency('pam_wrapper', required: get_option('pam')) + +xmllint = find_program('xmllint', required: false) +python3 = find_program('python3') # No meson without it! +python3_test_modules = { + 'cairo': true, + 'dbus': true, + 'dbusmock': true, + 'gi': true, + 'gi.repository.FPrint': true, + 'pypamtest': get_option('pam'), +} +python3_available_modules = [] + +foreach module, required : python3_test_modules + if required and run_command(python3, '-c', 'import @0@'.format(module)).returncode() != 0 + error('Python3 module \'' + module + '\' required by test suite not found') + endif +endforeach + +cdata = configuration_data() +cdata.set_quoted('GETTEXT_PACKAGE', meson.project_name()) +cdata.set_quoted('PACKAGE_VERSION', meson.project_version()) +cdata.set_quoted('VERSION', meson.project_version()) +cdata.set_quoted('SYSCONFDIR', sysconfdir) +cdata.set('POLKIT_HAS_AUTOPOINTERS', polkit_gobject_dep.version().version_compare('>= 0.114')) + +config_h = configure_file( + input: 'config.h.in', + output: 'config.h', + configuration: cdata +) + +subdir('src') +subdir('data') +subdir('utils') +if get_option('pam') + subdir('pam') +endif +if get_option('gtk_doc') + subdir('doc') +endif +subdir('tests') +subdir('po') + +output = [] +output += 'System paths:' +output += ' prefix: ' + get_option('prefix') +output += ' fprintd daemon directory: ' + fprintd_installdir +output += ' fprintd modules directory: ' + fprintd_plugindir +output += ' fprintd prints storage directory: ' + storage_path +output += ' DBus configuration directory: ' + dbus_conf_dir +output += ' DBus service directory: ' + dbus_service_dir +output += ' DBus interfaces directory: ' + dbus_interfaces_dir +output += ' Polkit policy directory: ' + polkit_policy_directory +output += ' Systemd service directory: ' + systemd_unit_dir +if get_option('pam') + output += ' PAM module directory: ' + pam_modules_dir +endif +output += '\nOptional features:\n' +output += ' PAM module: ' + pam_dep.found().to_string() +output += ' Manuals: ' + get_option('man').to_string() +output += ' GTK Doc: ' + get_option('gtk_doc').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') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..5daa9a4 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,25 @@ +option('pam', + description: 'Build the fprintd PAM module', + type: 'boolean', + value: true) +option('man', + description: 'Generate the man files', + type: 'boolean', + value: true) +option('systemd', + description: 'Install system service files', + type: 'boolean', + value: true) +option('systemd_system_unit_dir', + description: 'Directory for systemd service files', + type: 'string') +option('dbus_service_dir', + description: 'Directory for dbus service files', + type: 'string') +option('pam_modules_dir', + description: 'Directory for PAM modules', + type: 'string') +option('gtk_doc', + type: 'boolean', + value: false, + description: 'Use gtk-doc to build documentation') diff --git a/pam/fingerprint-strings.h b/pam/fingerprint-strings.h index f9fd993..68fc67c 100644 --- a/pam/fingerprint-strings.h +++ b/pam/fingerprint-strings.h @@ -1,7 +1,7 @@ /* * Helper functions to translate statuses and actions to strings * Copyright (C) 2008 Bastien Nocera - * + * * Experimental code. This will be moved out of fprintd into it's own * package once the system has matured. * @@ -9,109 +9,147 @@ * 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. */ -struct { - const char *dbus_name; - const char *place_str_generic; - const char *place_str_specific; - const char *swipe_str_generic; - const char *swipe_str_specific; +#ifndef _GNU_SOURCE +#error _GNU_SOURCE must be defined +#endif +#include +#include + +#define GNUC_UNUSED __attribute__((__unused__)) + +static bool +str_equal (const char *a, const char *b) +{ + if (a == NULL && b == NULL) + return true; + if (a == NULL || b == NULL) + return false; + return strcmp (a, b) == 0; +} + +struct +{ + const char *dbus_name; + const char *place_str_generic; + const char *place_str_specific; + const char *swipe_str_generic; + const char *swipe_str_specific; } fingers[] = { - { "any", - N_("Place your finger on the fingerprint reader"), - N_("Place your finger on %s"), - N_("Swipe your finger across the fingerprint reader"), - N_("Swipe your finger across %s") }, - { "left-thumb", - N_("Place your left thumb on the fingerprint reader"), - N_("Place your left thumb on %s"), - N_("Swipe your left thumb across the fingerprint reader"), - N_("Swipe your left thumb across %s") }, - { "left-index-finger", - N_("Place your left index finger on the fingerprint reader"), - N_("Place your left index finger on %s"), - N_("Swipe your left index finger across the fingerprint reader"), - N_("Swipe your left index finger across %s") }, - { "left-middle-finger", - N_("Place your left middle finger on the fingerprint reader"), - N_("Place your left middle finger on %s"), - N_("Swipe your left middle finger across the fingerprint reader"), - N_("Swipe your left middle finger across %s") }, - { "left-ring-finger", - N_("Place your left ring finger on the fingerprint reader"), - N_("Place your left ring finger on %s"), - N_("Swipe your left ring finger across the fingerprint reader"), - N_("Swipe your left ring finger across %s") }, - { "left-little-finger", - N_("Place your left little finger on the fingerprint reader"), - N_("Place your left little finger on %s"), - N_("Swipe your left little finger across the fingerprint reader"), - N_("Swipe your left little finger across %s") }, - { "right-thumb", - N_("Place your right thumb on the fingerprint reader"), - N_("Place your right thumb on %s"), - N_("Swipe your right thumb across the fingerprint reader"), - N_("Swipe your right thumb across %s") }, - { "right-index-finger", - N_("Place your right index finger on the fingerprint reader"), - N_("Place your right index finger on %s"), - N_("Swipe your right index finger across the fingerprint reader"), - N_("Swipe your right index finger across %s") }, - { "right-middle-finger", - N_("Place your right middle finger on the fingerprint reader"), - N_("Place your right middle finger on %s"), - N_("Swipe your right middle finger across the fingerprint reader"), - N_("Swipe your right middle finger across %s") }, - { "right-ring-finger", - N_("Place your right ring finger on the fingerprint reader"), - N_("Place your right ring finger on %s"), - N_("Swipe your right ring finger across the fingerprint reader"), - N_("Swipe your right ring finger across %s") }, - { "right-little-finger", - N_("Place your right little finger on the fingerprint reader"), - N_("Place your right little finger on %s"), - N_("Swipe your right little finger across the fingerprint reader"), - N_("Swipe your right little finger across %s") }, - { NULL, NULL, NULL, NULL, NULL } + { "any", + N_("Place your finger on the fingerprint reader"), + N_("Place your finger on %s"), + N_("Swipe your finger across the fingerprint reader"), + N_("Swipe your finger across %s") }, + { "left-thumb", + N_("Place your left thumb on the fingerprint reader"), + N_("Place your left thumb on %s"), + N_("Swipe your left thumb across the fingerprint reader"), + N_("Swipe your left thumb across %s") }, + { "left-index-finger", + N_("Place your left index finger on the fingerprint reader"), + N_("Place your left index finger on %s"), + N_("Swipe your left index finger across the fingerprint reader"), + N_("Swipe your left index finger across %s") }, + { "left-middle-finger", + N_("Place your left middle finger on the fingerprint reader"), + N_("Place your left middle finger on %s"), + N_("Swipe your left middle finger across the fingerprint reader"), + N_("Swipe your left middle finger across %s") }, + { "left-ring-finger", + N_("Place your left ring finger on the fingerprint reader"), + N_("Place your left ring finger on %s"), + N_("Swipe your left ring finger across the fingerprint reader"), + N_("Swipe your left ring finger across %s") }, + { "left-little-finger", + N_("Place your left little finger on the fingerprint reader"), + N_("Place your left little finger on %s"), + N_("Swipe your left little finger across the fingerprint reader"), + N_("Swipe your left little finger across %s") }, + { "right-thumb", + N_("Place your right thumb on the fingerprint reader"), + N_("Place your right thumb on %s"), + N_("Swipe your right thumb across the fingerprint reader"), + N_("Swipe your right thumb across %s") }, + { "right-index-finger", + N_("Place your right index finger on the fingerprint reader"), + N_("Place your right index finger on %s"), + N_("Swipe your right index finger across the fingerprint reader"), + N_("Swipe your right index finger across %s") }, + { "right-middle-finger", + N_("Place your right middle finger on the fingerprint reader"), + N_("Place your right middle finger on %s"), + N_("Swipe your right middle finger across the fingerprint reader"), + N_("Swipe your right middle finger across %s") }, + { "right-ring-finger", + N_("Place your right ring finger on the fingerprint reader"), + N_("Place your right ring finger on %s"), + N_("Swipe your right ring finger across the fingerprint reader"), + N_("Swipe your right ring finger across %s") }, + { "right-little-finger", + N_("Place your right little finger on the fingerprint reader"), + N_("Place your right little finger on %s"), + N_("Swipe your right little finger across the fingerprint reader"), + N_("Swipe your right little finger across %s") }, + { NULL, NULL, NULL, NULL, NULL } }; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" -G_GNUC_UNUSED static char *finger_str_to_msg(const char *finger_name, const char *driver_name, gboolean is_swipe) +GNUC_UNUSED static char * +finger_str_to_msg (const char *finger_name, const char *driver_name, bool is_swipe) { - int i; - - if (finger_name == NULL) - return NULL; - - for (i = 0; fingers[i].dbus_name != NULL; i++) { - if (g_str_equal (fingers[i].dbus_name, finger_name)) { - if (is_swipe == FALSE) { - if (driver_name) - return g_strdup_printf (TR (fingers[i].place_str_specific), driver_name); - else - return g_strdup (TR (fingers[i].place_str_generic)); - } else { - if (driver_name) - return g_strdup_printf (TR (fingers[i].swipe_str_specific), driver_name); - else - return g_strdup (TR (fingers[i].swipe_str_generic)); - } - } - } - - return NULL; + int i; + + if (finger_name == NULL) + return NULL; + + for (i = 0; fingers[i].dbus_name != NULL; i++) + { + if (!str_equal (fingers[i].dbus_name, finger_name)) + continue; + if (is_swipe == false) + { + if (driver_name) + { + char *s; + int ret; + ret = asprintf (&s, TR (fingers[i].place_str_specific), driver_name); + return ret >= 0 ? s : NULL; + } + else + { + return strdup (TR (fingers[i].place_str_generic)); + } + } + else + { + if (driver_name) + { + char *s; + int ret; + ret = asprintf (&s, TR (fingers[i].swipe_str_specific), driver_name); + return ret >= 0 ? s : NULL; + } + else + { + return strdup (TR (fingers[i].swipe_str_generic)); + } + } + } + + return NULL; } #pragma GCC diagnostic pop @@ -121,25 +159,27 @@ G_GNUC_UNUSED static char *finger_str_to_msg(const char *finger_name, const char * verify-match * verify-unknown-error */ -G_GNUC_UNUSED static const char *verify_result_str_to_msg(const char *result, gboolean is_swipe) +GNUC_UNUSED static const char * +verify_result_str_to_msg (const char *result, bool is_swipe) { - if (result == NULL) - return NULL; - - if (strcmp (result, "verify-retry-scan") == 0) { - if (is_swipe == FALSE) - return N_("Place your finger on the reader again"); - else - return N_("Swipe your finger again"); - } - if (strcmp (result, "verify-swipe-too-short") == 0) - return N_("Swipe was too short, try again"); - if (strcmp (result, "verify-finger-not-centered") == 0) - return N_("Your finger was not centered, try swiping your finger again"); - if (strcmp (result, "verify-remove-and-retry") == 0) - return N_("Remove your finger, and try swiping your finger again"); - - return NULL; + if (result == NULL) + return NULL; + + if (strcmp (result, "verify-retry-scan") == 0) + { + if (is_swipe == false) + return TR (N_("Place your finger on the reader again")); + else + return TR (N_("Swipe your finger again")); + } + if (strcmp (result, "verify-swipe-too-short") == 0) + return TR (N_("Swipe was too short, try again")); + if (strcmp (result, "verify-finger-not-centered") == 0) + return TR (N_("Your finger was not centered, try swiping your finger again")); + if (strcmp (result, "verify-remove-and-retry") == 0) + return TR (N_("Remove your finger, and try swiping your finger again")); + + return NULL; } /* Cases not handled: @@ -147,24 +187,25 @@ G_GNUC_UNUSED static const char *verify_result_str_to_msg(const char *result, gb * enroll-failed * enroll-unknown-error */ -G_GNUC_UNUSED static const char *enroll_result_str_to_msg(const char *result, gboolean is_swipe) +GNUC_UNUSED static const char * +enroll_result_str_to_msg (const char *result, bool is_swipe) { - if (result == NULL) - return NULL; - - if (strcmp (result, "enroll-retry-scan") == 0 || strcmp (result, "enroll-stage-passed") == 0) { - if (is_swipe == FALSE) - return N_("Place your finger on the reader again"); - else - return N_("Swipe your finger again"); - } - if (strcmp (result, "enroll-swipe-too-short") == 0) - return N_("Swipe was too short, try again"); - if (strcmp (result, "enroll-finger-not-centered") == 0) - return N_("Your finger was not centered, try swiping your finger again"); - if (strcmp (result, "enroll-remove-and-retry") == 0) - return N_("Remove your finger, and try swiping your finger again"); - - return NULL; -} + if (result == NULL) + return NULL; + if (strcmp (result, "enroll-retry-scan") == 0 || strcmp (result, "enroll-stage-passed") == 0) + { + if (is_swipe == false) + return TR (N_("Place your finger on the reader again")); + else + return TR (N_("Swipe your finger again")); + } + if (strcmp (result, "enroll-swipe-too-short") == 0) + return TR (N_("Swipe was too short, try again")); + if (strcmp (result, "enroll-finger-not-centered") == 0) + return TR (N_("Your finger was not centered, try swiping your finger again")); + if (strcmp (result, "enroll-remove-and-retry") == 0) + return TR (N_("Remove your finger, and try swiping your finger again")); + + return NULL; +} diff --git a/pam/meson.build b/pam/meson.build new file mode 100644 index 0000000..e95bcde --- /dev/null +++ b/pam/meson.build @@ -0,0 +1,30 @@ +mapfile = files('pam_fprintd.ver') +pam_modules_dir = get_option('pam_modules_dir') +if pam_modules_dir == '' + pam_modules_dir = '/' / get_option('libdir') / 'security' +endif + +pam_fprintd = shared_module('pam_fprintd', + name_prefix: '', + include_directories: [ + include_directories('..'), + ], + sources: [ + 'pam_fprintd.c', + 'fingerprint-strings.h', + ], + dependencies: [ + libsystemd_dep, + pam_dep, + ], + c_args: [ + '-DLOCALEDIR="@0@"'.format(localedir), + ], + link_args: [ + '-Wl,--version-script,@0@/@1@'.format(meson.source_root(), mapfile[0]), + '-Wl,--unresolved-symbols=report-all', + ], + link_depends: mapfile, + install: true, + install_dir: pam_modules_dir, +) diff --git a/pam/pam_fprintd.c b/pam/pam_fprintd.c index b2eee4d..021f168 100644 --- a/pam/pam_fprintd.c +++ b/pam/pam_fprintd.c @@ -1,7 +1,7 @@ /* * pam_fprint: PAM module for fingerprint authentication through fprintd * Copyright (C) 2007 Daniel Drake - * Copyright (C) 2008 Bastien Nocera + * Copyright (C) 2008-2014, 2017-2020 Bastien Nocera * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,511 +20,801 @@ #include +#define _GNU_SOURCE #include #include +#include #include #include #include #include +#include -#include -#include -#include +#include +#include +#include #define PAM_SM_AUTH #include +#include -#include "marshal.h" - -#define TR(s) dgettext(GETTEXT_PACKAGE, s) +#define _(s) ((char *) dgettext (GETTEXT_PACKAGE, s)) +#define TR(s) dgettext (GETTEXT_PACKAGE, s) +#define N_(s) (s) #include "fingerprint-strings.h" +#include "pam_fprintd_autoptrs.h" #define DEFAULT_MAX_TRIES 3 #define DEFAULT_TIMEOUT 30 +#define MIN_TIMEOUT 10 +#define DEBUG_MATCH "debug=" #define MAX_TRIES_MATCH "max-tries=" #define TIMEOUT_MATCH "timeout=" -#define D(pamh, ...) { \ - if (debug) { \ - char *s; \ - s = g_strdup_printf (__VA_ARGS__); \ - send_debug_msg (pamh, s); \ - g_free (s); \ - } \ -} - +static bool debug = false; +static unsigned max_tries = DEFAULT_MAX_TRIES; +static unsigned timeout = DEFAULT_TIMEOUT; -static gboolean debug = FALSE; -static guint max_tries = DEFAULT_MAX_TRIES; -static guint timeout = DEFAULT_TIMEOUT; +#define USEC_PER_SEC ((uint64_t) 1000000ULL) +#define NSEC_PER_USEC ((uint64_t) 1000ULL) -static gboolean send_info_msg(pam_handle_t *pamh, const char *msg) +static uint64_t +now (void) { - const struct pam_message mymsg = { - .msg_style = PAM_TEXT_INFO, - .msg = msg, - }; - const struct pam_message *msgp = &mymsg; - const struct pam_conv *pc; - struct pam_response *resp; - int r; - - r = pam_get_item(pamh, PAM_CONV, (const void **) &pc); - if (r != PAM_SUCCESS) - return FALSE; - - if (!pc || !pc->conv) - return FALSE; - - return (pc->conv(1, &msgp, &resp, pc->appdata_ptr) == PAM_SUCCESS); + struct timespec ts; + + clock_gettime (CLOCK_MONOTONIC, &ts); + return (uint64_t) ts.tv_sec * USEC_PER_SEC + (uint64_t) ts.tv_nsec / NSEC_PER_USEC; } -static gboolean send_err_msg(pam_handle_t *pamh, const char *msg) +static bool +str_has_prefix (const char *s, const char *prefix) { - const struct pam_message mymsg = { - .msg_style = PAM_ERROR_MSG, - .msg = msg, - }; - const struct pam_message *msgp = &mymsg; - const struct pam_conv *pc; - struct pam_response *resp; - int r; - - r = pam_get_item(pamh, PAM_CONV, (const void **) &pc); - if (r != PAM_SUCCESS) - return FALSE; - - if (!pc || !pc->conv) - return FALSE; - - return (pc->conv(1, &msgp, &resp, pc->appdata_ptr) == PAM_SUCCESS); + if (s == NULL || prefix == NULL) + return false; + return strncmp (s, prefix, strlen (prefix)) == 0; } -static void send_debug_msg(pam_handle_t *pamh, const char *msg) +static bool +send_msg (pam_handle_t *pamh, const char *msg, int style) { - gconstpointer item; - const char *service; - - if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS || !item) - service = ""; - else - service = item; + const struct pam_message mymsg = { + .msg_style = style, + .msg = msg, + }; + const struct pam_message *msgp = &mymsg; + const struct pam_conv *pc; + struct pam_response *resp; - openlog (service, LOG_CONS | LOG_PID, LOG_AUTHPRIV); + if (pam_get_item (pamh, PAM_CONV, (const void **) &pc) != PAM_SUCCESS) + return false; - syslog (LOG_AUTHPRIV|LOG_WARNING, "%s(%s): %s", "pam_fprintd", service, msg); - - closelog (); + if (!pc || !pc->conv) + return false; + return pc->conv (1, &msgp, &resp, pc->appdata_ptr) == PAM_SUCCESS; } -static DBusGProxy *create_manager (pam_handle_t *pamh, DBusGConnection **ret_conn, GMainLoop **ret_loop) +static bool +send_info_msg (pam_handle_t *pamh, const char *msg) { - DBusGConnection *connection; - DBusConnection *conn; - DBusGProxy *manager; - DBusError error; - GMainLoop *loop; - GMainContext *ctx; - - /* Otherwise dbus-glib doesn't setup it value types */ - connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, NULL); - - if (connection != NULL) - dbus_g_connection_unref (connection); - - /* And set us up a private D-Bus connection */ - dbus_error_init (&error); - conn = dbus_bus_get_private (DBUS_BUS_SYSTEM, &error); - if (conn == NULL) { - D(pamh, "Error with getting the bus: %s", error.message); - dbus_error_free (&error); - return NULL; - } - - /* Set up our own main loop context */ - ctx = g_main_context_new (); - loop = g_main_loop_new (ctx, FALSE); - dbus_connection_setup_with_g_main (conn, ctx); - - connection = dbus_connection_get_g_connection (conn); - - manager = dbus_g_proxy_new_for_name(connection, - "net.reactivated.Fprint", - "/net/reactivated/Fprint/Manager", - "net.reactivated.Fprint.Manager"); - *ret_conn = connection; - *ret_loop = loop; - - return manager; + return send_msg (pamh, msg, PAM_TEXT_INFO); } -static void close_and_unref (DBusGConnection *connection) +static bool +send_err_msg (pam_handle_t *pamh, const char *msg) { - DBusConnection *conn; - - conn = dbus_g_connection_get_connection (connection); - dbus_connection_close (conn); - dbus_g_connection_unref (connection); + return send_msg (pamh, msg, PAM_ERROR_MSG); } -static void unref_loop (GMainLoop *loop) +static char * +open_device (pam_handle_t *pamh, + sd_bus *bus, + bool *has_multiple_devices) { - GMainContext *ctx; - - /* The main context was created separately, so - * we'll need to unref it ourselves */ - ctx = g_main_loop_get_context (loop); - g_main_loop_unref (loop); - g_main_context_unref (ctx); + pf_auto (sd_bus_error) error = SD_BUS_ERROR_NULL; + pf_autoptr (sd_bus_message) m = NULL; + size_t num_devices; + const char *path = NULL; + const char *s; + int r; + + *has_multiple_devices = false; + + if (sd_bus_call_method (bus, + "net.reactivated.Fprint", + "/net/reactivated/Fprint/Manager", + "net.reactivated.Fprint.Manager", + "GetDevices", + &error, + &m, + NULL) < 0) + { + pam_syslog (pamh, LOG_ERR, "GetDevices failed: %s", error.message); + return NULL; + } + + r = sd_bus_message_enter_container (m, 'a', "o"); + if (r < 0) + { + pam_syslog (pamh, LOG_ERR, "Failed to parse answer from GetDevices(): %d", r); + return NULL; + } + + if (sd_bus_message_read_basic (m, 'o', &path) < 0) + return NULL; + + num_devices = 1; + while ((r = sd_bus_message_read_basic (m, 'o', &s)) > 0) + num_devices++; + *has_multiple_devices = (num_devices > 1); + if (debug) + pam_syslog (pamh, LOG_DEBUG, "Using device %s (out of %ld devices)", path, num_devices); + + sd_bus_message_exit_container (m); + + return path ? strdup (path) : NULL; } -#define DBUS_TYPE_G_OBJECT_PATH_ARRAY (dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH)) +typedef struct +{ + char *dev; + bool has_multiple_devices; + + unsigned max_tries; + char *result; + bool timed_out; + bool is_swipe; + bool verify_started; + int verify_ret; + pam_handle_t *pamh; + + char *driver; +} verify_data; -static DBusGProxy *open_device(pam_handle_t *pamh, DBusGConnection *connection, DBusGProxy *manager, gboolean *has_multiple_devices) +static void +verify_data_free (verify_data *data) { - GError *error = NULL; - const char *path; - DBusGProxy *dev; - GPtrArray *paths_array; - const char **paths; - - if (!dbus_g_proxy_call (manager, "GetDevices", &error, - G_TYPE_INVALID, DBUS_TYPE_G_OBJECT_PATH_ARRAY, - &paths_array, G_TYPE_INVALID)) { - D(pamh, "get_devices failed: %s", error->message); - g_error_free (error); - return NULL; - } - - if (paths_array == NULL || paths_array->len == 0) { - if (paths_array != NULL) - g_ptr_array_free (paths_array, TRUE); - D(pamh, "No devices found\n"); - return NULL; - } - - *has_multiple_devices = (paths_array->len > 1); - paths = (const char **)paths_array->pdata; - path = paths[0]; - - D(pamh, "Using device %s\n", path); - - dev = dbus_g_proxy_new_for_name(connection, - "net.reactivated.Fprint", - path, - "net.reactivated.Fprint.Device"); - - g_ptr_array_free (paths_array, TRUE); - - return dev; + free (data->result); + free (data->driver); + free (data->dev); + free (data); } -typedef struct { - guint max_tries; - char *result; - gboolean timed_out; - gboolean is_swipe; - pam_handle_t *pamh; - GMainLoop *loop; +PF_DEFINE_AUTOPTR_CLEANUP_FUNC (verify_data, verify_data_free) - char *driver; -} verify_data; +static int +verify_result (sd_bus_message *m, + void *userdata, + sd_bus_error *ret_error) +{ + verify_data *data = userdata; + const char *msg; + const char *result = NULL; + /* see https://github.com/systemd/systemd/issues/14643 */ + uint64_t done = false; + int r; + + 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)", + sd_bus_message_get_interface (m), + sd_bus_message_get_member (m)); + return 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); + 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; + } + + if (debug) + pam_syslog (data->pamh, LOG_DEBUG, "Verify result: %s (done: %d)", result, done ? 1 : 0); + + if (data->result) + { + free (data->result); + data->result = NULL; + } + + if (done && result) + { + data->result = strdup (result); + return 0; + } + + 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); + + return 0; +} -static void verify_result(GObject *object, const char *result, gboolean done, gpointer user_data) +static int +verify_finger_selected (sd_bus_message *m, + void *userdata, + sd_bus_error *ret_error) { - verify_data *data = user_data; - const char *msg; - - D(data->pamh, "Verify result: %s\n", result); - if (done != FALSE) { - data->result = g_strdup (result); - g_main_loop_quit (data->loop); - return; - } - - msg = TR(verify_result_str_to_msg (result, data->is_swipe)); - send_err_msg (data->pamh, msg); + verify_data *data = userdata; + const char *finger_name = NULL; + pf_autofree char *msg = NULL; + + if (sd_bus_message_read_basic (m, 's', &finger_name) < 0) + { + 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; + } + + 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) + pam_syslog (data->pamh, LOG_DEBUG, "verify_finger_selected %s", msg); + send_info_msg (data->pamh, msg); + + return 0; } -static void verify_finger_selected(GObject *object, const char *finger_name, gpointer user_data) +/* See https://github.com/systemd/systemd/issues/14636 */ +static int +get_property_string (sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + char **ret) { - verify_data *data = user_data; - char *msg; - msg = finger_str_to_msg(finger_name, data->driver, data->is_swipe); + pf_autoptr (sd_bus_message) reply = NULL; + const char *s; + char *n; + int r; + + r = sd_bus_call_method (bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", interface, member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container (reply, 'v', "s"); + if (r < 0) + return sd_bus_error_set_errno (error, r); + + r = sd_bus_message_read_basic (reply, 's', &s); + if (r < 0) + return sd_bus_error_set_errno (error, r); + + n = strdup (s); + if (!n) + return sd_bus_error_set_errno (error, -ENOMEM); - D(data->pamh, "verify_finger_selected %s", msg); - send_info_msg (data->pamh, msg); - g_free (msg); + *ret = n; + return 0; } -static gboolean verify_timeout_cb (gpointer user_data) + +static int +verify_started_cb (sd_bus_message *m, + void *userdata, + sd_bus_error *ret_error) { - verify_data *data = user_data; + 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->timed_out = TRUE; - send_info_msg (data->pamh, "Verification timed out"); - g_main_loop_quit (data->loop); + data->verify_started = true; - return FALSE; + return 1; } -static int do_verify(GMainLoop *loop, pam_handle_t *pamh, DBusGProxy *dev, gboolean has_multiple_devices) +static int +do_verify (sd_bus *bus, + verify_data *data) { - GError *error = NULL; - GHashTable *props; - DBusGProxy *p; - verify_data *data; - int ret; - - data = g_new0 (verify_data, 1); - data->max_tries = max_tries; - data->pamh = pamh; - data->loop = loop; - - /* Get some properties for the device */ - p = dbus_g_proxy_new_from_proxy (dev, "org.freedesktop.DBus.Properties", NULL); - - if (dbus_g_proxy_call (p, "GetAll", NULL, 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)) { - const char *scan_type; - if (has_multiple_devices) - data->driver = g_value_dup_string (g_hash_table_lookup (props, "name")); - scan_type = g_value_dup_string (g_hash_table_lookup (props, "scan-type")); - if (g_str_equal (scan_type, "swipe")) - data->is_swipe = TRUE; - g_hash_table_destroy (props); - } - - g_object_unref (p); - - dbus_g_proxy_add_signal(dev, "VerifyStatus", G_TYPE_STRING, G_TYPE_BOOLEAN, NULL); - dbus_g_proxy_add_signal(dev, "VerifyFingerSelected", G_TYPE_STRING, NULL); - dbus_g_proxy_connect_signal(dev, "VerifyStatus", G_CALLBACK(verify_result), - data, NULL); - dbus_g_proxy_connect_signal(dev, "VerifyFingerSelected", G_CALLBACK(verify_finger_selected), - data, NULL); - - ret = PAM_AUTH_ERR; - - while (ret == PAM_AUTH_ERR && data->max_tries > 0) { - GSource *source; - - /* Set up the timeout on our non-default context */ - source = g_timeout_source_new_seconds (timeout); - g_source_attach (source, g_main_loop_get_context (loop)); - g_source_set_callback (source, verify_timeout_cb, data, NULL); - - data->timed_out = FALSE; - - if (!dbus_g_proxy_call (dev, "VerifyStart", &error, G_TYPE_STRING, "any", G_TYPE_INVALID, G_TYPE_INVALID)) { - if (dbus_g_error_has_name(error, "net.reactivated.Fprint.Error.NoEnrolledPrints")) - ret = PAM_USER_UNKNOWN; - - D(pamh, "VerifyStart failed: %s", error->message); - g_error_free (error); - - g_source_destroy (source); - g_source_unref (source); - break; - } - - g_main_loop_run (loop); - - g_source_destroy (source); - g_source_unref (source); - - /* Ignore errors from VerifyStop */ - dbus_g_proxy_call (dev, "VerifyStop", NULL, G_TYPE_INVALID, G_TYPE_INVALID); - - if (data->timed_out) { - ret = PAM_AUTHINFO_UNAVAIL; - break; - } else { - if (g_str_equal (data->result, "verify-no-match")) { - send_err_msg (data->pamh, "Failed to match fingerprint"); - ret = PAM_AUTH_ERR; - } else if (g_str_equal (data->result, "verify-match")) - ret = PAM_SUCCESS; - else if (g_str_equal (data->result, "verify-unknown-error")) - ret = PAM_AUTHINFO_UNAVAIL; - else if (g_str_equal (data->result, "verify-disconnected")) { - ret = PAM_AUTHINFO_UNAVAIL; - g_free (data->result); - break; - } else { - send_info_msg (data->pamh, "An unknown error occurred"); - ret = PAM_AUTH_ERR; - g_free (data->result); - break; - } - g_free (data->result); - data->result = NULL; - } - data->max_tries--; - } - - dbus_g_proxy_disconnect_signal(dev, "VerifyStatus", G_CALLBACK(verify_result), data); - dbus_g_proxy_disconnect_signal(dev, "VerifyFingerSelected", G_CALLBACK(verify_finger_selected), data); - - g_free (data->driver); - g_free (data); - - return ret; + pf_autoptr (sd_bus_slot) verify_status_slot = NULL; + pf_autoptr (sd_bus_slot) verify_finger_selected_slot = NULL; + pf_autofree char *scan_type = NULL; + int r; + + /* Get some properties for the device */ + r = get_property_string (bus, + "net.reactivated.Fprint", + data->dev, + "net.reactivated.Fprint.Device", + "scan-type", + NULL, + &scan_type); + if (r < 0) + pam_syslog (data->pamh, LOG_ERR, "Failed to get scan-type for %s: %d", data->dev, r); + if (debug) + pam_syslog (data->pamh, LOG_DEBUG, "scan-type for %s: %s", data->dev, scan_type); + if (str_equal (scan_type, "swipe")) + data->is_swipe = true; + + if (data->has_multiple_devices) + { + get_property_string (bus, + "net.reactivated.Fprint", + data->dev, + "net.reactivated.Fprint.Device", + "name", + NULL, + &data->driver); + if (r < 0) + pam_syslog (data->pamh, LOG_ERR, "Failed to get driver name for %s: %d", data->dev, r); + if (debug && r == 0) + pam_syslog (data->pamh, LOG_DEBUG, "driver name for %s: %s", data->dev, data->driver); + } + + sd_bus_match_signal (bus, + &verify_status_slot, + "net.reactivated.Fprint", + data->dev, + "net.reactivated.Fprint.Device", + "VerifyStatus", + verify_result, + data); + + sd_bus_match_signal (bus, + &verify_finger_selected_slot, + "net.reactivated.Fprint", + data->dev, + "net.reactivated.Fprint.Device", + "VerifyFingerSelected", + verify_finger_selected, + data); + + while (data->max_tries > 0) + { + uint64_t verification_end = now () + (timeout * USEC_PER_SEC); + + data->timed_out = false; + data->verify_started = false; + data->verify_ret = PAM_INCOMPLETE; + + 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", + data->dev, + "net.reactivated.Fprint.Device", + "VerifyStart", + verify_started_cb, + data, + "s", + "any"); + + if (r < 0) + { + if (debug) + pam_syslog (data->pamh, LOG_DEBUG, "VerifyStart call failed: %d", r); + break; + } + + for (;;) + { + int64_t wait_time; + + wait_time = verification_end - now (); + if (wait_time <= 0) + break; + + r = sd_bus_process (bus, NULL); + if (r < 0) + break; + if (data->verify_ret != PAM_INCOMPLETE) + break; + if (!data->verify_started) + continue; + if (data->result != NULL) + break; + if (r == 0) + { + if (debug) + { + pam_syslog (data->pamh, LOG_DEBUG, + "Waiting for %" PRId64 " seconds (%" PRId64 " usecs)", + wait_time / USEC_PER_SEC, + wait_time); + } + if (sd_bus_wait (bus, wait_time) < 0) + break; + } + } + + if (data->verify_ret != PAM_INCOMPLETE) + return data->verify_ret; + + if (now () >= verification_end) + { + data->timed_out = true; + send_info_msg (data->pamh, _("Verification timed out")); + } + + /* Ignore errors from VerifyStop */ + data->verify_started = false; + sd_bus_call_method (bus, + "net.reactivated.Fprint", + data->dev, + "net.reactivated.Fprint.Device", + "VerifyStop", + NULL, + NULL, + NULL, + NULL); + + if (data->timed_out) + { + return PAM_AUTHINFO_UNAVAIL; + } + else + { + if (str_equal (data->result, "verify-no-match")) + { + send_err_msg (data->pamh, "Failed to match fingerprint"); + } + else if (str_equal (data->result, "verify-match")) + { + return PAM_SUCCESS; + } + else if (str_equal (data->result, "verify-unknown-error")) + { + return PAM_AUTHINFO_UNAVAIL; + } + else if (str_equal (data->result, "verify-disconnected")) + { + return PAM_AUTHINFO_UNAVAIL; + } + else + { + send_err_msg (data->pamh, _("An unknown error occurred")); + return PAM_AUTH_ERR; + } + } + data->max_tries--; + } + + if (data->max_tries == 0) + return PAM_MAXTRIES; + + return PAM_AUTH_ERR; } -static gboolean user_has_prints(DBusGProxy *dev, const char *username) +static bool +user_has_prints (pam_handle_t *pamh, + sd_bus *bus, + const char *dev, + const char *username) { - char **fingers; - gboolean have_prints; - - if (!dbus_g_proxy_call (dev, "ListEnrolledFingers", NULL, - G_TYPE_STRING, username, G_TYPE_INVALID, - G_TYPE_STRV, &fingers, G_TYPE_INVALID)) { - /* If ListEnrolledFingers fails then verification should - * also fail (both use the same underlying call), so we - * report FALSE here and bail out early. */ - return FALSE; - } - - have_prints = fingers != NULL && g_strv_length (fingers) > 0; - g_strfreev (fingers); - - return have_prints; + pf_auto (sd_bus_error) error = SD_BUS_ERROR_NULL; + pf_autoptr (sd_bus_message) m = NULL; + size_t num_fingers = 0; + const char *s; + int r; + + r = sd_bus_call_method (bus, + "net.reactivated.Fprint", + dev, + "net.reactivated.Fprint.Device", + "ListEnrolledFingers", + &error, + &m, + "s", + username); + if (r < 0) + { + /* If ListEnrolledFingers fails then verification should + * also fail (both use the same underlying call), so we + * report false here and bail out early. */ + if (debug) + pam_syslog (pamh, LOG_DEBUG, "ListEnrolledFingers failed for %s: %s", + username, error.message); + return false; + } + + r = sd_bus_message_enter_container (m, 'a', "s"); + if (r < 0) + { + pam_syslog (pamh, LOG_ERR, "Failed to parse answer from ListEnrolledFingers(): %d", r); + return false; + } + + while ((r = sd_bus_message_read_basic (m, 's', &s)) > 0) + num_fingers++; + sd_bus_message_exit_container (m); + + return num_fingers > 0; } -static void release_device(pam_handle_t *pamh, DBusGProxy *dev) +static void +release_device (pam_handle_t *pamh, + sd_bus *bus, + const char *dev) { - GError *error = NULL; - if (!dbus_g_proxy_call (dev, "Release", &error, G_TYPE_INVALID, G_TYPE_INVALID)) { - D(pamh, "ReleaseDevice failed: %s\n", error->message); - g_error_free (error); - } + pf_auto (sd_bus_error) error = SD_BUS_ERROR_NULL; + + if (sd_bus_call_method (bus, + "net.reactivated.Fprint", + dev, + "net.reactivated.Fprint.Device", + "Release", + &error, + NULL, + NULL, + NULL) < 0) + pam_syslog (pamh, LOG_ERR, "ReleaseDevice failed: %s", error.message); } -static gboolean claim_device(pam_handle_t *pamh, DBusGProxy *dev, const char *username) +static bool +claim_device (pam_handle_t *pamh, + sd_bus *bus, + const char *dev, + const char *username) { - GError *error = NULL; + pf_auto (sd_bus_error) error = SD_BUS_ERROR_NULL; + + if (sd_bus_call_method (bus, + "net.reactivated.Fprint", + dev, + "net.reactivated.Fprint.Device", + "Claim", + &error, + NULL, + "s", + username) < 0) + { + if (debug) + pam_syslog (pamh, LOG_DEBUG, "failed to claim device %s", error.message); + return false; + } + + return true; +} + +static int +name_owner_changed (sd_bus_message *m, + void *userdata, + sd_bus_error *ret_error) +{ + verify_data *data = userdata; + const char *name = NULL; + const char *old_owner = NULL; + const char *new_owner = NULL; + + if (sd_bus_message_read (m, "sss", &name, &old_owner, &new_owner) < 0) + { + pam_syslog (data->pamh, LOG_ERR, "Failed to parse NameOwnerChanged signal: %d", errno); + data->verify_ret = PAM_AUTHINFO_UNAVAIL; + return 0; + } - if (!dbus_g_proxy_call (dev, "Claim", &error, G_TYPE_STRING, username, G_TYPE_INVALID, G_TYPE_INVALID)) { - D(pamh, "failed to claim device %s\n", error->message); - g_error_free (error); - return FALSE; - } + if (strcmp (name, "net.reactivated.Fprint") != 0) + return 0; - return TRUE; + /* 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) +static int +do_auth (pam_handle_t *pamh, const char *username) { - DBusGProxy *manager; - DBusGConnection *connection; - DBusGProxy *dev; - GMainLoop *loop; - gboolean have_prints; - gboolean has_multiple_devices; - int ret = PAM_AUTHINFO_UNAVAIL; - - manager = create_manager (pamh, &connection, &loop); - if (manager == NULL) - return PAM_AUTHINFO_UNAVAIL; - - dev = open_device(pamh, connection, manager, &has_multiple_devices); - g_object_unref (manager); - if (!dev) { - unref_loop (loop); - close_and_unref (connection); - return PAM_AUTHINFO_UNAVAIL; - } - - have_prints = user_has_prints(dev, username); - D(pamh, "prints registered: %s\n", have_prints ? "yes" : "no"); - - if (have_prints) { - if (claim_device (pamh, dev, username)) { - ret = do_verify (loop, pamh, dev, has_multiple_devices); - release_device (pamh, dev); - } - } - - unref_loop (loop); - g_object_unref (dev); - close_and_unref (connection); - - return ret; + bool have_prints; + + pf_autoptr (verify_data) data = NULL; + 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; + } + + data->dev = open_device (pamh, bus, &data->has_multiple_devices); + if (data->dev == NULL) + return PAM_AUTHINFO_UNAVAIL; + + have_prints = user_has_prints (pamh, bus, data->dev, username); + if (debug) + pam_syslog (pamh, LOG_DEBUG, "prints registered: %s\n", have_prints ? "yes" : "no"); + + if (!have_prints) + return PAM_AUTHINFO_UNAVAIL; + + /* Only connect to NameOwnerChanged when needed. In case of automatic startup + * we rely on the fact that we never see those signals. + */ + 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; + } + + return PAM_AUTHINFO_UNAVAIL; } -PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, - const char **argv) +static bool +is_remote (pam_handle_t *pamh) { - const char *rhost = NULL; - const char *username; - guint i; - int r; - - bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); - bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - -#if !GLIB_CHECK_VERSION (2, 36, 0) - g_type_init(); -#endif - - dbus_g_object_register_marshaller (fprintd_marshal_VOID__STRING_BOOLEAN, - G_TYPE_NONE, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INVALID); - - pam_get_item(pamh, PAM_RHOST, (const void **)(const void*) &rhost); - - /* NULL or empty rhost if the host information is not available or set. - * "localhost" if the host is local. - * We want to not run for known remote hosts */ - if (rhost != NULL && - *rhost != '\0' && - strcmp (rhost, "localhost") != 0) { - return PAM_AUTHINFO_UNAVAIL; - } - - r = pam_get_user(pamh, &username, NULL); - if (r != PAM_SUCCESS) - return PAM_AUTHINFO_UNAVAIL; - - for (i = 0; i < argc; i++) { - if (argv[i] != NULL) { - if(g_str_equal (argv[i], "debug")) { - g_message ("debug on"); - debug = TRUE; - } - else if (strncmp(argv[i], MAX_TRIES_MATCH, strlen (MAX_TRIES_MATCH)) == 0 && strlen(argv[i]) == strlen (MAX_TRIES_MATCH) + 1) { - max_tries = atoi (argv[i] + strlen (MAX_TRIES_MATCH)); - if (max_tries < 1) - max_tries = DEFAULT_MAX_TRIES; - D(pamh, "max_tries specified as: %d", max_tries); - } - else if (strncmp(argv[i], TIMEOUT_MATCH, strlen (TIMEOUT_MATCH)) == 0 && strlen(argv[i]) <= strlen (TIMEOUT_MATCH) + 2) { - timeout = atoi (argv[i] + strlen (TIMEOUT_MATCH)); - if (timeout < 10) - timeout = DEFAULT_TIMEOUT; - D(pamh, "timeout specified as: %d", timeout); - } - } - } - - r = do_auth(pamh, username); - - return r; + const char *rhost = NULL; + + pam_get_item (pamh, PAM_RHOST, (const void **) (const void *) &rhost); + + /* NULL or empty rhost if the host information is not available or set. + * "localhost" if the host is local. + * We want to not run for known remote hosts */ + if (rhost != NULL && + *rhost != '\0' && + strcmp (rhost, "localhost") != 0) + return true; + + if (sd_session_is_remote (NULL) > 0) + return true; + + return false; } -PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, - const char **argv) +PAM_EXTERN int +pam_sm_authenticate (pam_handle_t *pamh, int flags, int argc, + const char **argv) { - return PAM_SUCCESS; + const char *username; + int i; + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + if (is_remote (pamh)) + return PAM_AUTHINFO_UNAVAIL; + + if (pam_get_user (pamh, &username, NULL) != PAM_SUCCESS) + return PAM_AUTHINFO_UNAVAIL; + + for (i = 0; i < argc; i++) + { + if (argv[i] != NULL) + { + if (str_equal (argv[i], "debug")) + { + pam_syslog (pamh, LOG_DEBUG, "debug on"); + debug = true; + } + else if (str_has_prefix (argv[i], DEBUG_MATCH)) + { + pam_syslog (pamh, LOG_DEBUG, "debug on"); + const char *value; + + value = argv[i] + strlen (DEBUG_MATCH); + if (str_equal (value, "on") || + str_equal (value, "true") || + str_equal (value, "1")) + { + pam_syslog (pamh, LOG_DEBUG, "debug on"); + debug = true; + } + else if (str_equal (value, "off") || + str_equal (value, "false") || + str_equal (value, "0")) + { + debug = false; + } + else + { + 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) + { + max_tries = atoi (argv[i] + strlen (MAX_TRIES_MATCH)); + if (max_tries < 1) + { + if (debug) + pam_syslog (pamh, LOG_DEBUG, "invalid max tries '%s', using %d", + argv[i] + strlen (MAX_TRIES_MATCH), DEFAULT_MAX_TRIES); + max_tries = DEFAULT_MAX_TRIES; + } + if (debug) + 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) + { + timeout = atoi (argv[i] + strlen (TIMEOUT_MATCH)); + if (timeout < MIN_TIMEOUT) + { + if (debug) + pam_syslog (pamh, LOG_DEBUG, "timeout %d secs too low, using %d", + timeout, MIN_TIMEOUT); + timeout = MIN_TIMEOUT; + } + else if (debug) + { + pam_syslog (pamh, LOG_DEBUG, "timeout specified as: %d secs", timeout); + } + } + } + } + + return do_auth (pamh, username); } -PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, - const char **argv) +PAM_EXTERN int +pam_sm_setcred (pam_handle_t *pamh, int flags, int argc, + const char **argv) { - return PAM_SUCCESS; + return PAM_SUCCESS; } +PAM_EXTERN int +pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return PAM_SUCCESS; +} diff --git a/pam/pam_fprintd.ver b/pam/pam_fprintd.ver new file mode 100644 index 0000000..a8d3722 --- /dev/null +++ b/pam/pam_fprintd.ver @@ -0,0 +1,6 @@ +{ +global: + pam_*; +local: + *; +}; diff --git a/pam/pam_fprintd_autoptrs.h b/pam/pam_fprintd_autoptrs.h new file mode 100644 index 0000000..3a7fef6 --- /dev/null +++ b/pam/pam_fprintd_autoptrs.h @@ -0,0 +1,61 @@ +/* + * pam_fprint: PAM module for fingerprint authentication through fprintd + * Copyright (C) 2020 Marco Trevisan + * + * 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 + +/* 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) diff --git a/po/POTFILES.in b/po/POTFILES.in index b4f402b..1b21e2f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -3,3 +3,4 @@ src/main.c src/manager.c src/device.c pam/fingerprint-strings.h +pam/pam_fprintd.c diff --git a/po/check-translations.sh b/po/check-translations.sh new file mode 100755 index 0000000..9754168 --- /dev/null +++ b/po/check-translations.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +po_dir=$(dirname $0) + +for i in $po_dir/*.po ; do + if ! grep -q `basename $i | sed 's,.po,,'` $po_dir/LINGUAS; then + echo '**********************************'; + echo '***' `basename $i | sed 's,.po,,'` missing from po/LINGUAS '***' ; + echo '**********************************'; + exit 1; + fi; +done; + +exit 0 diff --git a/po/meson.build b/po/meson.build new file mode 100644 index 0000000..ce0906a --- /dev/null +++ b/po/meson.build @@ -0,0 +1,6 @@ +i18n.gettext(meson.project_name(), preset: 'glib') + +test('check-translations', + find_program('check-translations.sh'), + suite: ['dist'], +) diff --git a/scripts/uncrustify.cfg b/scripts/uncrustify.cfg new file mode 100644 index 0000000..57d1532 --- /dev/null +++ b/scripts/uncrustify.cfg @@ -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 + diff --git a/scripts/uncrustify.sh b/scripts/uncrustify.sh new file mode 100755 index 0000000..a1cfc48 --- /dev/null +++ b/scripts/uncrustify.sh @@ -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 diff --git a/src/dbus-interactive-auth.patch b/src/dbus-interactive-auth.patch new file mode 100644 index 0000000..944089a --- /dev/null +++ b/src/dbus-interactive-auth.patch @@ -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); diff --git a/src/device.c b/src/device.c index 4609f31..8b97f6c 100644 --- a/src/device.c +++ b/src/device.c @@ -1,17 +1,18 @@ /* * /net/reactivated/Fprint/Device/foo object implementation * Copyright (C) 2008 Daniel Drake + * Copyright (C) 2020 Marco Trevisan * * 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. @@ -19,8 +20,6 @@ #include "config.h" -#include -#include #include #include #include @@ -30,1352 +29,1974 @@ #include #include -#include "fprintd-marshal.h" #include "fprintd.h" #include "storage.h" -static char *fingers[] = { - "unknown", - "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" +static const char *FINGERS_NAMES[] = { + [FP_FINGER_UNKNOWN] = "unknown", + [FP_FINGER_LEFT_THUMB] = "left-thumb", + [FP_FINGER_LEFT_INDEX] = "left-index-finger", + [FP_FINGER_LEFT_MIDDLE] = "left-middle-finger", + [FP_FINGER_LEFT_RING] = "left-ring-finger", + [FP_FINGER_LEFT_LITTLE] = "left-little-finger", + [FP_FINGER_RIGHT_THUMB] = "right-thumb", + [FP_FINGER_RIGHT_INDEX] = "right-index-finger", + [FP_FINGER_RIGHT_MIDDLE] = "right-middle-finger", + [FP_FINGER_RIGHT_RING] = "right-ring-finger", + [FP_FINGER_RIGHT_LITTLE] = "right-little-finger" }; -extern DBusGConnection *fprintd_dbus_conn; - -static void fprint_device_claim(FprintDevice *rdev, - const char *username, - DBusGMethodInvocation *context); -static void fprint_device_release(FprintDevice *rdev, - DBusGMethodInvocation *context); -static void fprint_device_verify_start(FprintDevice *rdev, - const char *finger_name, DBusGMethodInvocation *context); -static void fprint_device_verify_stop(FprintDevice *rdev, - DBusGMethodInvocation *context); -static void fprint_device_enroll_start(FprintDevice *rdev, - const char *finger_name, DBusGMethodInvocation *context); -static void fprint_device_enroll_stop(FprintDevice *rdev, - DBusGMethodInvocation *context); -static void fprint_device_list_enrolled_fingers(FprintDevice *rdev, - const char *username, - DBusGMethodInvocation *context); -static void fprint_device_delete_enrolled_fingers(FprintDevice *rdev, - const char *username, - DBusGMethodInvocation *context); -static void fprint_device_delete_enrolled_fingers2(FprintDevice *rdev, - DBusGMethodInvocation *context); - -#include "device-dbus-glue.h" +static void fprint_device_dbus_skeleton_iface_init (FprintDBusDeviceIface *); +static gboolean action_authorization_handler (GDBusInterfaceSkeleton *, + GDBusMethodInvocation *, + gpointer user_data); + +static GQuark quark_auth_user = 0; typedef enum { - ACTION_NONE = 0, - ACTION_IDENTIFY, - ACTION_VERIFY, - ACTION_ENROLL + ACTION_NONE = 0, + ACTION_IDENTIFY, + ACTION_VERIFY, + ACTION_ENROLL, + ACTION_OPEN, + ACTION_CLOSE, + ACTION_DELETE, } FprintDeviceAction; -struct session_data { - /* method invocation for async ClaimDevice() */ - DBusGMethodInvocation *context_claim_device; +typedef enum { + STATE_CLAIMED, + STATE_UNCLAIMED, + STATE_AUTO_CLAIM, + STATE_ANYTIME, +} FprintDeviceClaimState; - /* method invocation for async ReleaseDevice() */ - DBusGMethodInvocation *context_release_device; -}; +typedef struct +{ + volatile gint _refcount; -struct FprintDevicePrivate { - guint32 id; - FpDevice *dev; - struct session_data *session; + /* current method invocation */ + GDBusMethodInvocation *invocation; - PolkitAuthority *auth; + /* The current user of the device, if claimed */ + const char * const sender; - /* The current user of the device, if claimed */ - char *sender; + /* The current user of the device, or if allowed, + * what was passed as a username argument */ + const char * const username; - /* The current user of the device, or if allowed, - * what was passed as a username argument */ - char *username; + gboolean verify_status_reported; +} SessionData; - /* type of storage */ - int storage_type; +typedef struct +{ + guint32 id; + FpDevice *dev; + SessionData *_session; - /* Hashtable of connected clients */ - GHashTable *clients; + PolkitAuthority *auth; - /* Required to restart the operation on a retry failure. */ - FpPrint *verify_data; - GPtrArray *identify_data; - gint enroll_data; + /* Hashtable of connected clients */ + GHashTable *clients; - /* whether we're running an identify, or a verify */ - FprintDeviceAction current_action; - GCancellable *current_cancellable; - DBusGMethodInvocation *current_cancel_context; - /* Whether the device was disconnected */ - gboolean disconnected; -}; + /* Required to restart the operation on a retry failure. */ + FpPrint *verify_data; + GPtrArray *identify_data; + int enroll_data; -typedef struct FprintDevicePrivate FprintDevicePrivate; + /* whether we're running an identify, or a verify */ + FprintDeviceAction current_action; + GCancellable *current_cancellable; + GDBusMethodInvocation *current_cancel_invocation; +} FprintDevicePrivate; -#define DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), FPRINT_TYPE_DEVICE, FprintDevicePrivate)) +G_DEFINE_TYPE_WITH_CODE (FprintDevice, fprint_device, + FPRINT_DBUS_TYPE_DEVICE_SKELETON, + G_ADD_PRIVATE (FprintDevice) + G_IMPLEMENT_INTERFACE (FPRINT_DBUS_TYPE_DEVICE, + fprint_device_dbus_skeleton_iface_init)); enum fprint_device_properties { - FPRINT_DEVICE_CONSTRUCT_DEV = 1, - FPRINT_DEVICE_IN_USE, - FPRINT_DEVICE_NAME, - FPRINT_DEVICE_NUM_ENROLL, - FPRINT_DEVICE_SCAN_TYPE + FPRINT_DEVICE_CONSTRUCT_DEV = 1, + FPRINT_DEVICE_IN_USE, + FPRINT_DEVICE_NAME, + FPRINT_DEVICE_NUM_ENROLL, + FPRINT_DEVICE_SCAN_TYPE }; enum fprint_device_signals { - SIGNAL_VERIFY_STATUS, - SIGNAL_VERIFY_FINGER_SELECTED, - SIGNAL_ENROLL_STATUS, - NUM_SIGNALS, + SIGNAL_VERIFY_STATUS, + SIGNAL_VERIFY_FINGER_SELECTED, + SIGNAL_ENROLL_STATUS, + NUM_SIGNALS, }; -static GObjectClass *parent_class = NULL; static guint32 last_id = ~0; static guint signals[NUM_SIGNALS] = { 0, }; -static void fprint_device_finalize(GObject *object) +#ifndef POLKIT_HAS_AUTOPOINTERS +/* FIXME: Remove this once we're fine to depend on polkit 0.114 */ +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitAuthorizationResult, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitSubject, g_object_unref) +#endif + +static void +session_data_unref (SessionData *session) +{ + if (g_atomic_int_dec_and_test (&session->_refcount)) + { + g_clear_pointer ((char **) &session->sender, g_free); + g_clear_pointer ((char **) &session->username, g_free); + g_clear_object (&session->invocation); + g_free (session); + } +} +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SessionData, session_data_unref); + +static SessionData * +session_data_get (FprintDevicePrivate *priv) +{ + SessionData *invalid = (SessionData *) &priv->_session; + SessionData *cur; + + /* Get the current pointer and mark the pointer as "busy". */ + do + { + cur = priv->_session; + /* Swap if cur is valid, otherwise busy loop. */ + } + while (cur == invalid || !g_atomic_pointer_compare_and_exchange (&priv->_session, cur, invalid)); + + /* We can safely increase the reference count now. */ + if (cur) + g_atomic_int_inc (&cur->_refcount); + + /* Swap back, this must succeed. */ + if (!g_atomic_pointer_compare_and_exchange (&priv->_session, invalid, cur)) + g_assert_not_reached (); + + return cur; +} + +/* Pass NULL sender and username to unset session data. */ +static SessionData * +session_data_set_new (FprintDevicePrivate *priv, gchar *sender, gchar *username) +{ + SessionData *invalid = (SessionData *) &priv->_session; + SessionData *new = NULL; + SessionData *old; + + g_assert ((!sender && !username) || (sender && username)); + if (sender) + { + new = g_new0 (SessionData, 1); + /* Internal reference of the pointer and returned reference. */ + new->_refcount = 2; + *(char **) &new->sender = sender; + *(char **) &new->username = username; + } + + /* Get the current (but not if it is busy) and put the new one in place. */ + do + { + old = priv->_session; + /* Swap if old is valid, otherwise busy loop as someone is ref'ing it currently. */ + } + while (old == invalid || !g_atomic_pointer_compare_and_exchange (&priv->_session, old, new)); + + /* We can safely drop the our internal reference now. */ + if (old) + session_data_unref (old); + + return new; +} + +static void +fprint_device_dispose (GObject *object) { - FprintDevice *self = (FprintDevice *) object; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(self); + FprintDevice *self = (FprintDevice *) object; + FprintDevicePrivate *priv = fprint_device_get_instance_private (self); + + g_hash_table_remove_all (priv->clients); - g_hash_table_destroy (priv->clients); - /* FIXME close and stuff */ + G_OBJECT_CLASS (fprint_device_parent_class)->dispose (object); } -static void fprint_device_set_property(GObject *object, guint property_id, - const GValue *value, GParamSpec *pspec) +static void +fprint_device_finalize (GObject *object) { - FprintDevice *self = (FprintDevice *) object; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(self); - - switch (property_id) { - case FPRINT_DEVICE_CONSTRUCT_DEV: - priv->dev = g_value_dup_object(value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } + FprintDevice *self = (FprintDevice *) object; + FprintDevicePrivate *priv = fprint_device_get_instance_private (self); + + g_hash_table_destroy (priv->clients); + session_data_set_new (priv, NULL, NULL); + g_clear_object (&priv->auth); + g_clear_object (&priv->dev); + + if (priv->current_action != ACTION_NONE || + priv->_session || + priv->verify_data || + priv->identify_data || + priv->current_cancellable || + priv->current_cancel_invocation) + g_critical ("Device was not cleaned up properly before being finalized."); + + G_OBJECT_CLASS (fprint_device_parent_class)->finalize (object); } -static void fprint_device_get_property(GObject *object, guint property_id, - GValue *value, GParamSpec *pspec) +static void +fprint_device_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + FprintDevice *self = (FprintDevice *) object; + FprintDevicePrivate *priv = fprint_device_get_instance_private (self); + + switch (property_id) + { + case FPRINT_DEVICE_CONSTRUCT_DEV: + priv->dev = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +fprint_device_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) { - FprintDevice *self = (FprintDevice *) object; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(self); - - switch (property_id) { - case FPRINT_DEVICE_CONSTRUCT_DEV: - g_value_set_object(value, priv->dev); - break; - case FPRINT_DEVICE_IN_USE: - g_value_set_boolean(value, g_hash_table_size (priv->clients) != 0); - break; - case FPRINT_DEVICE_NAME: - g_value_set_static_string (value, fp_device_get_name (priv->dev)); - break; - case FPRINT_DEVICE_NUM_ENROLL: - if (priv->dev) - g_value_set_int (value, fp_device_get_nr_enroll_stages (priv->dev)); - else - g_value_set_int (value, -1); - break; - case FPRINT_DEVICE_SCAN_TYPE: { - const char *type; - - if (fp_device_get_scan_type (priv->dev) == FP_SCAN_TYPE_PRESS) - type = "press"; - else - type = "swipe"; - - g_value_set_static_string (value, type); - break; - } - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); - break; - } + FprintDevice *self = (FprintDevice *) object; + FprintDevicePrivate *priv = fprint_device_get_instance_private (self); + + switch (property_id) + { + case FPRINT_DEVICE_CONSTRUCT_DEV: + g_value_set_object (value, priv->dev); + break; + + case FPRINT_DEVICE_IN_USE: + g_value_set_boolean (value, g_hash_table_size (priv->clients) != 0); + break; + + case FPRINT_DEVICE_NAME: + g_value_set_static_string (value, fp_device_get_name (priv->dev)); + break; + + case FPRINT_DEVICE_NUM_ENROLL: + if (priv->dev) + g_value_set_int (value, fp_device_get_nr_enroll_stages (priv->dev)); + else + g_value_set_int (value, -1); + break; + + case FPRINT_DEVICE_SCAN_TYPE: { + const char *type; + + if (fp_device_get_scan_type (priv->dev) == FP_SCAN_TYPE_PRESS) + type = "press"; + else + type = "swipe"; + + g_value_set_static_string (value, type); + break; + } + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } } -static void fprint_device_class_init(FprintDeviceClass *klass) +static void +fprint_device_class_init (FprintDeviceClass *klass) { - GObjectClass *gobject_class = G_OBJECT_CLASS(klass); - GParamSpec *pspec; - - dbus_g_object_type_install_info(FPRINT_TYPE_DEVICE, - &dbus_glib_fprint_device_object_info); - parent_class = g_type_class_peek_parent(klass); - - gobject_class->finalize = fprint_device_finalize; - gobject_class->set_property = fprint_device_set_property; - gobject_class->get_property = fprint_device_get_property; - g_type_class_add_private(klass, sizeof(FprintDevicePrivate)); - - pspec = g_param_spec_object("dev", "Device", - "Set device construction property", - FP_TYPE_DEVICE, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE); - g_object_class_install_property(gobject_class, - FPRINT_DEVICE_CONSTRUCT_DEV, pspec); - - pspec = g_param_spec_boolean("in-use", "In use", - "Whether the device is currently in use", FALSE, - G_PARAM_READABLE); - g_object_class_install_property(gobject_class, - FPRINT_DEVICE_IN_USE, pspec); - - pspec = g_param_spec_string("name", "Name", - "The product name of the device", NULL, - G_PARAM_READABLE); - g_object_class_install_property(gobject_class, - FPRINT_DEVICE_NAME, pspec); - - pspec = g_param_spec_string("scan-type", "Scan Type", - "The scan type of the device", "press", - G_PARAM_READABLE); - g_object_class_install_property(gobject_class, - FPRINT_DEVICE_SCAN_TYPE, pspec); - - pspec = g_param_spec_int("num-enroll-stages", "Number of enrollments stages", - "Number of enrollment stages for the device.", - -1, G_MAXINT, -1, G_PARAM_READABLE); - g_object_class_install_property(gobject_class, - FPRINT_DEVICE_NUM_ENROLL, pspec); - - signals[SIGNAL_VERIFY_STATUS] = g_signal_new("verify-status", - G_TYPE_FROM_CLASS(gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - fprintd_marshal_VOID__STRING_BOOLEAN, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN); - signals[SIGNAL_ENROLL_STATUS] = g_signal_new("enroll-status", - G_TYPE_FROM_CLASS(gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - fprintd_marshal_VOID__STRING_BOOLEAN, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN); - signals[SIGNAL_VERIFY_FINGER_SELECTED] = g_signal_new("verify-finger-selected", - G_TYPE_FROM_CLASS(gobject_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; + + gobject_class->dispose = fprint_device_dispose; + gobject_class->finalize = fprint_device_finalize; + gobject_class->set_property = fprint_device_set_property; + gobject_class->get_property = fprint_device_get_property; + + pspec = g_param_spec_object ("dev", "Device", + "Set device construction property", + FP_TYPE_DEVICE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE); + g_object_class_install_property (gobject_class, + FPRINT_DEVICE_CONSTRUCT_DEV, pspec); + + pspec = g_param_spec_boolean ("in-use", "In use", + "Whether the device is currently in use", FALSE, + G_PARAM_READABLE); + g_object_class_install_property (gobject_class, + FPRINT_DEVICE_IN_USE, pspec); + + g_object_class_override_property (gobject_class, + FPRINT_DEVICE_NAME, + "name"); + + g_object_class_override_property (gobject_class, + FPRINT_DEVICE_SCAN_TYPE, + "scan-type"); + + g_object_class_override_property (gobject_class, + FPRINT_DEVICE_NUM_ENROLL, + "num-enroll-stages"); + + signals[SIGNAL_VERIFY_STATUS] = + g_signal_lookup ("verify-status", FPRINT_TYPE_DEVICE); + signals[SIGNAL_ENROLL_STATUS] = + g_signal_lookup ("enroll-status", FPRINT_TYPE_DEVICE); + signals[SIGNAL_VERIFY_FINGER_SELECTED] = + g_signal_lookup ("verify-finger-selected", FPRINT_TYPE_DEVICE); + + quark_auth_user = g_quark_from_static_string ("authorized-user"); } -static void fprint_device_init(FprintDevice *device) +static void +_unwatch_name (gpointer id) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(device); - priv->id = ++last_id; - - /* Setup PolicyKit */ - priv->auth = polkit_authority_get_sync (NULL, NULL); - priv->clients = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - NULL); + g_bus_unwatch_name (GPOINTER_TO_INT (id)); } -G_DEFINE_TYPE(FprintDevice, fprint_device, G_TYPE_OBJECT); +static void +fprint_device_init (FprintDevice *device) +{ + FprintDevicePrivate *priv = fprint_device_get_instance_private (device); + + priv->id = ++last_id; -FprintDevice *fprint_device_new(FpDevice *dev) + /* Setup PolicyKit */ + priv->auth = polkit_authority_get_sync (NULL, NULL); + priv->clients = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + _unwatch_name); + + g_signal_connect (device, "g-authorize-method", + G_CALLBACK (action_authorization_handler), + NULL); +} + +FprintDevice * +fprint_device_new (FpDevice *dev) { - return g_object_new(FPRINT_TYPE_DEVICE, "dev", dev, NULL); + return g_object_new (FPRINT_TYPE_DEVICE, "dev", dev, NULL); } -guint32 _fprint_device_get_id(FprintDevice *rdev) +guint32 +_fprint_device_get_id (FprintDevice *rdev) { - return DEVICE_GET_PRIVATE(rdev)->id; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + return priv->id; } static const char * -finger_num_to_name (int finger_num) +fp_finger_to_name (FpFinger finger) { - if (finger_num == -1) - return "any"; - if (finger_num < FP_FINGER_UNKNOWN || finger_num > FP_FINGER_RIGHT_LITTLE) - return NULL; - return fingers[finger_num]; + if (finger == FP_FINGER_UNKNOWN) + return "any"; + if (!FP_FINGER_IS_VALID (finger)) + return NULL; + return FINGERS_NAMES[finger]; } -static int -finger_name_to_num (const char *finger_name) +static FpFinger +finger_name_to_fp_finger (const char *finger_name) { - guint i; + FpFinger i; - if (finger_name == NULL || *finger_name == '\0' || g_str_equal (finger_name, "any")) - return -1; + if (finger_name == NULL || *finger_name == '\0' || g_str_equal (finger_name, "any")) + return FP_FINGER_UNKNOWN; - for (i = 0; i < G_N_ELEMENTS (fingers); i++) { - if (g_str_equal (finger_name, fingers[i])) - return i; - } + for (i = FP_FINGER_FIRST; i <= FP_FINGER_LAST; i++) + if (g_str_equal (finger_name, FINGERS_NAMES[i])) + return i; - /* Invalid, let's try that */ - return -1; + /* Invalid, let's try that */ + return FP_FINGER_UNKNOWN; } static const char * verify_result_to_name (gboolean match, GError *error) { - if (!error) { - if (match) - return "verify-match"; - else - return "verify-no-match"; - } - - if (error->domain == FP_DEVICE_RETRY) { - switch (error->code) { - case FP_DEVICE_RETRY_TOO_SHORT: - return "verify-swipe-too-short"; - case FP_DEVICE_RETRY_CENTER_FINGER: - return "verify-finger-not-centered"; - case FP_DEVICE_RETRY_REMOVE_FINGER: - return "verify-remove-and-retry"; - default: - return "verify-retry-scan"; - } - } else { - /* Which errors should be mapped to disconnection? - * Are drivers/libfprint/fprintd really in agreement here? - */ - if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO)) - return "verify-disconnect"; - - return "verify-unknown-error"; - } + if (!error) + { + if (match) + return "verify-match"; + else + return "verify-no-match"; + } + + if (error->domain == FP_DEVICE_RETRY) + { + switch (error->code) + { + case FP_DEVICE_RETRY_TOO_SHORT: + return "verify-swipe-too-short"; + + case FP_DEVICE_RETRY_CENTER_FINGER: + return "verify-finger-not-centered"; + + case FP_DEVICE_RETRY_REMOVE_FINGER: + return "verify-remove-and-retry"; + + default: + return "verify-retry-scan"; + } + } + else + { + /* Which errors should be mapped to disconnection? + * Are drivers/libfprint/fprintd really in agreement here? + */ + if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO)) + return "verify-disconnected"; + else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return "verify-no-match"; + + return "verify-unknown-error"; + } } static const char * enroll_result_to_name (gboolean completed, gboolean enrolled, GError *error) { - if (!error) { - if (!completed) - return "enroll-stage-passed"; - else if (enrolled) - return "enroll-completed"; - else - return "enroll-failed"; - } - - if (error->domain == FP_DEVICE_RETRY) { - switch (error->code) { - case FP_DEVICE_RETRY_TOO_SHORT: - return "enroll-swipe-too-short"; - case FP_DEVICE_RETRY_CENTER_FINGER: - return "enroll-finger-not-centered"; - case FP_DEVICE_RETRY_REMOVE_FINGER: - return "verify-remove-and-retry"; - default: - return "enroll-remove-and-retry"; - } - } else { - /* Which errors should be mapped to disconnection? - * Are drivers/libfprint/fprintd really in agreement here? - */ - if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO)) - return "enroll-disconnected"; - else if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_FULL)) - return "enroll-data-full"; - - return "enroll-unknown-error"; - } + if (!error) + { + if (!completed) + return "enroll-stage-passed"; + else if (enrolled) + return "enroll-completed"; + else + return "enroll-failed"; + } + + if (error->domain == FP_DEVICE_RETRY) + { + switch (error->code) + { + case FP_DEVICE_RETRY_TOO_SHORT: + return "enroll-swipe-too-short"; + + case FP_DEVICE_RETRY_CENTER_FINGER: + return "enroll-finger-not-centered"; + + case FP_DEVICE_RETRY_REMOVE_FINGER: + return "enroll-remove-and-retry"; + + default: + return "enroll-retry-scan"; + } + } + else + { + /* Which errors should be mapped to disconnection? + * Are drivers/libfprint/fprintd really in agreement here? + */ + if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO)) + return "enroll-disconnected"; + else if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_FULL)) + return "enroll-data-full"; + else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return "enroll-failed"; + + return "enroll-unknown-error"; + } } -static void -set_disconnected (FprintDevicePrivate *priv, const char *res) +static FprintDevicePermission +get_permissions_for_invocation (GDBusMethodInvocation *invocation) { - if (g_str_equal (res, "enroll-disconnected") || - g_str_equal (res, "verify-disconnected")) - priv->disconnected = TRUE; + FprintDevicePermission required_perms; + const char *method_name; + + required_perms = FPRINT_DEVICE_PERMISSION_NONE; + method_name = g_dbus_method_invocation_get_method_name (invocation); + + if (g_str_equal (method_name, "Claim")) + { + required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY; + required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL; + } + else if (g_str_equal (method_name, "DeleteEnrolledFingers")) + { + required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL; + } + else if (g_str_equal (method_name, "DeleteEnrolledFingers2")) + { + required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL; + } + else if (g_str_equal (method_name, "EnrollStart")) + { + required_perms |= FPRINT_DEVICE_PERMISSION_ENROLL; + } + else if (g_str_equal (method_name, "ListEnrolledFingers")) + { + required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY; + } + else if (g_str_equal (method_name, "VerifyStart")) + { + required_perms |= FPRINT_DEVICE_PERMISSION_VERIFY; + } + else if (g_str_equal (method_name, "Release")) + { + } + else if (g_str_equal (method_name, "EnrollStop")) + { + } + else if (g_str_equal (method_name, "VerifyStop")) + { + /* Don't require permissiong for for release/stop operations. + * We are authenticated already if we could start, and we don't + * want to end up authorizing interactively again. + */ + } + else + { + g_assert_not_reached (); + } + + return required_perms; +} + +static FprintDeviceClaimState +get_claim_state_for_invocation (GDBusMethodInvocation *invocation) +{ + const char *method_name; + + method_name = g_dbus_method_invocation_get_method_name (invocation); + + if (g_str_equal (method_name, "Claim")) + return STATE_UNCLAIMED; + else if (g_str_equal (method_name, "DeleteEnrolledFingers")) + return STATE_AUTO_CLAIM; + else if (g_str_equal (method_name, "ListEnrolledFingers")) + return STATE_ANYTIME; + + return STATE_CLAIMED; } static gboolean -_fprint_device_check_claimed (FprintDevice *rdev, - DBusGMethodInvocation *context, - GError **error) +_fprint_device_check_claimed (FprintDevice *rdev, + GDBusMethodInvocation *invocation, + GError **error) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - char *sender; - gboolean retval; - - /* The device wasn't claimed, exit */ - if (priv->sender == NULL) { - g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_CLAIM_DEVICE, - _("Device was not claimed before use")); - return FALSE; - } - - sender = dbus_g_method_get_sender (context); - retval = g_str_equal (sender, priv->sender); - g_free (sender); - - if (retval == FALSE) { - g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, - _("Device already in use by another user")); - } - - return retval; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(SessionData) session = NULL; + FprintDeviceClaimState requested_state; + const char *sender; + + requested_state = get_claim_state_for_invocation (invocation); + + if (requested_state == STATE_ANYTIME) + return TRUE; + + session = session_data_get (priv); + if (requested_state == STATE_AUTO_CLAIM) + requested_state = session ? STATE_CLAIMED : STATE_UNCLAIMED; + + if (requested_state == STATE_UNCLAIMED) + { + /* Is it already claimed? */ + if (!session) + return TRUE; + + g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, + "Device was already claimed"); + return FALSE; + } + + g_assert (requested_state == STATE_CLAIMED); + + /* The device wasn't claimed, exit */ + if (session == NULL) + { + g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_CLAIM_DEVICE, + _("Device was not claimed before use")); + return FALSE; + } + + sender = g_dbus_method_invocation_get_sender (invocation); + + if (!g_str_equal (sender, session->sender) || session->invocation != NULL) + { + g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, + _("Device already in use by another user")); + return FALSE; + } + + return TRUE; } static gboolean -_fprint_device_check_polkit_for_action (FprintDevice *rdev, DBusGMethodInvocation *context, const char *action, GError **error) +_fprint_device_check_polkit_for_action (FprintDevice *rdev, + GDBusMethodInvocation *invocation, + const char *action, + GError **error) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - char *sender; - PolkitSubject *subject; - PolkitAuthorizationResult *result; - GError *_error = NULL; - - /* Check that caller is privileged */ - sender = dbus_g_method_get_sender (context); - subject = polkit_system_bus_name_new (sender); - g_free (sender); - - result = polkit_authority_check_authorization_sync (priv->auth, - subject, - action, - NULL, - POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, - NULL, &_error); - g_object_unref (subject); - - if (result == NULL) { - g_set_error (error, FPRINT_ERROR, - FPRINT_ERROR_PERMISSION_DENIED, - "Not Authorized: %s", _error->message); - g_error_free (_error); - return FALSE; - } - - if (!polkit_authorization_result_get_is_authorized (result)) { - g_set_error (error, FPRINT_ERROR, - FPRINT_ERROR_PERMISSION_DENIED, - "Not Authorized: %s", action); - g_object_unref (result); - return FALSE; - } - - g_object_unref (result); - - return TRUE; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + const char *sender; + + g_autoptr(GError) local_error = NULL; + g_autoptr(PolkitAuthorizationResult) result = NULL; + g_autoptr(PolkitSubject) subject = NULL; + + /* Check that caller is privileged */ + sender = g_dbus_method_invocation_get_sender (invocation); + subject = polkit_system_bus_name_new (sender); + + result = polkit_authority_check_authorization_sync (priv->auth, + subject, + action, + NULL, + POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, + NULL, &local_error); + if (result == NULL) + { + g_set_error (error, FPRINT_ERROR, + FPRINT_ERROR_PERMISSION_DENIED, + "Not Authorized: %s", local_error->message); + return FALSE; + } + + if (!polkit_authorization_result_get_is_authorized (result)) + { + g_set_error (error, FPRINT_ERROR, + FPRINT_ERROR_PERMISSION_DENIED, + "Not Authorized: %s", action); + return FALSE; + } + + return TRUE; } static gboolean -_fprint_device_check_polkit_for_actions (FprintDevice *rdev, - DBusGMethodInvocation *context, - const char *action1, - const char *action2, - GError **error) +fprint_device_check_polkit_for_permissions (FprintDevice *rdev, + GDBusMethodInvocation *invocation, + FprintDevicePermission permissions, + GError **error) { - if (_fprint_device_check_polkit_for_action (rdev, context, action1, error) != FALSE) - return TRUE; + g_autoptr(GFlagsClass) permission_flags = NULL; + unsigned i; - g_error_free (*error); - *error = NULL; + if (permissions == FPRINT_DEVICE_PERMISSION_NONE) + return TRUE; - return _fprint_device_check_polkit_for_action (rdev, context, action2, error); + permission_flags = g_type_class_ref (FPRINT_TYPE_DEVICE_PERMISSION); + + for (i = 0; i < permission_flags->n_values; ++i) + { + GFlagsValue *value = &permission_flags->values[i]; + const char *action; + + if (!(value->value & permissions)) + continue; + + action = value->value_nick; + g_debug ("Getting authorization to perform Polkit action %s", + action); + + g_clear_error (error); + if (_fprint_device_check_polkit_for_action (rdev, invocation, + action, error)) + return TRUE; + } + + g_assert (!error || *error); + return FALSE; } static char * -_fprint_device_check_for_username (FprintDevice *rdev, - DBusGMethodInvocation *context, - const char *username, - char **ret_sender, - GError **error) +_fprint_device_check_for_username (FprintDevice *rdev, + GDBusMethodInvocation *invocation, + const char *username, + GError **error) { - DBusConnection *conn; - DBusError dbus_error; - char *sender; - unsigned long uid; - struct passwd *user; - - /* Get details about the current sender, and username/uid */ - conn = dbus_g_connection_get_connection (fprintd_dbus_conn); - sender = dbus_g_method_get_sender (context); - dbus_error_init (&dbus_error); - uid = dbus_bus_get_unix_user (conn, sender, &dbus_error); - - if (dbus_error_is_set(&dbus_error)) { - g_free (sender); - dbus_set_g_error (error, &dbus_error); - return NULL; - } - - user = getpwuid (uid); - if (user == NULL) { - g_free (sender); - g_set_error(error, FPRINT_ERROR, FPRINT_ERROR_INTERNAL, - "Failed to get information about user UID %lu", uid); - return NULL; - } - - /* The current user is usually allowed to access their - * own data, this should be followed by PolicyKit checks - * anyway */ - if (username == NULL || *username == '\0' || g_str_equal (username, user->pw_name)) { - if (ret_sender != NULL) - *ret_sender = sender; - else - g_free (sender); - return g_strdup (user->pw_name); - } - - /* If we're not allowed to set a different username, - * then fail */ - if (_fprint_device_check_polkit_for_action (rdev, context, "net.reactivated.fprint.device.setusername", error) == FALSE) { - g_free (sender); - return NULL; - } - - if (ret_sender != NULL) - *ret_sender = sender; - else - g_free (sender); - - return g_strdup (username); + g_autoptr(GVariant) ret = NULL; + g_autoptr(GError) local_error = NULL; + GDBusConnection *connection; + const char *sender; + struct passwd *user; + guint32 uid; + + /* Get details about the current sender, and username/uid */ + connection = g_dbus_method_invocation_get_connection (invocation); + sender = g_dbus_method_invocation_get_sender (invocation); + + ret = g_dbus_connection_call_sync (connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixUser", + g_variant_new ("(s)", sender), + NULL, G_DBUS_CALL_FLAGS_NONE, -1, + NULL, &local_error); + + if (!ret) + { + g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_INTERNAL, + "Could not get conection unix user ID: %s", + local_error->message); + return NULL; + } + + g_variant_get (ret, "(u)", &uid); + user = getpwuid (uid); + if (user == NULL) + { + g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_INTERNAL, + "Failed to get information about user UID %u", uid); + return NULL; + } + + /* The current user is usually allowed to access their + * own data, this should be followed by PolicyKit checks + * anyway */ + if (username == NULL || *username == '\0' || g_str_equal (username, user->pw_name)) + return g_strdup (user->pw_name); + + /* If we're not allowed to set a different username, + * then fail */ + if (!fprint_device_check_polkit_for_permissions (rdev, invocation, + FPRINT_DEVICE_PERMISSION_SETUSERNAME, + error)) + return NULL; + + return g_strdup (username); } static void _fprint_device_client_vanished (GDBusConnection *connection, - const char *name, - FprintDevice *rdev) + const char *name, + FprintDevice *rdev) { - g_autoptr(GError) error = NULL; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - - /* Was that the client that claimed the device? */ - if (g_strcmp0 (priv->sender, name) == 0) { - while (priv->current_action != ACTION_NONE) { - g_cancellable_cancel (priv->current_cancellable); - - g_main_context_iteration (NULL, TRUE); - } - - if (!fp_device_close_sync (priv->dev, NULL, &error)) { - g_warning ("Error closing device after disconnect: %s", error->message); - } - - g_slice_free (struct session_data, priv->session); - priv->session = NULL; - g_clear_pointer (&priv->sender, g_free); - g_clear_pointer (&priv->username, g_free); - } - g_hash_table_remove (priv->clients, name); - - if (g_hash_table_size (priv->clients) == 0) { - g_object_notify (G_OBJECT (rdev), "in-use"); - } + g_autoptr(GError) error = NULL; + g_autoptr(SessionData) session = NULL; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + session = session_data_get (priv); + + /* Was that the client that claimed the device? */ + if (session != NULL && + g_strcmp0 (session->sender, name) == 0) + { + while (priv->current_action != ACTION_NONE) + { + /* OPEN/CLOSE are not cancellable, we just need to wait */ + if (priv->current_cancellable) + g_cancellable_cancel (priv->current_cancellable); + + g_main_context_iteration (NULL, TRUE); + } + + /* The session may have disappeared at this point if the device + * was already closing. */ + g_clear_pointer (&session, session_data_unref); + session = session_data_get (priv); + if (session && !fp_device_close_sync (priv->dev, NULL, &error)) + g_critical ("Error closing device after disconnect: %s", error->message); + + session_data_set_new (priv, NULL, NULL); + } + g_hash_table_remove (priv->clients, name); + + if (g_hash_table_size (priv->clients) == 0) + g_object_notify (G_OBJECT (rdev), "in-use"); } static void _fprint_device_add_client (FprintDevice *rdev, const char *sender) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - guint id; - - id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->clients, sender)); - if (id == 0) { - id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, - sender, - G_BUS_NAME_WATCHER_FLAGS_NONE, - NULL, - (GBusNameVanishedCallback) _fprint_device_client_vanished, - rdev, - NULL); - g_hash_table_insert (priv->clients, g_strdup (sender), GUINT_TO_POINTER(id)); - g_object_notify (G_OBJECT (rdev), "in-use"); - } + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + guint id; + + id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->clients, sender)); + if (id == 0) + { + id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, + sender, + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, + (GBusNameVanishedCallback) _fprint_device_client_vanished, + rdev, + NULL); + g_hash_table_insert (priv->clients, g_strdup (sender), GUINT_TO_POINTER (id)); + g_object_notify (G_OBJECT (rdev), "in-use"); + } +} + +static void +dev_open_cb (FpDevice *dev, GAsyncResult *res, void *user_data) +{ + g_autoptr(GError) error = NULL; + FprintDevice *rdev = user_data; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(SessionData) session = NULL; + g_autoptr(GDBusMethodInvocation) invocation = NULL; + + session = session_data_get (priv); + invocation = g_steal_pointer (&session->invocation); + + priv->current_action = ACTION_NONE; + if (!fp_device_open_finish (dev, res, &error)) + { + g_autoptr(GError) dbus_error = NULL; + + dbus_error = g_error_new (FPRINT_ERROR, + FPRINT_ERROR_INTERNAL, + "Open failed with error: %s", error->message); + g_dbus_method_invocation_return_gerror (invocation, dbus_error); + session_data_set_new (priv, NULL, NULL); + return; + } + + g_debug ("claimed device %d", priv->id); + + fprint_dbus_device_complete_claim (FPRINT_DBUS_DEVICE (rdev), + invocation); +} + +static gboolean +fprintd_device_authorize_user (FprintDevice *rdev, + GDBusMethodInvocation *invocation, + GError **error) +{ + GVariant *params = NULL; + const char *username = NULL; + g_autofree char *user = NULL; + + params = g_dbus_method_invocation_get_parameters (invocation); + g_assert (g_variant_n_children (params) == 1); + g_variant_get (params, "(&s)", &username); + g_assert (username); + + user = _fprint_device_check_for_username (rdev, + invocation, + username, + error); + if (user == NULL) + return FALSE; + + /* We keep the user attached to the invocation as it may not be the same + * of the requested one, in case an empty one was passed. + * Given that now we may have multiple cuncurrent requests, it wouldn't + * be safe to add another member to the priv, as it would need even more + * multi-thread checks around, and over-complicate things. + */ + g_object_set_qdata_full (G_OBJECT (invocation), quark_auth_user, + g_steal_pointer (&user), g_free); + + return TRUE; +} + +static gboolean +fprint_device_claim (FprintDBusDevice *dbus_dev, + GDBusMethodInvocation *invocation, + const char *username) +{ + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(SessionData) session = NULL; + g_autoptr(GError) error = NULL; + char *sender, *user; + + if (!_fprint_device_check_claimed (rdev, invocation, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + user = g_object_steal_qdata (G_OBJECT (invocation), quark_auth_user); + g_assert (user); + g_assert (g_str_equal (username, "") || g_str_equal (user, username)); + + sender = g_strdup (g_dbus_method_invocation_get_sender (invocation)); + _fprint_device_add_client (rdev, sender); + + session = session_data_set_new (priv, g_steal_pointer (&sender), g_steal_pointer (&user)); + session->invocation = g_object_ref (invocation); + + g_debug ("user '%s' claiming the device: %d", session->username, priv->id); + + priv->current_action = ACTION_OPEN; + fp_device_open (priv->dev, NULL, (GAsyncReadyCallback) dev_open_cb, rdev); + + return TRUE; } -static void dev_open_cb(FpDevice *dev, GAsyncResult *res, void *user_data) +static void +dev_close_cb (FpDevice *dev, GAsyncResult *res, void *user_data) { - g_autoptr(GError) error = NULL; - FprintDevice *rdev = user_data; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - struct session_data *session = priv->session; + g_autoptr(GError) error = NULL; + FprintDevice *rdev = user_data; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); - if (!fp_device_open_finish (dev, res, &error)) { - g_autoptr(GError) dbus_error = NULL; + g_autoptr(SessionData) session = NULL; + g_autoptr(GDBusMethodInvocation) invocation = NULL; - g_clear_pointer (&priv->sender, g_free); + session = session_data_get (priv); + session_data_set_new (priv, NULL, NULL); + invocation = g_steal_pointer (&session->invocation); - dbus_error = g_error_new (FPRINT_ERROR, - FPRINT_ERROR_INTERNAL, - "Open failed with error: %s", error->message); - dbus_g_method_return_error(session->context_claim_device, error); - return; - } + priv->current_action = ACTION_NONE; + if (!fp_device_close_finish (dev, res, &error)) + { + g_autoptr(GError) dbus_error = NULL; - g_debug("device %d claimed", priv->id); + dbus_error = g_error_new (FPRINT_ERROR, + FPRINT_ERROR_INTERNAL, + "Release failed with error: %s", error->message); + g_dbus_method_invocation_return_gerror (invocation, dbus_error); + return; + } - dbus_g_method_return(session->context_claim_device); + g_debug ("released device %d", priv->id); + + fprint_dbus_device_complete_release (FPRINT_DBUS_DEVICE (rdev), + invocation); } -static void fprint_device_claim(FprintDevice *rdev, - const char *username, - DBusGMethodInvocation *context) +static gboolean +fprint_device_release (FprintDBusDevice *dbus_dev, + GDBusMethodInvocation *invocation) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - GError *error = NULL; - char *sender, *user; - - /* Is it already claimed? */ - if (priv->sender != NULL) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, - "Device was already claimed"); - dbus_g_method_return_error(context, error); - g_error_free(error); - return; - } - - g_assert (priv->username == NULL); - g_assert (priv->sender == NULL); - - sender = NULL; - user = _fprint_device_check_for_username (rdev, - context, - username, - &sender, - &error); - if (user == NULL) { - g_free (sender); - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - if (_fprint_device_check_polkit_for_actions (rdev, context, - "net.reactivated.fprint.device.verify", - "net.reactivated.fprint.device.enroll", - &error) == FALSE) { - g_free (sender); - g_free (user); - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - _fprint_device_add_client (rdev, sender); - - priv->username = user; - priv->sender = sender; - - g_debug ("user '%s' claiming the device: %d", priv->username, priv->id); - - priv->session = g_slice_new0(struct session_data); - priv->session->context_claim_device = context; - - fp_device_open (priv->dev, NULL, (GAsyncReadyCallback) dev_open_cb, rdev); + g_autoptr(GError) error = NULL; + g_autoptr(SessionData) session = NULL; + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + if (!_fprint_device_check_claimed (rdev, invocation, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + if (priv->current_cancellable) + { + if (priv->current_action == ACTION_ENROLL) + g_warning ("Enrollment was in progress, stopping it"); + else if (priv->current_action == ACTION_IDENTIFY || + priv->current_action == ACTION_VERIFY) + g_warning ("Verification was in progress, stopping it"); + else if (priv->current_action == ACTION_DELETE) + g_warning ("Deletion was in progress, stopping it"); + + g_cancellable_cancel (priv->current_cancellable); + while (priv->current_action != ACTION_NONE) + g_main_context_iteration (NULL, TRUE); + } + + session = session_data_get (priv); + session->invocation = g_object_ref (invocation); + + priv->current_action = ACTION_CLOSE; + fp_device_close (priv->dev, NULL, (GAsyncReadyCallback) dev_close_cb, rdev); + + return TRUE; } -static void dev_close_cb(FpDevice *dev, GAsyncResult *res, void *user_data) +static void +report_verify_status (FprintDevice *rdev, + gboolean match, + GError *error) { - g_autoptr(GError) error = NULL; - FprintDevice *rdev = user_data; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - struct session_data *session = priv->session; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + const char *result = verify_result_to_name (match, error); + + g_autoptr(SessionData) session = NULL; + gboolean done; - if (!fp_device_close_finish (dev, res, &error)) { - g_autoptr(GError) dbus_error = NULL; + done = (error == NULL || error->domain != FP_DEVICE_RETRY); - g_clear_pointer (&priv->sender, g_free); + session = session_data_get (priv); - dbus_error = g_error_new (FPRINT_ERROR, - FPRINT_ERROR_INTERNAL, - "Release failed with error: %s", error->message); - dbus_g_method_return_error(session->context_release_device, error); - return; - } + if (done && session->verify_status_reported) + { + /* It is completely fine for cancellation to occur after a + * result has been reported. */ + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Verify status already reported. Ignoring %s", result); + return; + } - g_debug("device %d released", priv->id); + g_debug ("report_verify_status: result %s", result); + g_signal_emit (rdev, signals[SIGNAL_VERIFY_STATUS], 0, result, done); - dbus_g_method_return(session->context_release_device); + if (done) + session->verify_status_reported = TRUE; +} - g_slice_free (struct session_data, priv->session); - priv->session = NULL; - g_clear_pointer (&priv->sender, g_free); - g_clear_pointer (&priv->username, g_free); +static gboolean +can_start_action (FprintDevice *rdev, GError **error) +{ + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + switch (priv->current_action) + { + case ACTION_NONE: + return TRUE; + + case ACTION_ENROLL: + g_set_error (error, + FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, + "Enrollment already in progress"); + break; + + case ACTION_IDENTIFY: + case ACTION_VERIFY: + g_set_error (error, + FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, + "Enrollment already in progress"); + break; + + case ACTION_OPEN: + g_set_error (error, + FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, + "Claim already in progress"); + break; + + case ACTION_CLOSE: + g_set_error (error, + FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, + "Release already in progress"); + break; + + case ACTION_DELETE: + g_set_error (error, + FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, + "Delete already in progress"); + break; + + default: /* Fallback only. */ + g_assert_not_reached (); + g_set_error (error, + FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, + "Another operation is already in progress"); + } + + return FALSE; } -static void fprint_device_release(FprintDevice *rdev, - DBusGMethodInvocation *context) +static void +match_cb (FpDevice *device, + FpPrint *match, + FpPrint *print, + gpointer user_data, + GError *error) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - struct session_data *session = priv->session; - GError *error = NULL; - - if (_fprint_device_check_claimed(rdev, context, &error) == FALSE) { - dbus_g_method_return_error (context, error); - g_error_free(error); - return; - } - - /* People that can claim can also release */ - if (_fprint_device_check_polkit_for_actions (rdev, context, - "net.reactivated.fprint.device.verify", - "net.reactivated.fprint.device.enroll", - &error) == FALSE) { - dbus_g_method_return_error (context, error); - g_error_free(error); - return; - } - - session->context_release_device = context; - fp_device_close (priv->dev, NULL, (GAsyncReadyCallback) dev_close_cb, rdev); + FprintDevice *rdev = user_data; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + gboolean matched; + gboolean cancelled; + + g_assert_true (error == NULL || error->domain == FP_DEVICE_RETRY); + + cancelled = g_cancellable_is_cancelled (priv->current_cancellable); + matched = match != NULL && cancelled == FALSE; + + /* No-match is reported only after the operation completes. + * This avoids problems when the operation is immediately restarted. */ + report_verify_status (rdev, matched, error); } -static void verify_cb(FpDevice *dev, GAsyncResult *res, void *user_data) +static void +verify_cb (FpDevice *dev, GAsyncResult *res, void *user_data) { - g_autoptr(GError) error = NULL; - struct FprintDevice *rdev = user_data; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - gboolean success; - const char *name; - gboolean match; - - success = fp_device_verify_finish (dev, res, &match, NULL, &error); - g_assert (!!success == !error); - name = verify_result_to_name (match, error); - - g_debug("verify_cb: result %s", name); - - set_disconnected (priv, name); - - /* Automatically restart the operation for retry failures */ - if (error && error->domain == FP_DEVICE_RETRY) { - g_signal_emit(rdev, signals[SIGNAL_VERIFY_STATUS], 0, name, FALSE); - - fp_device_verify (priv->dev, - priv->verify_data, - priv->current_cancellable, - (GAsyncReadyCallback) verify_cb, - rdev); - } else { - g_clear_object (&priv->verify_data); - g_signal_emit(rdev, signals[SIGNAL_VERIFY_STATUS], 0, name, TRUE); - - /* Return the cancellation or reset action right away if vanished. */ - if (priv->current_cancel_context) { - dbus_g_method_return(priv->current_cancel_context); - priv->current_cancel_context = NULL; - priv->current_action = ACTION_NONE; - } else if (g_cancellable_is_cancelled (priv->current_cancellable)) { - priv->current_action = ACTION_NONE; - } - - g_clear_object (&priv->current_cancellable); - } + g_autoptr(GError) error = NULL; + g_autoptr(SessionData) session = NULL; + FprintDevice *rdev = user_data; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (rdev); + gboolean success; + const char *name; + gboolean match; + + success = fp_device_verify_finish (dev, res, &match, NULL, &error); + g_assert (!!success == !error); + name = verify_result_to_name (match, error); + + session = session_data_get (priv); + + g_debug ("verify_cb: result %s", name); + + /* Automatically restart the operation for retry failures */ + if (error && error->domain == FP_DEVICE_RETRY) + { + fp_device_verify (priv->dev, + priv->verify_data, + priv->current_cancellable, + match_cb, rdev, NULL, + (GAsyncReadyCallback) verify_cb, + rdev); + } + else + { + g_clear_object (&priv->verify_data); + + if (error) + { + report_verify_status (rdev, FALSE, error); + + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Device reported an error during verify: %s", + error->message); + } + + /* Return the cancellation or reset action right away if vanished. */ + if (priv->current_cancel_invocation) + { + fprint_dbus_device_complete_verify_stop (dbus_dev, + g_steal_pointer (&priv->current_cancel_invocation)); + priv->current_action = ACTION_NONE; + session->verify_status_reported = FALSE; + } + else if (g_cancellable_is_cancelled (priv->current_cancellable)) + { + priv->current_action = ACTION_NONE; + session->verify_status_reported = FALSE; + } + + g_clear_object (&priv->current_cancellable); + } } -static void identify_cb(FpDevice *dev, GAsyncResult *res, void *user_data) +static void +identify_cb (FpDevice *dev, GAsyncResult *res, void *user_data) { - g_autoptr(GError) error = NULL; - struct FprintDevice *rdev = user_data; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - const char *name; - gboolean success; - FpPrint *match; - - success = fp_device_identify_finish (dev, res, &match, NULL, &error); - g_assert (!!success == !error); - name = verify_result_to_name (match != NULL, error); - - g_debug("verify_cb: result %s", name); - - set_disconnected (priv, name); - - /* Automatically restart the operation for retry failures */ - if (error && error->domain == FP_DEVICE_RETRY) { - g_signal_emit (rdev, signals[SIGNAL_VERIFY_STATUS], 0, name, FALSE); - - fp_device_identify (priv->dev, - priv->identify_data, - priv->current_cancellable, - (GAsyncReadyCallback) identify_cb, - rdev); - } else { - g_clear_pointer (&priv->identify_data, g_ptr_array_unref); - g_signal_emit (rdev, signals[SIGNAL_VERIFY_STATUS], 0, name, TRUE); - - /* Return the cancellation or reset action right away if vanished. */ - if (priv->current_cancel_context) { - dbus_g_method_return(priv->current_cancel_context); - priv->current_cancel_context = NULL; - priv->current_action = ACTION_NONE; - } else if (g_cancellable_is_cancelled (priv->current_cancellable)) { - priv->current_action = ACTION_NONE; - } - - g_clear_object (&priv->current_cancellable); - } + g_autoptr(GError) error = NULL; + g_autoptr(FpPrint) match = NULL; + FprintDevice *rdev = user_data; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (rdev); + const char *name; + gboolean success; + + success = fp_device_identify_finish (dev, res, &match, NULL, &error); + g_assert (!!success == !error); + name = verify_result_to_name (match != NULL, error); + + g_debug ("identify_cb: result %s", name); + + /* Automatically restart the operation for retry failures */ + if (error && error->domain == FP_DEVICE_RETRY) + { + fp_device_identify (priv->dev, + priv->identify_data, + priv->current_cancellable, + match_cb, rdev, NULL, + (GAsyncReadyCallback) identify_cb, + rdev); + } + else + { + g_clear_pointer (&priv->identify_data, g_ptr_array_unref); + + if (error) + { + report_verify_status (rdev, FALSE, error); + + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Device reported an error during identify: %s", + error->message); + } + + /* Return the cancellation or reset action right away if vanished. */ + if (priv->current_cancel_invocation) + { + fprint_dbus_device_complete_verify_stop (dbus_dev, + g_steal_pointer (&priv->current_cancel_invocation)); + priv->current_action = ACTION_NONE; + } + else if (g_cancellable_is_cancelled (priv->current_cancellable)) + { + g_autoptr(SessionData) session = NULL; + session = session_data_get (priv); + priv->current_action = ACTION_NONE; + session->verify_status_reported = FALSE; + } + + g_clear_object (&priv->current_cancellable); + } } -static void fprint_device_verify_start(FprintDevice *rdev, - const char *finger_name, DBusGMethodInvocation *context) +static gboolean +fprint_device_verify_start (FprintDBusDevice *dbus_dev, + GDBusMethodInvocation *invocation, + const char *finger_name) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - g_autoptr(GPtrArray) gallery = NULL; - g_autoptr(FpPrint) print = NULL; - g_autoptr(GError) error = NULL; - guint finger_num = finger_name_to_num (finger_name); - - if (_fprint_device_check_claimed(rdev, context, &error) == FALSE) { - dbus_g_method_return_error (context, error); - return; - } - - if (_fprint_device_check_polkit_for_action (rdev, context, "net.reactivated.fprint.device.verify", &error) == FALSE) { - dbus_g_method_return_error (context, error); - return; - } - - if (priv->current_action != ACTION_NONE) { - if (priv->current_action == ACTION_ENROLL) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, - "Enrollment in progress"); - } else { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, - "Verification already in progress"); - } - dbus_g_method_return_error(context, error); - return; - } - - if (finger_num == -1) { - GSList *prints; - - prints = store.discover_prints(priv->dev, priv->username); - if (prints == NULL) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS, - "No fingerprints enrolled"); - dbus_g_method_return_error(context, error); - return; - } - if (fp_device_supports_identify (priv->dev)) { - GSList *l; - - gallery = g_ptr_array_new_with_free_func (g_object_unref); - - for (l = prints; l != NULL; l = l->next) { - g_debug ("adding finger %d to the gallery", GPOINTER_TO_INT (l->data)); - store.print_data_load(priv->dev, GPOINTER_TO_INT (l->data), - priv->username, &print); - - if (print) - g_ptr_array_add (gallery, g_steal_pointer (&print)); - } - } else { - finger_num = GPOINTER_TO_INT (prints->data); - } - g_slist_free(prints); - } - - if (fp_device_supports_identify (priv->dev) && finger_num == -1) { - if (gallery->len == 0) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS, - "No fingerprints on that device"); - dbus_g_method_return_error(context, error); - return; - } - priv->current_action = ACTION_IDENTIFY; - - g_debug ("start identification device %d", priv->id); - priv->current_cancellable = g_cancellable_new (); - priv->identify_data = g_ptr_array_ref (gallery); - fp_device_identify (priv->dev, gallery, priv->current_cancellable, (GAsyncReadyCallback) identify_cb, rdev); - } else { - priv->current_action = ACTION_VERIFY; - - g_debug("start verification device %d finger %d", priv->id, finger_num); - - store.print_data_load(priv->dev, finger_num, - priv->username, &print); - - if (!print) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_INTERNAL, - "No such print %d", finger_num); - dbus_g_method_return_error(context, error); - return; - } - - priv->current_cancellable = g_cancellable_new (); - priv->verify_data = g_object_ref (print); - fp_device_verify (priv->dev, print, priv->current_cancellable, (GAsyncReadyCallback) verify_cb, rdev); - } - - /* Emit VerifyFingerSelected telling the front-end which finger - * we selected for auth */ - g_signal_emit(rdev, signals[SIGNAL_VERIFY_FINGER_SELECTED], - 0, finger_num_to_name (finger_num)); - - dbus_g_method_return(context); + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(GPtrArray) gallery = NULL; + g_autoptr(FpPrint) print = NULL; + g_autoptr(SessionData) session = NULL; + g_autoptr(GError) error = NULL; + FpFinger finger = finger_name_to_fp_finger (finger_name); + + if (!_fprint_device_check_claimed (rdev, invocation, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + session = session_data_get (priv); + + if (!can_start_action (rdev, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + if (finger == FP_FINGER_UNKNOWN) + { + g_autoptr(GSList) prints = NULL; + + prints = store.discover_prints (priv->dev, session->username); + if (prints == NULL) + { + g_set_error (&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS, + "No fingerprints enrolled"); + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + if (fp_device_supports_identify (priv->dev)) + { + GSList *l; + + gallery = g_ptr_array_new_with_free_func (g_object_unref); + + for (l = prints; l != NULL; l = l->next) + { + g_debug ("adding finger %u to the gallery", GPOINTER_TO_UINT (l->data)); + store.print_data_load (priv->dev, GPOINTER_TO_UINT (l->data), + session->username, &print); + + if (print) + g_ptr_array_add (gallery, g_steal_pointer (&print)); + } + } + else + { + finger = GPOINTER_TO_UINT (prints->data); + } + } + + if (fp_device_supports_identify (priv->dev) && finger == FP_FINGER_UNKNOWN) + { + if (gallery->len == 0) + { + g_set_error (&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS, + "No fingerprints on that device"); + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + priv->current_action = ACTION_IDENTIFY; + + g_debug ("start identification device %d", priv->id); + priv->current_cancellable = g_cancellable_new (); + priv->identify_data = g_ptr_array_ref (gallery); + fp_device_identify (priv->dev, gallery, priv->current_cancellable, + match_cb, rdev, NULL, + (GAsyncReadyCallback) identify_cb, rdev); + } + else + { + priv->current_action = ACTION_VERIFY; + + g_debug ("start verification device %d finger %d", priv->id, finger); + + store.print_data_load (priv->dev, finger, + session->username, &print); + + if (!print) + { + g_set_error (&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS, + "No such print %d", finger); + g_dbus_method_invocation_return_gerror (invocation, + error); + return TRUE; + } + + priv->current_cancellable = g_cancellable_new (); + priv->verify_data = g_object_ref (print); + fp_device_verify (priv->dev, print, priv->current_cancellable, + match_cb, rdev, NULL, + (GAsyncReadyCallback) verify_cb, rdev); + } + + fprint_dbus_device_complete_verify_start (dbus_dev, invocation); + + /* Emit VerifyFingerSelected telling the front-end which finger + * we selected for auth */ + g_signal_emit (rdev, signals[SIGNAL_VERIFY_FINGER_SELECTED], + 0, fp_finger_to_name (finger)); + + return TRUE; } -static void fprint_device_verify_stop(FprintDevice *rdev, - DBusGMethodInvocation *context) +static gboolean +fprint_device_verify_stop (FprintDBusDevice *dbus_dev, + GDBusMethodInvocation *invocation) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - GError *error = NULL; - - if (_fprint_device_check_claimed(rdev, context, &error) == FALSE) { - dbus_g_method_return_error (context, error); - g_error_free(error); - return; - } - - if (_fprint_device_check_polkit_for_action (rdev, context, "net.reactivated.fprint.device.verify", &error) == FALSE) { - dbus_g_method_return_error (context, error); - g_error_free(error); - return; - } - - if (priv->current_action == ACTION_NONE) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_NO_ACTION_IN_PROGRESS, - "No verification in progress"); - dbus_g_method_return_error(context, error); - g_error_free (error); - return; - } - - if (priv->current_cancellable) { - /* We return only when the action was cancelled */ - g_cancellable_cancel (priv->current_cancellable); - priv->current_cancel_context = context; - } else { - dbus_g_method_return (context); - priv->current_action = ACTION_NONE; - } + g_autoptr(SessionData) session = NULL; + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(GError) error = NULL; + + if (!_fprint_device_check_claimed (rdev, invocation, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + switch (priv->current_action) + { + case ACTION_VERIFY: + case ACTION_IDENTIFY: + break; + + case ACTION_NONE: + g_dbus_method_invocation_return_error_literal ( + invocation, FPRINT_ERROR, + FPRINT_ERROR_NO_ACTION_IN_PROGRESS, + "No verification in progress"); + return TRUE; + + default: + g_dbus_method_invocation_return_error_literal ( + invocation, FPRINT_ERROR, + FPRINT_ERROR_ALREADY_IN_USE, + "Another operation is already in progress"); + return TRUE; + } + + if (priv->current_cancellable) + { + /* We return only when the action was cancelled */ + g_cancellable_cancel (priv->current_cancellable); + priv->current_cancel_invocation = invocation; + } + else + { + fprint_dbus_device_complete_verify_stop (dbus_dev, invocation); + priv->current_action = ACTION_NONE; + + session = session_data_get (priv); + session->verify_status_reported = FALSE; + } + + return TRUE; } -static void enroll_progress_cb(FpDevice *dev, - gint completed_stages, - FpPrint *print, - gpointer user_data, - GError *error) +static void +enroll_progress_cb (FpDevice *dev, + gint completed_stages, + FpPrint *print, + gpointer user_data, + GError *error) { - struct FprintDevice *rdev = user_data; - const char *name = enroll_result_to_name (FALSE, FALSE, error); + FprintDevice *rdev = user_data; + const char *name = enroll_result_to_name (FALSE, FALSE, error); - g_debug("enroll_stage_cb: result %s", name); + g_debug ("enroll_stage_cb: result %s", name); - if (completed_stages < fp_device_get_nr_enroll_stages (dev)) - g_signal_emit(rdev, signals[SIGNAL_ENROLL_STATUS], 0, name, FALSE); + if (completed_stages < fp_device_get_nr_enroll_stages (dev)) + g_signal_emit (rdev, signals[SIGNAL_ENROLL_STATUS], 0, name, FALSE); } -static gboolean try_delete_print(FprintDevice *rdev) +static gboolean +try_delete_print (FprintDevice *rdev) { - g_autoptr(GError) error = NULL; - g_autoptr(GPtrArray) device_prints = NULL; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - GSList *users, *user; - - device_prints = fp_device_list_prints_sync (priv->dev, NULL, &error); - if (!device_prints) { - g_warning ("Failed to query prints: %s", error->message); - return FALSE; - } - - g_debug ("Device has %d prints stored", device_prints->len); - - users = store.discover_users(); - - for (user = users; user; user = user->next) { - const gchar *username = user->data; - GSList *fingers, *finger; - - fingers = store.discover_prints (priv->dev, username); - - for (finger = fingers; finger; finger = finger->next) { - g_autoptr(FpPrint) print = NULL; - - store.print_data_load (priv->dev, - GPOINTER_TO_INT (fingers->data), - username, - &print); - - if (print) { - /* Test against all device prints */ - for (gint i = 0; i < device_prints->len; i++) { - if (!fp_print_equal (print, - FP_PRINT (g_ptr_array_index (device_prints, i)))) - continue; - - /* Found an equal print, remove it and break out */ - g_ptr_array_remove_index (device_prints, i); - break; - } - } - } - - g_slist_free (fingers); - } - - g_slist_free_full (users, g_free); - - g_debug ("Device has %d prints stored that we do not need", device_prints->len); - if (device_prints->len == 0) - return FALSE; - - /* Just delete the first print in the list at this point. - * We could be smarter and fetch some more metadata. */ - fp_device_delete_print_sync (priv->dev, - g_ptr_array_index (device_prints, 0), - NULL, - &error); - - if (error) { - g_warning ("Failed to garbage collect a print"); - return FALSE; - } - - return TRUE; + g_autoptr(GError) error = NULL; + g_autoptr(GPtrArray) device_prints = NULL; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + GSList *users, *user; + + device_prints = fp_device_list_prints_sync (priv->dev, NULL, &error); + if (!device_prints) + { + g_warning ("Failed to query prints: %s", error->message); + return FALSE; + } + + g_debug ("Device has %d prints stored", device_prints->len); + + users = store.discover_users (); + + for (user = users; user; user = user->next) + { + const char *username = user->data; + g_autoptr(GSList) fingers = NULL; + GSList *finger; + + fingers = store.discover_prints (priv->dev, username); + + for (finger = fingers; finger; finger = finger->next) + { + g_autoptr(FpPrint) print = NULL; + guint index; + + store.print_data_load (priv->dev, + GPOINTER_TO_UINT (fingers->data), + username, + &print); + + if (!print) + continue; + + if (!g_ptr_array_find_with_equal_func (device_prints, + print, + (GEqualFunc) fp_print_equal, + &index)) + continue; + + /* Found an equal print, remove it */ + g_ptr_array_remove_index (device_prints, index); + } + } + + g_slist_free_full (users, g_free); + + g_debug ("Device has %d prints stored that we do not need", device_prints->len); + if (device_prints->len == 0) + return FALSE; + + /* Just delete the first print in the list at this point. + * We could be smarter and fetch some more metadata. */ + fp_device_delete_print_sync (priv->dev, + g_ptr_array_index (device_prints, 0), + NULL, + &error); + + if (error) + { + g_warning ("Failed to garbage collect a print: %s", error->message); + return FALSE; + } + + return TRUE; +} + +#if !GLIB_CHECK_VERSION (2, 63, 3) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GDate, g_date_free); +#endif + +static FpPrint * +fprint_device_create_enroll_template (FprintDevice *rdev, FpFinger finger) +{ + g_autoptr(SessionData) session = NULL; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(GDateTime) datetime = NULL; + g_autoptr(GDate) date = NULL; + FpPrint *template = NULL; + gint year, month, day; + + session = session_data_get (priv); + + template = fp_print_new (priv->dev); + fp_print_set_finger (template, finger); + fp_print_set_username (template, session->username); + datetime = g_date_time_new_now_local (); + g_date_time_get_ymd (datetime, &year, &month, &day); + date = g_date_new_dmy (day, month, year); + fp_print_set_enroll_date (template, date); + + return template; } -static FpPrint* -fprint_device_create_enroll_template(FprintDevice *rdev, gint finger_num) +static void +enroll_cb (FpDevice *dev, GAsyncResult *res, void *user_data) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - FpPrint *template = NULL; - GDateTime *datetime = NULL; - GDate *date = NULL; - gint year, month, day; - - template = fp_print_new (priv->dev); - fp_print_set_finger (template, finger_num); - fp_print_set_username (template, priv->username); - datetime = g_date_time_new_now_local (); - g_date_time_get_ymd (datetime, &year, &month, &day); - date = g_date_new_dmy (day, month, year); - fp_print_set_enroll_date (template, date); - g_date_free (date); - g_date_time_unref (datetime); - - return template; + g_autoptr(GError) error = NULL; + FprintDevice *rdev = user_data; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (rdev); + + g_autoptr(FpPrint) print = NULL; + const char *name; + + print = fp_device_enroll_finish (dev, res, &error); + + /* We need to special case the issue where the on device storage + * is completely full. In that case, we check whether we can delete + * a print that is not coming from us; assuming it is from an old + * installation. + * We do this synchronously, which is not great but should be good + * enough. */ + if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_FULL)) + { + g_debug ("Device storage is full, trying to garbage collect old prints"); + if (try_delete_print (rdev)) + { + /* Success? Then restart the operation */ + fp_device_enroll (priv->dev, + fprint_device_create_enroll_template (rdev, priv->enroll_data), + priv->current_cancellable, + enroll_progress_cb, + rdev, + NULL, + (GAsyncReadyCallback) enroll_cb, + rdev); + return; + } + } + + name = enroll_result_to_name (TRUE, print != NULL, error); + + g_debug ("enroll_cb: result %s", name); + + if (print) + { + int r; + r = store.print_data_save (print); + if (r < 0) + name = "enroll-failed"; + } + + g_signal_emit (rdev, signals[SIGNAL_ENROLL_STATUS], 0, name, TRUE); + + if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Device reported an error during enroll: %s", error->message); + + /* Return the cancellation or reset action right away if vanished. */ + if (priv->current_cancel_invocation) + { + fprint_dbus_device_complete_enroll_stop (dbus_dev, + g_steal_pointer (&priv->current_cancel_invocation)); + priv->current_action = ACTION_NONE; + } + else if (g_cancellable_is_cancelled (priv->current_cancellable)) + { + priv->current_action = ACTION_NONE; + } + g_clear_object (&priv->current_cancellable); } -static void enroll_cb(FpDevice *dev, GAsyncResult *res, void *user_data) + +static gboolean +fprint_device_enroll_start (FprintDBusDevice *dbus_dev, + GDBusMethodInvocation *invocation, + const char *finger_name) { - g_autoptr(GError) error = NULL; - struct FprintDevice *rdev = user_data; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - g_autoptr(FpPrint) print = NULL; - const char *name; - - print = fp_device_enroll_finish (dev, res, &error); - - /* We need to special case the issue where the on device storage - * is completely full. In that case, we check whether we can delete - * a print that is not coming from us; assuming it is from an old - * installation. - * We do this synchronously, which is not great but should be good - * enough. */ - if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_FULL)) { - g_print ("Device storage is full, trying to garbage collect old prints"); - if (try_delete_print (rdev)) { - /* Success? Then restart the operation */ - fp_device_enroll (priv->dev, - fprint_device_create_enroll_template (rdev, priv->enroll_data), - priv->current_cancellable, - enroll_progress_cb, - rdev, - NULL, - (GAsyncReadyCallback) enroll_cb, - rdev); - return; - } - } - - name = enroll_result_to_name (TRUE, print != NULL, error); - - g_debug("enroll_cb: result %s", name); - - if (print) { - gint r; - r = store.print_data_save(print); - if (r < 0) - name = "enroll-failed"; - } - - set_disconnected (priv, name); - - g_signal_emit(rdev, signals[SIGNAL_ENROLL_STATUS], 0, name, TRUE); - - /* Return the cancellation or reset action right away if vanished. */ - if (priv->current_cancel_context) { - dbus_g_method_return(priv->current_cancel_context); - priv->current_cancel_context = NULL; - priv->current_action = ACTION_NONE; - } else if (g_cancellable_is_cancelled (priv->current_cancellable)) { - priv->current_action = ACTION_NONE; - } - g_clear_object (&priv->current_cancellable); + g_autoptr(GError) error = NULL; + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + FpFinger finger = finger_name_to_fp_finger (finger_name); + + if (!_fprint_device_check_claimed (rdev, invocation, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + if (finger == FP_FINGER_UNKNOWN) + { + g_set_error (&error, FPRINT_ERROR, FPRINT_ERROR_INVALID_FINGERNAME, + "Invalid finger name"); + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + if (!can_start_action (rdev, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + g_debug ("start enrollment device %d finger %d", priv->id, finger); + + priv->current_cancellable = g_cancellable_new (); + priv->enroll_data = finger; + fp_device_enroll (priv->dev, + fprint_device_create_enroll_template (rdev, priv->enroll_data), + priv->current_cancellable, + enroll_progress_cb, + rdev, + NULL, + (GAsyncReadyCallback) enroll_cb, + rdev); + + priv->current_action = ACTION_ENROLL; + + fprint_dbus_device_complete_enroll_start (dbus_dev, invocation); + + return TRUE; } +static gboolean +fprint_device_enroll_stop (FprintDBusDevice *dbus_dev, + GDBusMethodInvocation *invocation) +{ + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(GError) error = NULL; + + if (!_fprint_device_check_claimed (rdev, invocation, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + switch (priv->current_action) + { + case ACTION_ENROLL: + break; + + case ACTION_NONE: + g_dbus_method_invocation_return_error_literal ( + invocation, FPRINT_ERROR, + FPRINT_ERROR_NO_ACTION_IN_PROGRESS, + "No enrollment in progress"); + return TRUE; + + default: + g_dbus_method_invocation_return_error_literal ( + invocation, FPRINT_ERROR, + FPRINT_ERROR_ALREADY_IN_USE, + "Another operation is already in progress"); + return TRUE; + } + + if (priv->current_cancellable) + { + /* We return only when the action was cancelled */ + g_cancellable_cancel (priv->current_cancellable); + priv->current_cancel_invocation = invocation; + } + else + { + fprint_dbus_device_complete_enroll_stop (dbus_dev, invocation); + priv->current_action = ACTION_NONE; + } + + return TRUE; +} +static gboolean +fprint_device_list_enrolled_fingers (FprintDBusDevice *dbus_dev, + GDBusMethodInvocation *invocation, + const char *username) +{ + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(GPtrArray) ret = NULL; + g_autoptr(GSList) prints = NULL; + GSList *item; + const char *sender; + const char *user; + + sender = g_dbus_method_invocation_get_sender (invocation); + _fprint_device_add_client (rdev, sender); + + user = g_object_get_qdata (G_OBJECT (invocation), quark_auth_user); + g_assert (user); + prints = store.discover_prints (priv->dev, user); + + if (!prints) + { + g_dbus_method_invocation_return_error_literal (invocation, + FPRINT_ERROR, + FPRINT_ERROR_NO_ENROLLED_PRINTS, + "Failed to discover prints"); + return TRUE; + } + + ret = g_ptr_array_new (); + for (item = prints; item; item = item->next) + { + FpFinger finger = GPOINTER_TO_UINT (item->data); + g_ptr_array_add (ret, (char *) fp_finger_to_name (finger)); + } + g_ptr_array_add (ret, NULL); + + fprint_dbus_device_complete_list_enrolled_fingers (dbus_dev, + invocation, (const gchar *const *) ret->pdata); + + return TRUE; +} -static void fprint_device_enroll_start(FprintDevice *rdev, - const char *finger_name, DBusGMethodInvocation *context) +static void +delete_enrolled_fingers (FprintDevice *rdev, const char *user) { - g_autoptr(GError) error = NULL; - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - int finger_num = finger_name_to_num (finger_name); - - if (finger_num == -1) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_INVALID_FINGERNAME, - "Invalid finger name"); - dbus_g_method_return_error(context, error); - return; - } - - if (_fprint_device_check_claimed(rdev, context, &error) == FALSE) { - dbus_g_method_return_error (context, error); - return; - } - - if (_fprint_device_check_polkit_for_action (rdev, context, "net.reactivated.fprint.device.enroll", &error) == FALSE) { - dbus_g_method_return_error (context, error); - return; - } - - if (priv->current_action != ACTION_NONE) { - if (priv->current_action == ACTION_ENROLL) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, - "Enrollment already in progress"); - } else { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_ALREADY_IN_USE, - "Verification in progress"); - } - dbus_g_method_return_error(context, error); - return; - } - - g_debug("start enrollment device %d finger %d", priv->id, finger_num); - - priv->current_cancellable = g_cancellable_new (); - priv->enroll_data = finger_num; - fp_device_enroll (priv->dev, - fprint_device_create_enroll_template (rdev, priv->enroll_data), - priv->current_cancellable, - enroll_progress_cb, - rdev, - NULL, - (GAsyncReadyCallback) enroll_cb, - rdev); - - priv->current_action = ACTION_ENROLL; - - dbus_g_method_return(context); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + guint i; + + g_debug ("Deleting enrolled fingers for user %s", user); + + /* First try deleting the print from the device, we don't consider it + * fatal if this does not work. */ + if (fp_device_has_storage (priv->dev)) + { + g_autoptr(GSList) prints = NULL; + GSList *l; + + prints = store.discover_prints (priv->dev, user); + + for (l = prints; l != NULL; l = l->next) + { + g_autoptr(FpPrint) print = NULL; + + store.print_data_load (priv->dev, + GPOINTER_TO_UINT (l->data), + user, + &print); + + if (print) + { + g_autoptr(GError) error = NULL; + + if (!fp_device_delete_print_sync (priv->dev, print, NULL, &error)) + { + g_warning ("Error deleting print from device: %s", error->message); + g_warning ("This might indicate an issue in the libfprint driver or in the fingerprint device."); + } + } + } + } + + for (i = FP_FINGER_FIRST; i <= FP_FINGER_LAST; i++) + store.print_data_delete (priv->dev, i, user); } -static void fprint_device_enroll_stop(FprintDevice *rdev, - DBusGMethodInvocation *context) +#ifdef __linux__ +static void +log_offending_client_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - GError *error = NULL; - - if (_fprint_device_check_claimed(rdev, context, &error) == FALSE) { - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - if (_fprint_device_check_polkit_for_action (rdev, context, "net.reactivated.fprint.device.enroll", &error) == FALSE) { - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - if (priv->current_action != ACTION_ENROLL) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_NO_ACTION_IN_PROGRESS, - "No enrollment in progress"); - dbus_g_method_return_error(context, error); - g_error_free (error); - return; - } - - if (priv->current_cancellable) { - /* We return only when the action was cancelled */ - g_cancellable_cancel (priv->current_cancellable); - priv->current_cancel_context = context; - } else { - dbus_g_method_return (context); - priv->current_action = ACTION_NONE; - } + GDBusConnection *connection = G_DBUS_CONNECTION (object); + + g_autoptr(GVariant) ret = NULL; + g_autofree char *path = NULL; + g_autofree char *content = NULL; + guint pid = 0; + + ret = g_dbus_connection_call_finish (connection, res, NULL); + + if (!ret) + return; + + g_variant_get (ret, "(u)", &pid); + path = g_strdup_printf ("/proc/%u/comm", pid); + if (g_file_get_contents (path, &content, NULL, NULL)) + { + g_strchomp (content); + g_warning ("Offending API user is %s", content); + } } -static void fprint_device_list_enrolled_fingers(FprintDevice *rdev, - const char *username, - DBusGMethodInvocation *context) +static void +log_offending_client (GDBusMethodInvocation *invocation) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - GError *error = NULL; - GSList *prints; - GSList *item; - GPtrArray *ret; - char *user, *sender; - - user = _fprint_device_check_for_username (rdev, - context, - username, - NULL, - &error); - if (user == NULL) { - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - if (_fprint_device_check_polkit_for_action (rdev, context, "net.reactivated.fprint.device.verify", &error) == FALSE) { - g_free (user); - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - sender = dbus_g_method_get_sender (context); - _fprint_device_add_client (rdev, sender); - g_free (sender); - - prints = store.discover_prints(priv->dev, user); - g_free (user); - if (!prints) { - g_set_error(&error, FPRINT_ERROR, FPRINT_ERROR_NO_ENROLLED_PRINTS, - "Failed to discover prints"); - dbus_g_method_return_error(context, error); - g_error_free (error); - return; - } - - ret = g_ptr_array_new (); - for (item = prints; item; item = item->next) { - int finger_num = GPOINTER_TO_INT (item->data); - g_ptr_array_add (ret, g_strdup (finger_num_to_name (finger_num))); - } - g_ptr_array_add (ret, NULL); - - g_slist_free(prints); - - dbus_g_method_return(context, g_ptr_array_free (ret, FALSE)); + const char *sender; + GDBusConnection *connection; + + connection = g_dbus_method_invocation_get_connection (invocation); + sender = g_dbus_method_invocation_get_sender (invocation); + + g_dbus_connection_call (connection, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + g_variant_new ("(s)", sender), + NULL, G_DBUS_CALL_FLAGS_NONE, + -1, NULL, log_offending_client_cb, NULL); } +#endif -static void delete_enrolled_fingers(FprintDevice *rdev, const char *user) +static gboolean +fprint_device_delete_enrolled_fingers (FprintDBusDevice *dbus_dev, + GDBusMethodInvocation *invocation, + const char *username) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - guint i; - - /* First try deleting the print from the device, we don't consider it - * fatal if this does not work. */ - if (fp_device_has_storage (priv->dev)) { - g_autoptr(GSList) prints = NULL; - GSList *l; - - prints = store.discover_prints(priv->dev, user); - - for (l = prints; l != NULL; l = l->next) { - g_autoptr(FpPrint) print = NULL; - - store.print_data_load(priv->dev, - GPOINTER_TO_INT (l->data), - user, - &print); - - if (print) { - g_autoptr(GError) error = NULL; - - if (!fp_device_delete_print_sync (priv->dev, print, NULL, &error)) { - g_warning ("Error deleting print from device: %s", error->message); - g_warning ("This might indicate an issue in the libfprint driver or in the fingerprint device."); - } - } - } - } - - for (i = FP_FINGER_LEFT_THUMB; i <= FP_FINGER_RIGHT_LITTLE; i++) { - store.print_data_delete(priv->dev, i, user); - } + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(GError) error = NULL; + g_autofree char *user = NULL; + const char *sender; + gboolean opened; + + g_warning ("The API user should be updated to use DeleteEnrolledFingers2 method!"); +#ifdef __linux__ + log_offending_client (invocation); +#endif + + if (!can_start_action (rdev, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + priv->current_action = ACTION_DELETE; + + if (!_fprint_device_check_claimed (rdev, invocation, &error)) + { + /* Return error for anything but FPRINT_ERROR_CLAIM_DEVICE */ + if (!g_error_matches (error, FPRINT_ERROR, FPRINT_ERROR_CLAIM_DEVICE)) + { + g_dbus_method_invocation_return_gerror (invocation, + error); + return TRUE; + } + + opened = FALSE; + } + else + { + opened = TRUE; + } + + sender = g_dbus_method_invocation_get_sender (invocation); + _fprint_device_add_client (rdev, sender); + + if (!opened && fp_device_has_storage (priv->dev)) + fp_device_open_sync (priv->dev, NULL, NULL); + + user = g_object_steal_qdata (G_OBJECT (invocation), quark_auth_user); + g_assert (user); + g_assert (g_str_equal (username, "") || g_str_equal (user, username)); + + delete_enrolled_fingers (rdev, user); + + if (!opened && fp_device_has_storage (priv->dev)) + fp_device_close_sync (priv->dev, NULL, NULL); + + priv->current_action = ACTION_NONE; + + fprint_dbus_device_complete_delete_enrolled_fingers (dbus_dev, + invocation); + return TRUE; } -static void fprint_device_delete_enrolled_fingers(FprintDevice *rdev, - const char *username, - DBusGMethodInvocation *context) +static gboolean +fprint_device_delete_enrolled_fingers2 (FprintDBusDevice *dbus_dev, + GDBusMethodInvocation *invocation) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - GError *error = NULL; - char *user, *sender; - gboolean opened; - - g_warning ("The API user should be updated to use DeleteEnrolledFingers2 method!"); - - user = _fprint_device_check_for_username (rdev, - context, - username, - NULL, - &error); - if (user == NULL) { - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - if (_fprint_device_check_polkit_for_action (rdev, context, "net.reactivated.fprint.device.enroll", &error) == FALSE) { - g_free (user); - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } - - if (_fprint_device_check_claimed(rdev, context, &error) == FALSE) { - /* Return error for anything but FPRINT_ERROR_CLAIM_DEVICE */ - if (!g_error_matches (error, FPRINT_ERROR, FPRINT_ERROR_CLAIM_DEVICE)) { - dbus_g_method_return_error (context, error); - return; - } - - g_clear_error (&error); - opened = FALSE; - } else { - opened = TRUE; - } - - sender = dbus_g_method_get_sender (context); - _fprint_device_add_client (rdev, sender); - g_free (sender); - - if (!opened && fp_device_has_storage (priv->dev)) - fp_device_open_sync (priv->dev, NULL, NULL); - - delete_enrolled_fingers (rdev, user); - - if (!opened && fp_device_has_storage (priv->dev)) - fp_device_close_sync (priv->dev, NULL, NULL); - - g_free (user); - - dbus_g_method_return(context); + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + + g_autoptr(SessionData) session = NULL; + g_autoptr(GError) error = NULL; + + if (!_fprint_device_check_claimed (rdev, invocation, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + if (!can_start_action (rdev, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } + + priv->current_action = ACTION_DELETE; + + session = session_data_get (priv); + + delete_enrolled_fingers (rdev, session->username); + + priv->current_action = ACTION_NONE; + + fprint_dbus_device_complete_delete_enrolled_fingers2 (dbus_dev, + invocation); + return TRUE; } -static void fprint_device_delete_enrolled_fingers2(FprintDevice *rdev, - DBusGMethodInvocation *context) +static gboolean +handle_unauthorized_access (FprintDevice *rdev, + GDBusMethodInvocation *invocation, + GError *error) { - FprintDevicePrivate *priv = DEVICE_GET_PRIVATE(rdev); - GError *error = NULL; + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); - if (_fprint_device_check_claimed(rdev, context, &error) == FALSE) { - dbus_g_method_return_error (context, error); - return; - } + g_assert (error); - if (_fprint_device_check_polkit_for_action (rdev, context, "net.reactivated.fprint.device.enroll", &error) == FALSE) { - dbus_g_method_return_error (context, error); - g_error_free (error); - return; - } + g_warning ("Client %s not authorized to call method '%s' for device %s: %s", + g_dbus_method_invocation_get_sender (invocation), + g_dbus_method_invocation_get_method_name (invocation), + fp_device_get_name (priv->dev), + error->message); + g_dbus_method_invocation_return_gerror (invocation, error); - delete_enrolled_fingers (rdev, priv->username); + return FALSE; +} - dbus_g_method_return(context); +static gboolean +action_authorization_handler (GDBusInterfaceSkeleton *interface, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + FprintDBusDevice *dbus_dev = FPRINT_DBUS_DEVICE (interface); + FprintDevice *rdev = FPRINT_DEVICE (dbus_dev); + FprintDevicePrivate *priv = fprint_device_get_instance_private (rdev); + FprintDevicePermission required_perms; + gboolean needs_user_auth = FALSE; + + g_autoptr(GError) error = NULL; + const gchar *method_name; + + method_name = g_dbus_method_invocation_get_method_name (invocation); + + g_debug ("Requesting device '%s' authorization for method %s from %s", + fp_device_get_name (priv->dev), method_name, + g_dbus_method_invocation_get_sender (invocation)); + + if (g_str_equal (method_name, "Claim")) + needs_user_auth = TRUE; + else if (g_str_equal (method_name, "DeleteEnrolledFingers")) + needs_user_auth = TRUE; + else if (g_str_equal (method_name, "ListEnrolledFingers")) + needs_user_auth = TRUE; + + /* This is just a quick check in order to avoid authentication if + * the user cannot make the call at this time anyway. + * The method handler itself is required to check again! */ + if (!_fprint_device_check_claimed (rdev, invocation, &error)) + return handle_unauthorized_access (rdev, invocation, error); + + if (needs_user_auth && + !fprintd_device_authorize_user (rdev, invocation, &error)) + return handle_unauthorized_access (rdev, invocation, error); + + required_perms = get_permissions_for_invocation (invocation); + + /* This may possibly block the invocation till the user has not + * provided an authentication method, so other calls could arrive */ + if (!fprint_device_check_polkit_for_permissions (rdev, invocation, + required_perms, + &error)) + return handle_unauthorized_access (rdev, invocation, error); + + g_debug ("Authorization granted to %s to call method '%s' for device %s!", + fp_device_get_name (priv->dev), + g_dbus_method_invocation_get_method_name (invocation), + g_dbus_method_invocation_get_sender (invocation)); + + return TRUE; } +static void +fprint_device_dbus_skeleton_iface_init (FprintDBusDeviceIface *iface) +{ + iface->handle_claim = fprint_device_claim; + iface->handle_delete_enrolled_fingers = fprint_device_delete_enrolled_fingers; + iface->handle_delete_enrolled_fingers2 = fprint_device_delete_enrolled_fingers2; + iface->handle_enroll_start = fprint_device_enroll_start; + iface->handle_enroll_stop = fprint_device_enroll_stop; + iface->handle_list_enrolled_fingers = fprint_device_list_enrolled_fingers; + iface->handle_release = fprint_device_release; + iface->handle_verify_start = fprint_device_verify_start; + iface->handle_verify_stop = fprint_device_verify_stop; +} diff --git a/src/device.xml b/src/device.xml index 9f71bf9..92af74d 100644 --- a/src/device.xml +++ b/src/device.xml @@ -12,9 +12,6 @@ - - PolicyKit integration @@ -250,9 +247,9 @@ No further prints can be enrolled on this device, Device.EnrollStop should now be called. - Delete other prints from the device first to continue (e.g. from other users). Note that - old prints or prints from other operating systems may be deleted automatically to resolve - this error without any notification. + Delete other prints from the device first to continue + (e.g. from other users). Note that old prints or prints from other operating systems may be deleted automatically + to resolve this error without any notification. @@ -282,8 +279,6 @@ An array of strings representing the enrolled fingerprints. See Fingerprint names. - - @@ -304,15 +299,15 @@ The username for whom to delete the enrolled fingerprints. See Usernames. - - Delete all the enrolled fingerprints for the chosen user. - Only for compatibility, use DeleteEnrolledFingers2 instead. + This call only exists for compatibility reasons, you should instead claim the device using + Device.Claim and then call + DeleteEnrolledFingers2. @@ -325,12 +320,10 @@ - - - Delete all the enrolled fingerprints for the user currently claiming the device. + Delete all the enrolled fingerprints for the user currently claiming the device with Device.Claim. @@ -346,8 +339,6 @@ The username for whom to claim the device. See Usernames. - - @@ -366,8 +357,6 @@ - - @@ -388,8 +377,6 @@ A string representing the finger to verify. See Fingerprint names. - - @@ -413,8 +400,6 @@ - - @@ -484,12 +469,10 @@ Fingerprint names. Note that "any" is not a valid finger name for this method. - - - Start enrollemnt for the selected finger. You need to have claimed the device using + Start enrollment for the selected finger. You need to have claimed the device using Device.Claim before calling this method. Enrollment status is sent through Device::EnrollStatus. @@ -499,7 +482,7 @@ if the caller lacks the appropriate PolicyKit authorization if the device was not claimed if the device was already being used - if there are no enrolled prints for the chosen user + if the finger name passed is invalid if there was an internal error @@ -509,8 +492,6 @@ - - diff --git a/src/file_storage.c b/src/file_storage.c index d46a047..158f35a 100644 --- a/src/file_storage.c +++ b/src/file_storage.c @@ -7,12 +7,12 @@ * 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. @@ -42,263 +42,299 @@ #define FILE_STORAGE_PATH "/var/lib/fprint" #define DIR_PERMS 0700 -#define FP_FINGER_IS_VALID(finger) \ - ((finger) >= FP_FINGER_LEFT_THUMB && (finger) <= FP_FINGER_RIGHT_LITTLE) +static char *storage_path = NULL; -static const char *get_storage_path() +static const char * +get_storage_path (void) { - const char *path; - - /* set by systemd >= 240 to an absolute path - * taking into account the StateDirectory - * unit file setting */ - path = g_getenv ("STATE_DIRECTORY"); - if (path != NULL) - return path; - - return FILE_STORAGE_PATH; + const char *path = NULL; + + if (storage_path != NULL) + return storage_path; + + /* set by systemd >= 240 to an absolute path + * taking into account the StateDirectory + * unit file setting */ + path = g_getenv ("STATE_DIRECTORY"); + if (path != NULL) + { + /* If multiple directories are set, then in the environment variable + * 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]); + } + } + + 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) +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, - FpFinger finger, char *base_store) +static char * +__get_path_to_print (const char *driver, const char * device_id, + FpFinger finger, char *base_store) { - char *dirpath; - char *path; - char fingername[2]; + g_autofree char *dirpath = NULL; + char *path; + char fingername[2]; - g_snprintf(fingername, 2, "%x", finger); + g_snprintf (fingername, 2, "%x", finger); - dirpath = get_path_to_storedir(driver, device_id, base_store); - path = g_build_filename(dirpath, fingername, NULL); - g_free(dirpath); - return path; + dirpath = get_path_to_storedir (driver, device_id, base_store); + path = g_build_filename (dirpath, fingername, NULL); + 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), - fp_device_get_device_id(dev), - finger, - base_store); + return __get_path_to_print (fp_device_get_driver (dev), + fp_device_get_device_id (dev), + finger, + 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), - fp_device_get_device_id(dev), - finger, - base_store); + return __get_path_to_print (fp_device_get_driver (dev), + fp_device_get_device_id (dev), + finger, + 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_autofree char *path = NULL; - g_autofree char *dirpath = NULL; - g_autofree char *base_store = NULL; - g_autofree char *buf = NULL; - gsize len; - int r; - - base_store = file_storage_get_basestore_for_username(fp_print_get_username (print)); - - if (!fp_print_serialize (print, (guchar **) &buf, &len, &err)) { - g_warning ("Error serializing data: %s", err->message); - return -ENOMEM; - } - - path = __get_path_to_print(fp_print_get_driver (print), - fp_print_get_device_id (print), - fp_print_get_finger (print), - base_store); - dirpath = g_path_get_dirname(path); - r = g_mkdir_with_parents(dirpath, DIR_PERMS); - if (r < 0) { - g_debug("file_storage_print_data_save(): could not mkdir(\"%s\"): %s", - dirpath, g_strerror(r)); - return r; - } - - //fp_dbg("saving to %s", path); - g_file_set_contents(path, buf, len, &err); - if (err) { - g_debug("file_storage_print_data_save(): could not save '%s': %s", - path, err->message); - /* FIXME interpret error codes */ - return err->code; - } - - return 0; + g_autoptr(GError) err = NULL; + g_autofree char *path = NULL; + g_autofree char *dirpath = NULL; + g_autofree char *base_store = NULL; + g_autofree char *buf = NULL; + gsize len; + int r; + + base_store = file_storage_get_basestore_for_username (fp_print_get_username (print)); + + if (!fp_print_serialize (print, (guchar **) &buf, &len, &err)) + { + g_warning ("Error serializing data: %s", err->message); + return -ENOMEM; + } + + path = __get_path_to_print (fp_print_get_driver (print), + fp_print_get_device_id (print), + fp_print_get_finger (print), + base_store); + dirpath = g_path_get_dirname (path); + r = g_mkdir_with_parents (dirpath, DIR_PERMS); + if (r < 0) + { + g_debug ("file_storage_print_data_save(): could not mkdir(\"%s\"): %s", + dirpath, g_strerror (r)); + return r; + } + + //fp_dbg("saving to %s", path); + g_file_set_contents (path, buf, len, &err); + if (err) + { + g_debug ("file_storage_print_data_save(): could not save '%s': %s", + path, err->message); + /* FIXME interpret error codes */ + return err->code; + } + + 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; - gsize length; - char *contents; - FpPrint *new; - - //fp_dbg("from %s", path); - g_file_get_contents(path, &contents, &length, &err); - if (err) { - int r = err->code; - /* FIXME interpret more error codes */ - if (r == G_FILE_ERROR_NOENT) - return -ENOENT; - else - return r; - } - - new = fp_print_deserialize ((guchar *) contents, length, &err); - if (!new) { - g_print ("Error deserializing data: %s", err->message); - return -EIO; - } - - *print = new; - return 0; + g_autoptr(GError) err = NULL; + gsize length; + g_autofree char *contents = NULL; + FpPrint *new; + + //fp_dbg("from %s", path); + g_file_get_contents (path, &contents, &length, &err); + if (err) + { + int r = err->code; + /* FIXME interpret more error codes */ + if (r == G_FILE_ERROR_NOENT) + return -ENOENT; + else + return r; + } + + new = fp_print_deserialize ((guchar *) contents, length, &err); + if (!new) + { + g_print ("Error deserializing data: %s", err->message); + return -EIO; + } + + *print = new; + return 0; } -int file_storage_print_data_load(FpDevice *dev, - FpFinger finger, - const char *username, - FpPrint **print) +int +file_storage_print_data_load (FpDevice *dev, + FpFinger finger, + const char *username, + FpPrint **print) { - g_autofree gchar *path = NULL; - g_autofree gchar *base_store = NULL; - FpPrint *new = NULL; - int r; - - base_store = file_storage_get_basestore_for_username(username); - - path = get_path_to_print(dev, finger, base_store); - r = load_from_file(path, &new); - g_debug ("file_storage_print_data_load(): loaded '%s' %s", - path, g_strerror(r)); - if (r) - return r; - - if (!fp_print_compatible (new, dev)) { - g_object_unref (new); - return -EINVAL; - } - - *print = new; - return 0; + g_autofree gchar *path = NULL; + g_autofree gchar *base_store = NULL; + + g_autoptr(FpPrint) new = NULL; + int r; + + base_store = file_storage_get_basestore_for_username (username); + + path = get_path_to_print (dev, finger, base_store); + r = load_from_file (path, &new); + g_debug ("file_storage_print_data_load(): loaded '%s' %s", + path, g_strerror (r)); + if (r) + return r; + + if (!fp_print_compatible (new, dev)) + return -EINVAL; + + *print = g_steal_pointer (&new); + 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 *path = NULL; - int r; + g_autofree gchar *base_store = NULL; + g_autofree gchar *path = NULL; + int r; - base_store = file_storage_get_basestore_for_username(username); + base_store = file_storage_get_basestore_for_username (username); - path = get_path_to_print_dscv(dev, finger, base_store); + path = get_path_to_print_dscv (dev, finger, base_store); - r = g_unlink(path); - g_debug("file_storage_print_data_delete(): unlink(\"%s\") %s", - path, g_strerror(r)); + r = g_unlink (path); + g_debug ("file_storage_print_data_delete(): unlink(\"%s\") %s", + path, g_strerror (r)); - /* FIXME: cleanup empty directory */ - return g_unlink(path); + /* FIXME: cleanup empty directory */ + return g_unlink (path); } -static GSList *scan_dev_storedir(char *devpath, - GSList *list) +static GSList * +scan_dev_storedir (char *devpath, + GSList *list) { - g_autoptr(GError) err = NULL; - const gchar *ent; - - GDir *dir = g_dir_open(devpath, 0, &err); - if (!dir) { - g_debug("scan_dev_storedir(): opendir(\"%s\") failed: %s", devpath, err->message); - return list; - } - - while ((ent = g_dir_read_name(dir))) { - /* ent is an 1 hex character fp_finger code */ - guint64 val; - gchar *endptr; - - if (*ent == 0 || strlen(ent) != 1) - continue; - - val = g_ascii_strtoull(ent, &endptr, 16); - if (endptr == ent || !FP_FINGER_IS_VALID(val)) { - g_debug("scan_dev_storedir(): skipping print file '%s'", ent); - continue; - } - - list = g_slist_prepend(list, GINT_TO_POINTER(val)); - } - - g_dir_close(dir); - return list; + g_autoptr(GError) err = NULL; + const gchar *ent; + + GDir *dir = g_dir_open (devpath, 0, &err); + + if (!dir) + { + g_debug ("scan_dev_storedir(): opendir(\"%s\") failed: %s", devpath, err->message); + return list; + } + + while ((ent = g_dir_read_name (dir))) + { + /* ent is an 1 hex character fp_finger code */ + guint64 val; + gchar *endptr; + + if (*ent == 0 || strlen (ent) != 1) + continue; + + val = g_ascii_strtoull (ent, &endptr, 16); + if (endptr == ent || !FP_FINGER_IS_VALID (val)) + { + g_debug ("scan_dev_storedir(): skipping print file '%s'", ent); + continue; + } + + list = g_slist_prepend (list, GUINT_TO_POINTER (val)); + } + + g_dir_close (dir); + 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; - g_autofree gchar *base_store = NULL; - g_autofree gchar *storedir = NULL; + GSList *list = NULL; + g_autofree gchar *base_store = NULL; + g_autofree gchar *storedir = NULL; - base_store = file_storage_get_basestore_for_username(username); + base_store = file_storage_get_basestore_for_username (username); - storedir = get_path_to_storedir(fp_device_get_driver (dev), - fp_device_get_device_id (dev), - base_store); + storedir = get_path_to_storedir (fp_device_get_driver (dev), + fp_device_get_device_id (dev), + base_store); - g_debug ("file_storage_discover_prints() for user '%s' in '%s'", - username, storedir); + g_debug ("file_storage_discover_prints() for user '%s' in '%s'", + username, storedir); - list = scan_dev_storedir(storedir, list); + list = scan_dev_storedir (storedir, list); - return list; + return list; } -GSList *file_storage_discover_users() +GSList * +file_storage_discover_users (void) { - g_autoptr(GError) err = NULL; - GSList *list = NULL; - const gchar *ent; - GDir *dir = g_dir_open(get_storage_path(), 0, &err); + g_autoptr(GError) err = NULL; + GSList *list = NULL; + const gchar *ent; + GDir *dir = g_dir_open (get_storage_path (), 0, &err); - if (!dir) { - return list; - } + if (!dir) + return list; - while ((ent = g_dir_read_name(dir))) { - /* ent is a username */ - if (*ent == 0) - continue; + while ((ent = g_dir_read_name (dir))) + { + /* ent is a username */ + if (*ent == 0) + continue; - list = g_slist_prepend(list, g_strdup (ent)); - } + list = g_slist_prepend (list, g_strdup (ent)); + } - g_dir_close(dir); - return list; + g_dir_close (dir); + return list; } -int file_storage_init(void) +int +file_storage_init (void) { - /* Nothing to do */ - return 0; + /* Nothing to do */ + return 0; } -int file_storage_deinit(void) +int +file_storage_deinit (void) { - /* Nothing to do */ - return 0; + g_clear_pointer (&storage_path, g_free); + return 0; } diff --git a/src/file_storage.h b/src/file_storage.h index 2209410..29e70df 100644 --- a/src/file_storage.h +++ b/src/file_storage.h @@ -6,38 +6,35 @@ * 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. * */ -#ifndef FILE_STORAGE_H - -#define FILE_STORAGE_H - -int file_storage_print_data_save(FpPrint *print); +#pragma once -int file_storage_print_data_load(FpDevice *dev, - FpFinger finger, - const char *username, - FpPrint **print); +int file_storage_print_data_save (FpPrint *print); -int file_storage_print_data_delete(FpDevice *dev, - FpFinger finger, - const char *username); +int file_storage_print_data_load (FpDevice *dev, + FpFinger finger, + const char *username, + FpPrint **print); -int file_storage_init(void); +int file_storage_print_data_delete (FpDevice *dev, + FpFinger finger, + const char *username); -int file_storage_deinit(void); +int file_storage_init (void); -GSList *file_storage_discover_prints(FpDevice *dev, const char *username); -GSList *file_storage_discover_users(void); +int file_storage_deinit (void); -#endif +GSList *file_storage_discover_prints (FpDevice *dev, + const char *username); +GSList *file_storage_discover_users (void); diff --git a/src/fprintd.h b/src/fprintd.h index bea767b..4075bc2 100644 --- a/src/fprintd.h +++ b/src/fprintd.h @@ -6,99 +6,88 @@ * 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. */ -#ifndef __FPRINTD_H__ -#define __FPRINTD_H__ +#pragma once #include -#include -#include "fprint.h" +#include +#include +#include "fprintd-enums.h" +#include "fprintd-dbus.h" /* General */ #define TIMEOUT 30 #define FPRINT_SERVICE_NAME "net.reactivated.Fprint" -extern DBusGConnection *fprintd_dbus_conn; +#define FPRINT_SERVICE_PATH "/net/reactivated/Fprint" /* Errors */ -GQuark fprint_error_quark(void); -GType fprint_error_get_type(void); +GQuark fprint_error_quark (void); -#define FPRINT_ERROR fprint_error_quark() -#define FPRINT_TYPE_ERROR fprint_error_get_type() -#define FPRINT_ERROR_DBUS_INTERFACE "net.reactivated.Fprint.Error" +#define FPRINT_ERROR fprint_error_quark () typedef enum { - FPRINT_ERROR_CLAIM_DEVICE, /* developer didn't claim the device */ - FPRINT_ERROR_ALREADY_IN_USE, /* device is already claimed by somebody else */ - FPRINT_ERROR_INTERNAL, /* internal error occured */ - FPRINT_ERROR_PERMISSION_DENIED, /* PolicyKit refused the action */ - FPRINT_ERROR_NO_ENROLLED_PRINTS, /* No prints are enrolled */ - FPRINT_ERROR_NO_ACTION_IN_PROGRESS, /* No actions currently in progress */ - FPRINT_ERROR_INVALID_FINGERNAME, /* the finger name passed was invalid */ - FPRINT_ERROR_NO_SUCH_DEVICE, /* device does not exist */ + /* developer didn't claim the device */ + FPRINT_ERROR_CLAIM_DEVICE, /*< nick=net.reactivated.Fprint.Error.ClaimDevice >*/ + /* device is already claimed by somebody else */ + FPRINT_ERROR_ALREADY_IN_USE, /*< nick=net.reactivated.Fprint.Error.AlreadyInUse >*/ + /* internal error occurred */ + FPRINT_ERROR_INTERNAL, /*< nick=net.reactivated.Fprint.Error.Internal >*/ + /* PolicyKit refused the action */ + 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; -/* Manager */ -#define FPRINT_TYPE_MANAGER (fprint_manager_get_type()) -#define FPRINT_MANAGER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), FPRINT_TYPE_MANAGER, FprintManager)) -#define FPRINT_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FPRINT_TYPE_MANAGER, FprintManagerClass)) -#define FPRINT_IS_MANAGER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), FPRINT_TYPE_MANAGER)) -#define FPRINT_IS_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FPRINT_TYPE_MANAGER)) -#define FPRINT_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FPRINT_TYPE_MANAGER, FprintManagerClass)) +/* 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; -struct FprintManager { - GObject parent; -}; +/* Manager */ +#define FPRINT_TYPE_MANAGER (fprint_manager_get_type ()) +G_DECLARE_FINAL_TYPE (FprintManager, fprint_manager, FPRINT, MANAGER, GObject) -struct FprintManagerClass { - GObjectClass parent; +struct _FprintManager +{ + GObject parent; }; -typedef struct FprintManager FprintManager; -typedef struct FprintManagerClass FprintManagerClass; - -FprintManager *fprint_manager_new(gboolean no_timeout); -GType fprint_manager_get_type(void); +FprintManager *fprint_manager_new (GDBusConnection *connection, + gboolean no_timeout); /* Device */ -#define FPRINT_TYPE_DEVICE (fprint_device_get_type()) -#define FPRINT_DEVICE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), FPRINT_DEVICE_TYPE, FprintDevice)) -#define FPRINT_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FPRINT_DEVICE_TYPE, FprintDeviceClass)) -#define FPRINT_IS_DEVICE(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), FPRINT_TYPE_DEVICE)) -#define FPRINT_IS_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FPRINT_TYPE_DEVICE)) -#define FPRINT_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FPRINT_TYPE_DEVICE, FprintDeviceClass)) +#define FPRINT_TYPE_DEVICE (fprint_device_get_type ()) +G_DECLARE_FINAL_TYPE (FprintDevice, fprint_device, FPRINT, DEVICE, + FprintDBusDeviceSkeleton) -struct FprintDevice { - GObject parent; +struct _FprintDevice +{ + FprintDBusDeviceSkeleton parent; }; -struct FprintDeviceClass { - GObjectClass parent; -}; - -typedef struct FprintDevice FprintDevice; -typedef struct FprintDeviceClass FprintDeviceClass; - -FprintDevice *fprint_device_new(FpDevice *dev); -GType fprint_device_get_type(void); -guint32 _fprint_device_get_id(FprintDevice *rdev); +FprintDevice *fprint_device_new (FpDevice *dev); +guint32 _fprint_device_get_id (FprintDevice *rdev); /* Print */ /* TODO */ - -/* Binding data included in main.c thorugh 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; - -#endif - diff --git a/src/main.c b/src/main.c index 4037808..00171a0 100644 --- a/src/main.c +++ b/src/main.c @@ -1,17 +1,18 @@ /* * fprint D-Bus daemon * Copyright (C) 2008 Daniel Drake + * Copyright (C) 2020 Marco Trevisan * * 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. @@ -19,196 +20,203 @@ #include "config.h" +#include #include #include -#include +#include #include #include #include #include +#include #include #include "fprintd.h" #include "storage.h" #include "file_storage.h" -extern DBusGConnection *fprintd_dbus_conn; +fp_storage store; + static gboolean no_timeout = FALSE; static gboolean g_fatal_warnings = FALSE; static void set_storage_file (void) { - store.init = &file_storage_init; - store.deinit = &file_storage_deinit; - store.print_data_save = &file_storage_print_data_save; - store.print_data_load = &file_storage_print_data_load; - store.print_data_delete = &file_storage_print_data_delete; - store.discover_prints = &file_storage_discover_prints; - store.discover_users = &file_storage_discover_users; + store.init = &file_storage_init; + store.deinit = &file_storage_deinit; + store.print_data_save = &file_storage_print_data_save; + store.print_data_load = &file_storage_print_data_load; + store.print_data_delete = &file_storage_print_data_delete; + store.discover_prints = &file_storage_discover_prints; + store.discover_users = &file_storage_discover_users; } static gboolean load_storage_module (const char *module_name) { - GModule *module; - char *filename; - - filename = g_module_build_path (PLUGINDIR, module_name); - module = g_module_open (filename, 0); - g_free (filename); - if (module == NULL) - return FALSE; - - if (!g_module_symbol (module, "init", (gpointer *) &store.init) || - !g_module_symbol (module, "deinit", (gpointer *) &store.deinit) || - !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_delete", (gpointer *) &store.print_data_delete) || - !g_module_symbol (module, "discover_prints", (gpointer *) &store.discover_prints)) { - g_module_close (module); - return FALSE; - } - - g_module_make_resident (module); - - return TRUE; + GModule *module; + g_autofree char *filename = NULL; + + filename = g_module_build_path (PLUGINDIR, module_name); + module = g_module_open (filename, 0); + if (module == NULL) + return FALSE; + + if (!g_module_symbol (module, "init", (gpointer *) &store.init) || + !g_module_symbol (module, "deinit", (gpointer *) &store.deinit) || + !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_delete", (gpointer *) &store.print_data_delete) || + !g_module_symbol (module, "discover_prints", (gpointer *) &store.discover_prints)) + { + g_module_close (module); + return FALSE; + } + + g_module_make_resident (module); + + return TRUE; } static gboolean load_conf (void) { - GKeyFile *file; - char *filename; - char *module_name; - GError *error = NULL; - gboolean ret; - - filename = g_build_filename (SYSCONFDIR, "fprintd.conf", NULL); - file = g_key_file_new (); - g_debug("About to load configuration file '%s'", filename); - 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); - goto bail; - } - - g_free (filename); - filename = NULL; - - module_name = g_key_file_get_string (file, "storage", "type", &error); - if (module_name == NULL) - goto bail; - - g_key_file_free (file); - - if (g_str_equal (module_name, "file")) { - g_free (module_name); - set_storage_file (); - return TRUE; - } - - ret = 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; + g_autofree char *filename = NULL; + g_autofree char *module_name = NULL; + + g_autoptr(GKeyFile) file = NULL; + g_autoptr(GError) error = NULL; + + filename = g_build_filename (SYSCONFDIR, "fprintd.conf", NULL); + file = g_key_file_new (); + g_debug ("About to load configuration file '%s'", filename); + 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); + return FALSE; + } + + module_name = g_key_file_get_string (file, "storage", "type", &error); + if (module_name == NULL) + return FALSE; + + if (g_str_equal (module_name, "file")) + { + set_storage_file (); + return TRUE; + } + + return load_storage_module (module_name); } static const GOptionEntry entries[] = { - {"g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal", NULL}, - {"no-timeout", 't', 0, G_OPTION_ARG_NONE, &no_timeout, "Do not exit after unused for a while", NULL}, - { NULL } + {"g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal", NULL}, + {"no-timeout", 't', 0, G_OPTION_ARG_NONE, &no_timeout, "Do not exit after unused for a while", NULL}, + { NULL } }; -int main(int argc, char **argv) +static gboolean +sigterm_callback (gpointer data) +{ + GMainLoop *loop = data; + + g_main_loop_quit (loop); + return FALSE; +} + +static void +on_name_acquired (GDBusConnection *connection, + const char *name, + gpointer user_data) { - GOptionContext *context; - GMainLoop *loop; - GError *error = NULL; - FprintManager *manager; - DBusGProxy *driver_proxy; - guint32 request_name_ret; - - bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); - bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - textdomain (GETTEXT_PACKAGE); - - context = g_option_context_new ("Fingerprint handler daemon"); - g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); - -#if !GLIB_CHECK_VERSION (2, 36, 0) - g_type_init(); -#endif - - if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) { - g_warning ("couldn't parse command-line options: %s\n", error->message); - g_error_free (error); - return 1; - } - - if (g_fatal_warnings) { - GLogLevelFlags fatal_mask; - - fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); - fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; - g_log_set_always_fatal (fatal_mask); - } - - /* Obtain a connection to the session bus */ - fprintd_dbus_conn = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); - if (fprintd_dbus_conn == NULL) { - g_warning("Failed to open connection to bus: %s", error->message); - 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, - * and the default storage plugin */ - if (!load_conf()) - set_storage_file (); - store.init (); - - loop = g_main_loop_new(NULL, FALSE); - - g_debug("Launching FprintObject"); - - /* create the one instance of the Manager object to be shared between - * all fprintd users */ - manager = fprint_manager_new(no_timeout); - - /* Obtain the well-known name after the manager has been initialized. - * Otherwise a client immediately enumerating the devices will not see - * any. */ - if (!org_freedesktop_DBus_request_name(driver_proxy, FPRINT_SERVICE_NAME, - 0, &request_name_ret, &error)) { - g_warning("Failed to get name: %s", error->message); - g_object_unref (manager); - return 1; - } - - 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_main_loop_run(loop); - g_debug("main loop completed"); - - g_object_unref (manager); - - return 0; + g_debug ("D-Bus service launched with name: %s", name); } +static void +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; + + setlocale (LC_ALL, ""); + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + context = g_option_context_new ("Fingerprint handler daemon"); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + + if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) + { + g_warning ("couldn't parse command-line options: %s\n", error->message); + return 1; + } + + if (g_fatal_warnings) + { + GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); + fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal (fatal_mask); + } + + /* Obtain a connection to the system bus */ + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!G_IS_DBUS_CONNECTION (connection)) + { + g_warning ("Failed to open connection to bus: %s", error->message); + return 1; + } + + /* Load the configuration file, + * and the default storage plugin */ + if (!load_conf ()) + set_storage_file (); + store.init (); + + loop = g_main_loop_new (NULL, FALSE); + g_unix_signal_add (SIGTERM, sigterm_callback, loop); + + g_debug ("Launching FprintObject"); + + /* create the one instance of the Manager object to be shared between + * all fprintd users. This blocks until all the devices are enumerated */ + manager = fprint_manager_new (connection, no_timeout); + + /* Obtain the well-known name after the manager has been initialized. + * Otherwise a client immediately enumerating the devices will not see + * any. */ + request_name_ret = g_bus_own_name_on_connection (connection, + FPRINT_SERVICE_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, + on_name_acquired, + on_name_lost, + loop, NULL); + + g_debug ("entering main loop"); + g_main_loop_run (loop); + g_bus_unown_name (request_name_ret); + g_debug ("main loop completed"); + + return 0; +} diff --git a/src/manager.c b/src/manager.c index d4c1d9f..3753c4a 100644 --- a/src/manager.c +++ b/src/manager.c @@ -1,17 +1,18 @@ /* * /net/reactivated/Fprint/Manager object implementation * Copyright (C) 2008 Daniel Drake + * Copyright (C) 2020 Marco Trevisan * * 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. @@ -19,7 +20,6 @@ #include #include -#include #include #include #include @@ -27,258 +27,431 @@ #include "fprintd.h" -DBusGConnection *fprintd_dbus_conn; - -static gboolean fprint_manager_get_devices(FprintManager *manager, - GPtrArray **devices, GError **error); -static gboolean fprint_manager_get_default_device(FprintManager *manager, - const char **device, GError **error); -#include "manager-dbus-glue.h" - -static GObjectClass *parent_class = NULL; - -G_DEFINE_TYPE(FprintManager, fprint_manager, G_TYPE_OBJECT); +static void fprint_manager_constructed (GObject *object); +static gboolean fprint_manager_get_devices (FprintManager *manager, + GPtrArray **devices, + GError **error); +static gboolean fprint_manager_get_default_device (FprintManager *manager, + const char **device, + GError **error); typedef struct { - FpContext *context; - GSList *dev_registry; - gboolean no_timeout; - guint timeout_id; + GDBusConnection *connection; + GDBusObjectManager *object_manager; + FprintDBusManager *dbus_manager; + FpContext *context; + gboolean no_timeout; + guint timeout_id; } FprintManagerPrivate; -#define FPRINT_MANAGER_GET_PRIVATE(o) \ - (G_TYPE_INSTANCE_GET_PRIVATE ((o), FPRINT_TYPE_MANAGER, FprintManagerPrivate)) +G_DEFINE_TYPE_WITH_CODE (FprintManager, fprint_manager, G_TYPE_OBJECT, G_ADD_PRIVATE (FprintManager)) + +enum { + PROP_0, + FPRINT_MANAGER_CONNECTION, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; -static void fprint_manager_finalize(GObject *object) +static void +fprint_manager_finalize (GObject *object) { - FprintManagerPrivate *priv = FPRINT_MANAGER_GET_PRIVATE (object); + FprintManagerPrivate *priv = fprint_manager_get_instance_private (FPRINT_MANAGER (object)); - g_slist_free(priv->dev_registry); + g_clear_object (&priv->object_manager); + g_clear_object (&priv->dbus_manager); + g_clear_object (&priv->connection); + g_clear_object (&priv->context); - G_OBJECT_CLASS(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, - &dbus_glib_fprint_manager_object_info); - dbus_g_error_domain_register (FPRINT_ERROR, FPRINT_ERROR_DBUS_INTERFACE, FPRINT_TYPE_ERROR); + FprintDevice *rdev; - g_type_class_add_private ((GObjectClass *) klass, sizeof (FprintManagerPrivate)); + g_object_get (object, "device", &rdev, NULL); + return rdev; +} - G_OBJECT_CLASS(klass)->finalize = fprint_manager_finalize; - parent_class = g_type_class_peek_parent(klass); +static void +fprint_manager_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + 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 gchar *get_device_path(FprintDevice *rdev) +static void +fprint_manager_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) { - return g_strdup_printf("/net/reactivated/Fprint/Device/%d", - _fprint_device_get_id(rdev)); + 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)); } static gboolean fprint_manager_timeout_cb (FprintManager *manager) { - //FIXME kill all the devices - exit(0); - return FALSE; + //FIXME kill all the devices + exit (0); + return FALSE; } static void fprint_manager_in_use_notified (FprintDevice *rdev, GParamSpec *spec, FprintManager *manager) { - FprintManagerPrivate *priv = FPRINT_MANAGER_GET_PRIVATE (manager); - guint num_devices_used = 0; - GSList *l; - gboolean in_use; - - if (priv->timeout_id > 0) { - g_source_remove (priv->timeout_id); - priv->timeout_id = 0; - } - if (priv->no_timeout) - return; - - for (l = priv->dev_registry; l != NULL; l = l->next) { - FprintDevice *dev = l->data; - - g_object_get (G_OBJECT(dev), "in-use", &in_use, NULL); - if (in_use != FALSE) - num_devices_used++; - } - - if (num_devices_used == 0) - priv->timeout_id = g_timeout_add_seconds (TIMEOUT, (GSourceFunc) fprint_manager_timeout_cb, manager); + FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); + guint num_devices_used = 0; + + g_autolist (GDBusObject) devices = NULL; + GList *l; + gboolean in_use; + + if (priv->timeout_id > 0) + { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + } + if (priv->no_timeout) + return; + + devices = g_dbus_object_manager_get_objects (priv->object_manager); + + 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); + if (in_use != FALSE) + num_devices_used++; + } + + if (num_devices_used == 0) + priv->timeout_id = g_timeout_add_seconds (TIMEOUT, (GSourceFunc) fprint_manager_timeout_cb, manager); } -static void -device_added_cb (FprintManager *manager, FpDevice *device, FpContext *context) +static gboolean +handle_get_devices (FprintManager *manager, GDBusMethodInvocation *invocation, + FprintDBusManager *skeleton) { - FprintManagerPrivate *priv = FPRINT_MANAGER_GET_PRIVATE (manager); - FprintDevice *rdev = fprint_device_new(device); - g_autofree gchar *path = NULL; + 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; + } - g_signal_connect (G_OBJECT(rdev), "notify::in-use", - G_CALLBACK (fprint_manager_in_use_notified), manager); + fprint_dbus_manager_complete_get_devices (skeleton, invocation, + (const gchar *const *) + devices->pdata); - priv->dev_registry = g_slist_prepend (priv->dev_registry, rdev); - path = get_device_path (rdev); - dbus_g_connection_register_g_object(fprintd_dbus_conn, path, - G_OBJECT(rdev)); + return TRUE; } -static void -device_removed_cb (FprintManager *manager, FpDevice *device, FpContext *context) +static gboolean +handle_get_default_device (FprintManager *manager, + GDBusMethodInvocation *invocation, + FprintDBusManager *skeleton) { - FprintManagerPrivate *priv = FPRINT_MANAGER_GET_PRIVATE (manager); - GSList *item; - g_autofree gchar *path = NULL; + const gchar *device; + + g_autoptr(GError) error = NULL; - for (item = priv->dev_registry; item; item = item->next) { - FprintDevice *rdev; - g_autoptr(FpDevice) dev = NULL; + if (!fprint_manager_get_default_device (manager, &device, &error)) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return TRUE; + } - rdev = item->data; + fprint_dbus_manager_complete_get_default_device (skeleton, invocation, + device); - g_object_get (rdev, "dev", &dev, NULL); - if (dev != device) - continue; + return TRUE; +} - priv->dev_registry = g_slist_delete_link (priv->dev_registry, item); +static void +device_added_cb (FprintManager *manager, FpDevice *device, FpContext *context) +{ + FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); - dbus_g_connection_unregister_g_object(fprintd_dbus_conn, G_OBJECT(rdev)); + g_autoptr(FprintDBusObjectSkeleton) object = NULL; + g_autoptr(FprintDevice) rdev = NULL; + g_autofree gchar *path = NULL; - g_signal_handlers_disconnect_by_data (rdev, manager); - g_object_unref (rdev); + rdev = fprint_device_new (device); - /* We cannot continue to iterate at this point, but we don't need to either */ - break; - } + g_signal_connect (G_OBJECT (rdev), "notify::in-use", + G_CALLBACK (fprint_manager_in_use_notified), manager); - /* The device that disappeared might have been in-use. - * Do we need to do anything else in this case to clean up more gracefully? */ - fprint_manager_in_use_notified (NULL, NULL, manager); + path = get_device_path (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 -fprint_manager_init (FprintManager *manager) +device_removed_cb (FprintManager *manager, FpDevice *device, FpContext *context) { - FprintManagerPrivate *priv = FPRINT_MANAGER_GET_PRIVATE (manager); - GPtrArray *devices; + FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); - priv->context = fp_context_new (); + g_autolist (FprintDBusObjectSkeleton) objects = NULL; + GList *item; - /* And register the signals for initial enumeration and hotplug. */ - g_signal_connect_object (priv->context, - "device-added", - (GCallback) device_added_cb, - manager, - G_CONNECT_SWAPPED); + objects = g_dbus_object_manager_get_objects (priv->object_manager); - g_signal_connect_object (priv->context, - "device-removed", - (GCallback) device_removed_cb, - manager, - G_CONNECT_SWAPPED); + for (item = objects; item; item = item->next) + { + g_autoptr(FprintDevice) rdev = NULL; + g_autoptr(FpDevice) dev = NULL; + FprintDBusObjectSkeleton *object = item->data; - /* Prepare everthing by enumerating all devices. */ - fp_context_enumerate (priv->context); + rdev = fprint_dbus_object_skeleton_get_device (object); + g_object_get (rdev, "dev", &dev, NULL); + if (dev != device) + continue; - dbus_g_connection_register_g_object(fprintd_dbus_conn, - "/net/reactivated/Fprint/Manager", G_OBJECT(manager)); -} + g_dbus_object_manager_server_unexport ( + G_DBUS_OBJECT_MANAGER_SERVER (priv->object_manager), + g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (rdev))); -FprintManager *fprint_manager_new(gboolean no_timeout) -{ - FprintManagerPrivate *priv; - GObject *object; + g_signal_handlers_disconnect_by_data (rdev, manager); - object = g_object_new(FPRINT_TYPE_MANAGER, NULL); - priv = FPRINT_MANAGER_GET_PRIVATE (object); - priv->no_timeout = no_timeout; + /* We cannot continue to iterate at this point, but we don't need to either */ + break; + } - if (!priv->no_timeout) - priv->timeout_id = g_timeout_add_seconds (TIMEOUT, (GSourceFunc) fprint_manager_timeout_cb, object); + /* The device that disappeared might have been in-use. + * Do we need to do anything else in this case to clean up more gracefully? */ + fprint_manager_in_use_notified (NULL, NULL, manager); +} - return FPRINT_MANAGER (object); +static void +fprint_manager_constructed (GObject *object) +{ + FprintManager *manager = FPRINT_MANAGER (object); + 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 (); + + 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. */ + g_signal_connect_object (priv->context, + "device-added", + (GCallback) device_added_cb, + manager, + G_CONNECT_SWAPPED); + + g_signal_connect_object (priv->context, + "device-removed", + (GCallback) device_removed_cb, + manager, + G_CONNECT_SWAPPED); + + /* Prepare everything by enumerating all devices. + * This blocks the main loop until the existing devices are enumerated + */ + fp_context_enumerate (priv->context); + + G_OBJECT_CLASS (fprint_manager_parent_class)->constructed (object); } -static gboolean fprint_manager_get_devices(FprintManager *manager, - GPtrArray **devices, GError **error) +static void +fprint_manager_init (FprintManager *manager) { - FprintManagerPrivate *priv = FPRINT_MANAGER_GET_PRIVATE (manager); - GSList *elem; - GSList *l; - int num_open; - GPtrArray *devs; - - elem = g_slist_reverse(g_slist_copy(priv->dev_registry)); - num_open = g_slist_length(elem); - devs = g_ptr_array_sized_new(num_open); - - if (num_open > 0) { - for (l = elem; l != NULL; l = l->next) { - FprintDevice *rdev = l->data; - g_ptr_array_add(devs, get_device_path(rdev)); - } - } - - g_slist_free(elem); - - *devices = devs; - return TRUE; } -static gboolean fprint_manager_get_default_device(FprintManager *manager, - const char **device, GError **error) +FprintManager * +fprint_manager_new (GDBusConnection *connection, gboolean no_timeout) { - FprintManagerPrivate *priv = FPRINT_MANAGER_GET_PRIVATE (manager); - GSList *elem;; - int num_open; - - elem = priv->dev_registry; - num_open = g_slist_length(elem); - - if (num_open > 0) { - *device = get_device_path (g_slist_last (elem)->data); - return TRUE; - } else { - g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_NO_SUCH_DEVICE, - "No devices available"); - *device = NULL; - return FALSE; - } + FprintManagerPrivate *priv; + GObject *object; + + object = g_object_new (FPRINT_TYPE_MANAGER, "connection", connection, NULL); + priv = fprint_manager_get_instance_private (FPRINT_MANAGER (object)); + priv->no_timeout = no_timeout; + + if (!priv->no_timeout) + priv->timeout_id = g_timeout_add_seconds (TIMEOUT, (GSourceFunc) fprint_manager_timeout_cb, object); + + return FPRINT_MANAGER (object); } -GQuark fprint_error_quark(void) +static gboolean +fprint_manager_get_devices (FprintManager *manager, + GPtrArray **devices, GError **error) { - static GQuark quark = 0; - if (!quark) - quark = g_quark_from_static_string("fprintd-error-quark"); - return quark; + FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); + + g_autolist (FprintDBusObjectSkeleton) objects = NULL; + GList *l; + int num_open; + GPtrArray *devs; + + objects = g_dbus_object_manager_get_objects (priv->object_manager); + objects = g_list_reverse (objects); + + num_open = g_list_length (objects); + devs = g_ptr_array_sized_new (num_open); + + if (num_open > 0) + { + for (l = objects; l != NULL; l = l->next) + { + g_autoptr(FprintDevice) rdev = NULL; + FprintDBusObjectSkeleton *object = l->data; + const char *path; + + 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; + return TRUE; } -#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } -GType -fprint_error_get_type (void) +static gboolean +fprint_manager_get_default_device (FprintManager *manager, + const char **device, GError **error) { - 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; + FprintManagerPrivate *priv = fprint_manager_get_instance_private (manager); + + g_autolist (FprintDBusObjectSkeleton) objects = NULL; + int num_open; + + objects = g_dbus_object_manager_get_objects (priv->object_manager); + num_open = g_list_length (objects); + + if (num_open > 0) + { + 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; + } + else + { + g_set_error (error, FPRINT_ERROR, FPRINT_ERROR_NO_SUCH_DEVICE, + "No devices available"); + *device = NULL; + return FALSE; + } +} + +GQuark +fprint_error_quark (void) +{ + static volatile gsize quark = 0; + + if (g_once_init_enter (&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); + } + + g_once_init_leave (&quark, domain); + } + return (GQuark) quark; } diff --git a/src/manager.xml b/src/manager.xml index f4a38c7..2d0ba28 100644 --- a/src/manager.xml +++ b/src/manager.xml @@ -5,8 +5,6 @@ ]> - diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..1df6216 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,93 @@ +bash = find_program('bash') +dbus_interfaces = ['Manager', 'Device'] +dbus_interfaces_files = [] + +foreach interface_name: dbus_interfaces + interface = interface_name.to_lower() + interface_file = interface + '.xml' + dbus_interfaces_files += custom_target('dbus_interface_' + interface, + input: interface_file, + output: 'net.reactivated.Fprint.@0@.xml'.format(interface_name), + command: ['cp', '@INPUT@', '@OUTPUT@'], + install: true, + install_dir: dbus_interfaces_dir, + ) +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( + include_directories: [ + include_directories('..'), + ], + sources: [ + fprintd_enum_files, + fprintd_dbus_sources, + ], + dependencies: [ + glib_dep, + gio_dep, + gio_unix_dep, + gmodule_dep, + libfprint_dep, + polkit_gobject_dep, + ], + compile_args: [ + '-DG_LOG_DOMAIN="@0@"'.format(meson.project_name()), + '-DLOCALEDIR="@0@"'.format(localedir), + '-DPLUGINDIR="@0@"'.format(fprintd_plugindir), + ], +) + +libfprintd_private = static_library('fprintd-private', + sources: [ + 'device.c', + 'fprintd.h', + 'manager.c', + ], + dependencies: fprintd_deps, + gnu_symbol_visibility: 'hidden', +) + +fprintd = executable('fprintd', + sources: [ + 'file_storage.c', + 'file_storage.h', + 'fprintd.h', + 'main.c', + 'storage.h', + ], + dependencies: fprintd_deps, + link_with: libfprintd_private, + gnu_symbol_visibility: 'hidden', + install: true, + install_dir: fprintd_installdir, +) diff --git a/src/storage.h b/src/storage.h index 3819b87..ef25a84 100644 --- a/src/storage.h +++ b/src/storage.h @@ -6,21 +6,19 @@ * 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. * */ -#ifndef STORAGE_H - -#define STORAGE_H +#pragma once typedef int (*storage_print_data_save)(FpPrint *print); typedef int (*storage_print_data_load)(FpDevice *dev, @@ -30,25 +28,24 @@ typedef int (*storage_print_data_load)(FpDevice *dev, typedef int (*storage_print_data_delete)(FpDevice *dev, FpFinger finger, const char *username); -typedef GSList *(*storage_discover_prints)(FpDevice *dev, const char *username); -typedef GSList *(*storage_discover_users)(); +typedef GSList *(*storage_discover_prints)(FpDevice *dev, + const char *username); +typedef GSList *(*storage_discover_users)(void); typedef int (*storage_init)(void); typedef int (*storage_deinit)(void); -struct storage { - storage_init init; - storage_deinit deinit; - storage_print_data_save print_data_save; - storage_print_data_load print_data_load; - storage_print_data_delete print_data_delete; - storage_discover_prints discover_prints; - storage_discover_users discover_users; +struct storage +{ + storage_init init; + storage_deinit deinit; + storage_print_data_save print_data_save; + storage_print_data_load print_data_load; + storage_print_data_delete print_data_delete; + storage_discover_prints discover_prints; + storage_discover_users discover_users; }; typedef struct storage fp_storage; /* The currently setup store */ -fp_storage store; - -#endif - +extern fp_storage store; diff --git a/tests/LSAN-leaks-suppress.txt b/tests/LSAN-leaks-suppress.txt new file mode 100644 index 0000000..a5fc940 --- /dev/null +++ b/tests/LSAN-leaks-suppress.txt @@ -0,0 +1,4 @@ +leak:initialize_device +leak:usbi_alloc_device +leak:libusb-1.0.so.* +leak:PyMem_RawMalloc diff --git a/tests/dbusmock/fprintd.py b/tests/dbusmock/fprintd.py new file mode 100644 index 0000000..9eae4e4 --- /dev/null +++ b/tests/dbusmock/fprintd.py @@ -0,0 +1,439 @@ +# -*- coding: utf-8 -*- + +'''fprintd mock template + +This creates the expected methods and properties of the +net.reactivated.Fprint.Manager object (/net/reactivated/Fprint/Manager) +but no devices. +''' + +# 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__ = 'Bastien Nocera' +__email__ = 'hadess@hadess.net' +__copyright__ = '(c) 2020 Red Hat Inc.' +__license__ = 'LGPL 3+' + +import sys +from gi.repository import GLib +import dbus +import asyncio + +from dbusmock import MOCK_IFACE, mockobject + +BUS_NAME = 'net.reactivated.Fprint' +MAIN_OBJ = '/net/reactivated/Fprint/Manager' +SYSTEM_BUS = True +IS_OBJECT_MANAGER = False + +MAIN_IFACE = 'net.reactivated.Fprint.Manager' +MANAGER_MOCK_IFACE = 'net.reactivated.Fprint.Manager.Mock' + +DEVICE_IFACE = 'net.reactivated.Fprint.Device' +DEVICE_MOCK_IFACE = 'net.reactivated.Fprint.Device.Mock' + +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' +] + +VALID_VERIFY_STATUS = [ + 'verify-no-match', + 'verify-match', + 'verify-retry-scan', + 'verify-swipe-too-short', + 'verify-finger-not-centered', + 'verify-remove-and-retry', + 'verify-disconnected', + 'verify-unknown-error' +] + +VALID_ENROLL_STATUS = [ + 'enroll-completed', + 'enroll-failed', + 'enroll-stage-passed', + 'enroll-retry-scan', + 'enroll-swipe-too-short', + 'enroll-finger-not-centered', + 'enroll-remove-and-retry', + 'enroll-data-full', + 'enroll-disconnected', + 'enroll-unknown-error' +] + +def load(mock, parameters): + fprintd = mockobject.objects[MAIN_OBJ] + mock.last_device_id = 0 + fprintd.fingers = {} + mock.loop = asyncio.new_event_loop() + +@dbus.service.method(MAIN_IFACE, + in_signature='', out_signature='ao') +def GetDevices(self): + return [(k) for k in mockobject.objects.keys() if "/Device/" in k] + +@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] + +@dbus.service.method(MANAGER_MOCK_IFACE, + in_signature='sisb', out_signature='s') +def AddDevice(self, device_name, num_enroll_stages, scan_type, + has_identification=False): + '''Convenience method to add a fingerprint reader device + + You have to specify a device name, the number of enrollment + stages it would use (> 0) and the scan type, as a string + (either 'press' or 'swipe') + ''' + + if scan_type not in ['swipe', 'press']: + raise dbus.exceptions.DBusException( + 'Invalid scan_type \'%s\'.' % scan_type, + name='org.freedesktop.DBus.Error.InvalidArgs') + + if num_enroll_stages <= 0: + raise dbus.exceptions.DBusException( + 'Invalid num_enroll_stages \'%s\'.' % num_enroll_stages, + name='org.freedesktop.DBus.Error.InvalidArgs') + + self.last_device_id += 1 + path = '/net/reactivated/Fprint/Device/%d' % self.last_device_id + device_properties = { + 'name': dbus.String(device_name, variant_level=1), + 'num-enroll-stages': dbus.Int32(num_enroll_stages, variant_level=1), + 'scan-type': scan_type + } + + self.AddObject(path, + DEVICE_IFACE, + # Properties + device_properties, + # Methods + [ + ('ListEnrolledFingers', 's', 'as', ListEnrolledFingers), + ('DeleteEnrolledFingers', 's', '', DeleteEnrolledFingers), + ('DeleteEnrolledFingers2', '', '', DeleteEnrolledFingers2), + ('Claim', 's', '', Claim), + ('Release', '', '', Release), + ('VerifyStart', 's', '', VerifyStart), + ('VerifyStop', '', '', VerifyStop), + ('EnrollStart', 's', '', EnrollStart), + ('EnrollStop', '', '', EnrollStop) + ]) + + device = mockobject.objects[path] + device.fingers = {} + device.has_identification = has_identification + device.claimed_user = None + device.action = None + device.selected_finger = None + device.verify_script = [] + + 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, + in_signature='s', out_signature='as') +def ListEnrolledFingers(device, user): + if user in device.fingers: + return device.fingers[user] + raise dbus.exceptions.DBusException( + 'No enrolled prints in device %s for user %s' % (device.path, user), + name='net.reactivated.Fprint.Error.NoEnrolledPrints') + +@dbus.service.method(DEVICE_IFACE, + in_signature='s', out_signature='') +def DeleteEnrolledFingers(device, user): + device.fingers[user] = [] + +@dbus.service.method(DEVICE_IFACE, + in_signature='', out_signature='') +def DeleteEnrolledFingers2(device): + if not device.claimed_user: + raise dbus.exceptions.DBusException( + 'Device was not claimed before use', + name='net.reactivated.Fprint.Error.ClaimDevice') + device.fingers[device.claimed_user] = [] + +@dbus.service.method(DEVICE_IFACE, + in_signature='s', out_signature='') +def Claim(device, user): + if device.claimed_user: + raise dbus.exceptions.DBusException( + 'Device already in use by %s' % device.claimed_user, + name='net.reactivated.Fprint.Error.AlreadyInUse') + + device.claimed_user = user + +@dbus.service.method(DEVICE_IFACE, + in_signature='', out_signature='') +def Release(device): + if not device.claimed_user: + raise dbus.exceptions.DBusException( + 'Device was not claimed before use', + name='net.reactivated.Fprint.Error.ClaimDevice') + device.claimed_user = None + device.action = None + device.selected_finger = None + +def can_verify_finger(device, finger_name): + # We should already have checked that there are enrolled fingers + if finger_name == 'any': + return True + if finger_name in device.fingers[device.claimed_user]: + return True + return False + +def glib_sleep(timeout): + waiting = True + + 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', [ + result, + done + ]) + +@dbus.service.method(DEVICE_IFACE, + in_signature='s', out_signature='') +def VerifyStart(device, finger_name): + if not device.claimed_user: + raise dbus.exceptions.DBusException( + 'Device was not claimed before use', + name='net.reactivated.Fprint.Error.ClaimDevice') + if device.claimed_user not in device.fingers: + raise dbus.exceptions.DBusException( + 'No enrolled prints for user \'%s\'' % device.claimed_user, + name='net.reactivated.Fprint.Error.NoEnrolledPrints') + if not finger_name: + raise dbus.exceptions.DBusException( + 'Invalid empty finger_name.', + name='org.freedesktop.DBus.Error.InvalidArgs') + if not can_verify_finger(device, finger_name): + raise dbus.exceptions.DBusException( + 'Finger \'%s\' not enrolled.' % finger_name, + name='org.freedesktop.DBus.Error.Internal') + if device.action: + raise dbus.exceptions.DBusException( + 'Action \'%s\' already in progress' % device.action, + name='net.reactivated.Fprint.Error.AlreadyInUse') + device.action = 'verify' + + if finger_name == 'any' and not device.has_identification: + finger_name = device.fingers[device.claimed_user][0] + device.selected_finger = finger_name + # Needs to happen after method return + GLib.idle_add(device.EmitSignal, + DEVICE_IFACE, 'VerifyFingerSelected', 's', [ + device.selected_finger + ]) + + error = None + 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, + in_signature='sb', out_signature='') +def EmitVerifyStatus(device, result, done): + if (not device.action) or (device.action != 'verify'): + raise dbus.exceptions.DBusException( + 'Cannot send verify statuses when not verifying', + name='org.freedesktop.DBus.Error.InvalidArgs') + if result not in VALID_VERIFY_STATUS: + raise dbus.exceptions.DBusException( + 'Unknown verify status \'%s\'' % result, + name='org.freedesktop.DBus.Error.InvalidArgs') + device.EmitSignal(DEVICE_IFACE, 'VerifyStatus', 'sb', [ + result, + done + ]) + +@dbus.service.method(DEVICE_IFACE, + in_signature='', out_signature='') +def VerifyStop(device): + if device.action != 'verify': + raise dbus.exceptions.DBusException( + 'No verification to stop', + name='net.reactivated.Fprint.Error.NoActionInProgress') + device.action = None + device.selected_finger = None + +@dbus.service.method(DEVICE_IFACE, + in_signature='s', out_signature='') +def EnrollStart(device, finger_name): + if finger_name not in VALID_FINGER_NAMES: + raise dbus.exceptions.DBusException( + 'Invalid finger name \'%s\'' % finger_name, + name='net.reactivated.Fprint.Error.InvalidFingername') + if not device.claimed_user: + raise dbus.exceptions.DBusException( + 'Device was not claimed before use', + name='net.reactivated.Fprint.Error.ClaimDevice') + if device.action: + raise dbus.exceptions.DBusException( + 'Action \'%s\' already in progress' % device.action, + name='net.reactivated.Fprint.Error.AlreadyInUse') + device.action = 'enroll' + +@dbus.service.method(DEVICE_MOCK_IFACE, + in_signature='sb', out_signature='') +def EmitEnrollStatus(device, result, done): + if (not device.action) or (device.action != 'enroll'): + raise dbus.exceptions.DBusException( + 'Cannot send enroll statuses when not enrolling', + name='org.freedesktop.DBus.Error.InvalidArgs') + if result not in VALID_ENROLL_STATUS: + raise dbus.exceptions.DBusException( + 'Unknown enroll status \'%s\'' % result, + name='org.freedesktop.DBus.Error.InvalidArgs') + device.EmitSignal(DEVICE_IFACE, 'EnrollStatus', 'sb', [ + result, + done + ]) + # FIXME save enrolled finger? + +@dbus.service.method(DEVICE_IFACE, + in_signature='', out_signature='') +def EnrollStop(device): + if device.action != 'enroll': + raise dbus.exceptions.DBusException( + 'No enrollment to stop', + name='net.reactivated.Fprint.Error.NoActionInProgress') + device.action = None + +@dbus.service.method(DEVICE_MOCK_IFACE, + in_signature='sas', out_signature='') +def SetEnrolledFingers(device, user, fingers): + '''Convenience method to set the list of enrolled fingers. + + The device_path is the return value from AddDevice(), and the + array of fingers must only contain valid finger names. + + Returns nothing. + ''' + + for k in fingers: + if k not in VALID_FINGER_NAMES: + raise dbus.exceptions.DBusException( + 'Invalid finger name \'%s\'' % k, + name='org.freedesktop.DBus.Error.InvalidArgs') + + 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, + in_signature='a(sbi)', out_signature='') +def SetVerifyScript(device, script): + '''Convenience method to set the verification script. + + After VerifyStart is called, signal results will be sent in order after + a certain timeout declared in seconds. The array contains each + 'result' followed by the 'done' argument for VerifyStatus, and the + amount of time to wait before each signal is sent. + + Returns nothing. + ''' + + 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 diff --git a/tests/dbusmock/polkitd.py b/tests/dbusmock/polkitd.py new file mode 100644 index 0000000..acb447c --- /dev/null +++ b/tests/dbusmock/polkitd.py @@ -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] diff --git a/tests/fprintd.py b/tests/fprintd.py index 6069c9d..9e6fc7e 100755 --- a/tests/fprintd.py +++ b/tests/fprintd.py @@ -1,5 +1,6 @@ #! /usr/bin/env python3 # Copyright © 2017, 2019 Red Hat, Inc +# 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 @@ -16,6 +17,7 @@ # Authors: # Christian J. Kellner # Benjamin Berg +# Marco Trevisan import unittest import time @@ -25,12 +27,14 @@ import os.path import sys import tempfile import glob +import pwd import shutil import socket import struct import dbusmock import gi -from gi.repository import GLib, Gio +gi.require_version('FPrint', '2.0') +from gi.repository import GLib, Gio, FPrint import cairo try: @@ -47,6 +51,11 @@ def get_timeout(topic='default'): 'default': 20, 'daemon_start': 60 }, + 'asan': { + 'test': 120, + 'default': 6, + 'daemon_start': 10 + }, 'default': { 'test': 60, 'default': 3, @@ -54,8 +63,13 @@ def get_timeout(topic='default'): } } - valgrind = os.getenv('VALGRIND') - lut = vals['valgrind' if valgrind is not None else 'default'] + if os.getenv('VALGRIND') is not None: + lut = vals['valgrind'] + elif os.getenv('ADDRESS_SANITIZER') is not None: + lut = vals['asan'] + else: + lut = vals['default'] + if topic not in lut: raise ValueError('invalid topic') return lut[topic] @@ -120,7 +134,11 @@ class FPrintdTest(dbusmock.DBusTestCase): @classmethod def setUpClass(cls): + super().setUpClass() fprintd = None + cls._polkitd = None + + cls._has_hotplug = FPrint.Device.find_property("removed") is not None if 'FPRINT_BUILD_DIR' in os.environ: print('Testing local build') @@ -151,16 +169,20 @@ class FPrintdTest(dbusmock.DBusTestCase): cls.test_bus = Gio.TestDBus.new(Gio.TestDBusFlags.NONE) cls.test_bus.up() - try: - del os.environ['DBUS_SESSION_BUS_ADDRESS'] - except KeyError: - pass - os.environ['DBUS_SYSTEM_BUS_ADDRESS'] = cls.test_bus.get_bus_address() - cls.dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) + cls.test_bus.unset() + addr = cls.test_bus.get_bus_address() + os.environ['DBUS_SYSTEM_BUS_ADDRESS'] = addr + cls.dbus = Gio.DBusConnection.new_for_address_sync(addr, + Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION | + Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT, None, None) + assert cls.dbus.is_closed() == False @classmethod def tearDownClass(cls): + cls.dbus.close() cls.test_bus.down() + del cls.dbus + del cls.test_bus shutil.rmtree(cls.tmpdir) dbusmock.DBusTestCase.tearDownClass() @@ -169,7 +191,7 @@ class FPrintdTest(dbusmock.DBusTestCase): timeout = get_timeout('daemon_start') # seconds env = os.environ.copy() env['G_DEBUG'] = 'fatal-criticals' - env['STATE_DIRECTORY'] = self.state_dir + env['STATE_DIRECTORY'] = (self.state_dir + ':' + '/hopefully/a/state_dir_path/that/shouldnt/be/writable') env['RUNTIME_DIRECTORY'] = self.run_dir argv = [self.paths['daemon'], '-t'] @@ -184,7 +206,7 @@ class FPrintdTest(dbusmock.DBusTestCase): env=env, stdout=None, stderr=subprocess.STDOUT) - self.device = None + self.addCleanup(self.daemon_stop) timeout_count = timeout * 10 timeout_sleep = 0.1 @@ -232,14 +254,26 @@ class FPrintdTest(dbusmock.DBusTestCase): self.daemon.terminate() except OSError: pass - self.daemon.wait() + self.daemon.wait(timeout=2) + self.assertLess(self.daemon.returncode, 128) + self.assertGreaterEqual(self.daemon.returncode, 0) self.daemon = None - self.client = None def polkitd_start(self): + if self._polkitd: + return + + if 'POLKITD_MOCK_PATH' in os.environ: + polkitd_template = os.path.join(os.getenv('POLKITD_MOCK_PATH'), 'polkitd.py') + else: + polkitd_template = os.path.join(os.path.dirname(__file__), 'dbusmock/polkitd.py') + print ('Using template from %s' % polkitd_template) + self._polkitd, self._polkitd_obj = self.spawn_server_template( - 'polkitd', {}, stdout=DEVNULL) + polkitd_template, {}, stdout=subprocess.PIPE) + + return self._polkitd def polkitd_stop(self): if self._polkitd is None: @@ -247,51 +281,104 @@ class FPrintdTest(dbusmock.DBusTestCase): self._polkitd.terminate() self._polkitd.wait() - + def get_current_user(self): + return pwd.getpwuid(os.getuid()).pw_name def setUp(self): self.test_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.test_dir) self.state_dir = os.path.join(self.test_dir, 'state') self.run_dir = os.path.join(self.test_dir, 'run') + os.environ['FP_DRIVERS_WHITELIST'] = 'virtual_image' - def tearDown(self): - shutil.rmtree(self.test_dir) + def assertFprintError(self, fprint_error): + return self.assertRaisesRegex(GLib.Error, + '.*net\.reactivated\.Fprint\.Error\.{}.*'.format(fprint_error)) # From libfprint tests - def send_retry(self, retry_error=1): - # The default (1) is too-short + def send_retry(self, retry_error=FPrint.DeviceRetry.TOO_SHORT): with Connection(self.sockaddr) as con: con.sendall(struct.pack('ii', -1, retry_error)) # From libfprint tests + def send_error(self, error=FPrint.DeviceError.GENERAL): + with Connection(self.sockaddr) as con: + con.sendall(struct.pack('ii', -2, error)) + + # From libfprint tests + def send_remove(self): + with Connection(self.sockaddr) as con: + con.sendall(struct.pack('ii', -5, 0)) + + # From libfprint tests def send_image(self, image): img = self.prints[image] with Connection(self.sockaddr) as con: mem = img.get_data() mem = mem.tobytes() - assert len(mem) == img.get_width() * img.get_height() + self.assertEqual(len(mem), img.get_width() * img.get_height()) encoded_img = struct.pack('ii', img.get_width(), img.get_height()) encoded_img += mem con.sendall(encoded_img) - def test_enroll_verify_delete(self): + def call_device_method_async(self, method, *args): + """ add cancellable... """ + self.device.call(method, GLib.Variant(*args), + Gio.DBusCallFlags.NONE, -1, None, self._method_call_handler) + + def _method_call_handler(self, proxy, res): + try: + self._async_call_res = proxy.call_finish(res) + except Exception as e: + self._async_call_res = e + + def wait_for_device_reply(self): + self._async_call_res = None + while not self._async_call_res: + ctx.iteration(True) + + if isinstance(self._async_call_res, Exception): + raise self._async_call_res + + def gdbus_device_method_call_process(self, method, args=[]): + return subprocess.Popen([ + 'gdbus', + 'call', + '--system', + '--dest', + self.device.get_name(), + '--object-path', + self.device.get_object_path(), + '--method', + '{}.{}'.format(self.device.get_interface_name(), method), + ] + args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + + def call_device_method_from_other_client(self, method, args=[]): + try: + proc = self.gdbus_device_method_call_process(method, args) + proc.wait(timeout=5) + if proc.returncode != 0: + raise GLib.GError(proc.stdout.read()) + return proc.stdout.read() + except subprocess.TimeoutExpired as e: + raise GLib.GError(e.output) + + +class FPrintdVirtualDeviceBaseTest(FPrintdTest): + + def setUp(self): + super().setUp() + + self.manager = None + self.device = None self.polkitd_start() self.daemon_start() if self.device is None: - self.daemon_stop() - self.polkitd_stop() self.skipTest("Need virtual_image device to run the test") - def timeout_cb(*args): - # Note: With meson we could just rely on it to kill us - print("Test timed out, hard exiting") - sys.exit(1) - - timeout = GLib.timeout_add(get_timeout('test') * 1000, timeout_cb) - self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername', 'net.reactivated.fprint.device.enroll', 'net.reactivated.fprint.device.verify']) @@ -302,8 +389,9 @@ class FPrintdTest(dbusmock.DBusTestCase): self._abort = params[1] self._last_result = params[0] - if not self._abort and self._last_result == 'enroll-stage-passed': - self.send_image('whorl') + if not self._abort and self._last_result.startswith('enroll-'): + # Exit wait loop, onto next enroll state (if any) + self._abort = True elif self._abort: pass else: @@ -320,142 +408,1131 @@ class FPrintdTest(dbusmock.DBusTestCase): self._abort = True self._last_result = 'Unexpected signal' - signal_id = self.device.connect('g-signal', signal_cb) - - self.device.Claim('(s)', 'testuser') + self.g_signal_id = self.device.connect('g-signal', signal_cb) - self.device.EnrollStart('(s)', 'right-index-finger') + def tearDown(self): + self.polkitd_stop() + self.device.disconnect(self.g_signal_id) + self.device = None + self.manager = None - self.send_image('whorl') + super().tearDown() + def wait_for_result(self, expected=None): self._abort = False while not self._abort: ctx.iteration(True) - assert self._last_result == 'enroll-completed' + self.assertTrue(self._abort) + self._abort = False + + if expected is not None: + self.assertEqual(self._last_result, expected) + + def enroll_image(self, img, finger='right-index-finger', expected_result='enroll-completed'): + self.device.EnrollStart('(s)', finger) + + stages = self.device.get_cached_property('num-enroll-stages').unpack() + for stage in range(stages): + self.send_image(img) + if stage < stages - 1: + self.wait_for_result('enroll-stage-passed') + else: + self.wait_for_result(expected_result) self.device.EnrollStop() + self.assertEqual(self._last_result, expected_result) + + def enroll_multiple_images(self, images_override={}, return_index=-1): + enroll_map = { + 'left-thumb': 'whorl', + 'right-index-finger': 'arch', + 'left-little-finger': 'loop-right', + } + enroll_map.update(images_override) + + for finger, print in enroll_map.items(): + self.enroll_image(print, finger=finger) + + enrolled = self.device.ListEnrolledFingers('(s)', 'testuser') + self.assertCountEqual(enroll_map.keys(), enrolled) + + if return_index >= 0: + return enroll_map[enrolled[return_index]] + + return (enrolled, enroll_map) + + +class FPrintdManagerTests(FPrintdVirtualDeviceBaseTest): + + def setUp(self): + super().setUp() + self._polkitd_obj.SetAllowed(['']) + + def test_manager_get_devices(self): + self.assertListEqual(self.manager.GetDevices(), + [ self.device.get_object_path() ]) + + def test_manager_get_default_device(self): + self.assertEqual(self.manager.GetDefaultDevice(), + self.device.get_object_path()) + + +class FPrintdManagerPreStartTests(FPrintdTest): + + def test_manager_get_no_devices(self): + os.environ['FP_DRIVERS_WHITELIST'] = 'hopefully_no_existing_driver' + self.daemon_start() + self.assertListEqual(self.manager.GetDevices(), []) + + def test_manager_get_no_default_device(self): + os.environ['FP_DRIVERS_WHITELIST'] = 'hopefully_no_existing_driver' + self.daemon_start() + + with self.assertFprintError('NoSuchDevice'): + self.manager.GetDefaultDevice() + + def test_manager_get_devices_on_name_appeared(self): + self._appeared_name = None + + def on_name_appeared(connection, name, name_owner): + self._appeared_name = name + + def on_name_vanished(connection, name): + self._appeared_name = 'NAME_VANISHED' + + id = Gio.bus_watch_name_on_connection(self.dbus, + 'net.reactivated.Fprint', Gio.BusNameWatcherFlags.NONE, + on_name_appeared, on_name_vanished) + self.addCleanup(Gio.bus_unwatch_name, id) + + self.daemon_start() + while not self._appeared_name: + ctx.iteration(True) + + self.assertEqual(self._appeared_name, 'net.reactivated.Fprint') + + try: + appeared_device = self.dbus.call_sync( + 'net.reactivated.Fprint', + '/net/reactivated/Fprint/Manager', + 'net.reactivated.Fprint.Manager', + 'GetDefaultDevice', None, None, + Gio.DBusCallFlags.NO_AUTO_START, 500, None) + except GLib.GError as e: + if 'net.reactivated.Fprint.Error.NoSuchDevice' in e.message: + self.skipTest("Need virtual_image device to run the test") + raise(e) + + self.assertIsNotNone(appeared_device) + [dev_path] = appeared_device + self.assertTrue(dev_path.startswith('/net/reactivated/Fprint/Device/')) + + +class FPrintdVirtualDeviceTest(FPrintdVirtualDeviceBaseTest): + + def test_name_property(self): + self.assertEqual(self.device.get_cached_property('name').unpack(), + 'Virtual image device for debugging') + + def test_enroll_stages_property(self): + self.assertEqual(self.device.get_cached_property('num-enroll-stages').unpack(), 5) + + def test_scan_type(self): + self.assertEqual(self.device.get_cached_property('scan-type').unpack(), + 'swipe') + + def test_allowed_claim_release_enroll(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.enroll']) + self.device.Claim('(s)', 'testuser') + self.device.Release() + + def test_allowed_claim_release_verify(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.verify']) + self.device.Claim('(s)', 'testuser') + self.device.Release() + + def test_allowed_claim_current_user(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll']) + self.device.Claim('(s)', '') + self.device.Release() + + self.device.Claim('(s)', self.get_current_user()) + self.device.Release() + + def test_allowed_list_enrolled_fingers_empty_user(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll']) + self.device.Claim('(s)', '') + self.enroll_image('whorl', finger='left-thumb') + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify']) + + self.assertEqual(self.device.ListEnrolledFingers('(s)', ''), ['left-thumb']) + self.assertEqual(self.device.ListEnrolledFingers('(s)', self.get_current_user()), ['left-thumb']) + + def test_allowed_list_enrolled_fingers_current_user(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll']) + self.device.Claim('(s)', self.get_current_user()) + self.enroll_image('whorl', finger='right-thumb') + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify']) + + self.assertEqual(self.device.ListEnrolledFingers('(s)', ''), ['right-thumb']) + self.assertEqual(self.device.ListEnrolledFingers('(s)', self.get_current_user()), ['right-thumb']) + + def test_unallowed_claim(self): + self._polkitd_obj.SetAllowed(['']) + + with self.assertFprintError('PermissionDenied'): + self.device.Claim('(s)', 'testuser') + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername']) + + with self.assertFprintError('PermissionDenied'): + self.device.Claim('(s)', 'testuser') + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll']) + + with self.assertFprintError('PermissionDenied'): + self.device.Claim('(s)', 'testuser') + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify']) + + with self.assertFprintError('PermissionDenied'): + self.device.Claim('(s)', 'testuser') + + def test_unallowed_enroll_with_verify_claim(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify']) + self.device.Claim('(s)', '') + + with self.assertFprintError('PermissionDenied'): + self.enroll_image('whorl', finger='right-thumb') + + def test_unallowed_delete_with_verify_claim(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify']) + self.device.Claim('(s)', '') + + with self.assertFprintError('PermissionDenied'): + self.device.DeleteEnrolledFingers('(s)', 'testuser') + + def test_unallowed_delete2_with_verify_claim(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify']) + self.device.Claim('(s)', '') - assert os.path.exists(os.path.join(self.state_dir, 'testuser/virtual_image/0/7')) + with self.assertFprintError('PermissionDenied'): + self.device.DeleteEnrolledFingers2() + + def test_unallowed_verify_with_enroll_claim(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll']) + self.device.Claim('(s)', '') + + with self.assertFprintError('PermissionDenied'): + self.device.VerifyStart('(s)', 'any') + + def test_unallowed_claim_current_user(self): + self._polkitd_obj.SetAllowed(['']) + + with self.assertFprintError('PermissionDenied'): + self.device.Claim('(s)', '') + + with self.assertFprintError('PermissionDenied'): + self.device.Claim('(s)', self.get_current_user()) + + def test_multiple_claims(self): + self.device.Claim('(s)', 'testuser') + + with self.assertFprintError('AlreadyInUse'): + self.device.Claim('(s)', 'testuser') + + self.device.Release() + + def test_always_allowed_release(self): + self.device.Claim('(s)', 'testuser') + + self._polkitd_obj.SetAllowed(['']) + + self.device.Release() + + def test_unclaimed_release(self): + with self.assertFprintError('ClaimDevice'): + self.device.Release() + + def test_unclaimed_verify_start(self): + with self.assertFprintError('ClaimDevice'): + self.device.VerifyStart('(s)', 'any') + + def test_unclaimed_verify_stop(self): + with self.assertFprintError('ClaimDevice'): + self.device.VerifyStop() + + def test_unclaimed_enroll_start(self): + with self.assertFprintError('ClaimDevice'): + self.device.EnrollStart('(s)', 'left-index-finger') + + def test_unclaimed_enroll_stop(self): + with self.assertFprintError('ClaimDevice'): + self.device.EnrollStop() + + def test_unclaimed_delete_enrolled_fingers(self): + self.device.DeleteEnrolledFingers('(s)', 'testuser') + + def test_unclaimed_delete_enrolled_fingers2(self): + with self.assertFprintError('ClaimDevice'): + self.device.DeleteEnrolledFingers2() + + def test_unclaimed_list_enrolled_fingers(self): + with self.assertFprintError('NoEnrolledPrints'): + self.device.ListEnrolledFingers('(s)', 'testuser') + + def test_claim_device_open_fail(self): + os.rename(self.tmpdir, self.tmpdir + '-moved') + self.addCleanup(os.rename, self.tmpdir + '-moved', self.tmpdir) + + with self.assertFprintError('Internal'): + self.device.Claim('(s)', 'testuser') + + def test_claim_from_other_client_is_released_when_vanished(self): + self.call_device_method_from_other_client('Claim', ['testuser']) + time.sleep(1) + self.device.Claim('(s)', 'testuser') + self.device.Release() + + def test_claim_disconnect(self): + addr = os.environ['DBUS_SYSTEM_BUS_ADDRESS'] + + # Get a separat bus connection + dbus = Gio.DBusConnection.new_for_address_sync(addr, + Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION | + Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT, None, None) + assert dbus.is_closed() == False + + + dev_path = self.device.get_object_path() + dev = Gio.DBusProxy.new_sync(dbus, + Gio.DBusProxyFlags.DO_NOT_AUTO_START, + None, + 'net.reactivated.Fprint', + dev_path, + 'net.reactivated.Fprint.Device', + None) + + def call_done(obj, result, user_data): + # Ignore the callback (should be an error) + pass + + # Do an async call to claim and immediately close + dev.Claim('(s)', 'testuser', result_handler=call_done) + + # Ensure the call is on the wire, then close immediately + dbus.flush_sync() + dbus.close_sync() + + time.sleep(1) + + def test_removal_during_enroll(self): + if not self._has_hotplug: + self.skipTest("libfprint is too old for hotplug") + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.enroll']) + self.device.Claim('(s)', 'testuser') + self.device.EnrollStart('(s)', 'left-index-finger') + + # Now remove the device while we are enrolling, which will cause an error + self.send_remove() + self.wait_for_result(expected='enroll-unknown-error') + + # The device will still be there now until it is released + devices = self.manager.GetDevices() + self.assertIn(self.device.get_object_path(), devices) + with self.assertFprintError('Internal'): + self.device.Release() + + # And now it will be gone + devices = self.manager.GetDevices() + self.assertNotIn(self.device.get_object_path(), devices) + + +class FPrintdVirtualDeviceClaimedTest(FPrintdVirtualDeviceBaseTest): + + def setUp(self): + super().setUp() + self.device.Claim('(s)', 'testuser') + + def tearDown(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll']) + try: + self.device.Release() + except GLib.GError as e: + if not 'net.reactivated.Fprint.Error.ClaimDevice' in e.message: + raise(e) + super().tearDown() + + def test_wrong_finger_enroll_start(self): + with self.assertFprintError('InvalidFingername'): + self.device.EnrollStart('(s)', 'any') + + def test_verify_with_no_enrolled_prints(self): + with self.assertFprintError('NoEnrolledPrints'): + self.device.VerifyStart('(s)', 'any') + + def test_enroll_verify_list_delete(self): + # This test can trigger a race in older libfprint, only run if we have + # hotplug support, which coincides with the fixed release. + if not self._has_hotplug: + self.skipTest("libfprint is too old for hotplug") + + with self.assertFprintError('NoEnrolledPrints'): + self.device.ListEnrolledFingers('(s)', 'testuser') + + with self.assertFprintError('NoEnrolledPrints'): + self.device.ListEnrolledFingers('(s)', 'nottestuser') + + self.enroll_image('whorl') + + self.assertTrue(os.path.exists(os.path.join(self.state_dir, 'testuser/virtual_image/0/7'))) + + with self.assertFprintError('NoEnrolledPrints'): + self.device.ListEnrolledFingers('(s)', 'nottestuser') + + self.assertEqual(self.device.ListEnrolledFingers('(s)', 'testuser'), ['right-index-finger']) # Finger is enrolled, try to verify it self.device.VerifyStart('(s)', 'any') # Try a wrong print; will stop verification self.send_image('tented_arch') - self._abort = False - while not self._abort: - ctx.iteration(True) - assert self._verify_stopped == True - assert self._last_result == 'verify-no-match' + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-no-match') self.device.VerifyStop() self.device.VerifyStart('(s)', 'any') # Send a retry error (swipe too short); will not stop verification self.send_retry() - self._abort = False - while not self._abort: - ctx.iteration(True) - assert self._verify_stopped == False - assert self._last_result == 'verify-swipe-too-short' + self.wait_for_result() + self.assertFalse(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-swipe-too-short') # Try the correct print; will stop verification self.send_image('whorl') - self._abort = False - while not self._abort: - ctx.iteration(True) - assert self._verify_stopped == True - assert self._last_result == 'verify-match' + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-match') + self.device.VerifyStop() + self.assertEqual(self.device.ListEnrolledFingers('(s)', 'testuser'), ['right-index-finger']) # And delete the print(s) again self.device.DeleteEnrolledFingers('(s)', 'testuser') - assert not os.path.exists(os.path.join(self.state_dir, 'testuser/virtual_image/0/7')) + self.assertFalse(os.path.exists(os.path.join(self.state_dir, 'testuser/virtual_image/0/7'))) + + with self.assertFprintError('NoEnrolledPrints'): + self.device.ListEnrolledFingers('(s)', 'testuser') + + def test_enroll_delete2(self): + self.enroll_image('whorl') + + self.assertTrue(os.path.exists(os.path.join(self.state_dir, 'testuser/virtual_image/0/7'))) + + # And delete the print(s) again using the new API + self.device.DeleteEnrolledFingers2() + + self.assertFalse(os.path.exists(os.path.join(self.state_dir, 'testuser/virtual_image/0/7'))) + + def test_enroll_invalid_storage_dir(self): + # Directory wil not exist yet + os.makedirs(self.state_dir, mode=0o500) + self.addCleanup(os.chmod, self.state_dir, mode=0o700) + + try: + os.open(os.path.join(self.state_dir, "testfile"), os.O_CREAT | os.O_WRONLY) + self.skipTest('Permissions aren\'t respected (CI environment?)') + except PermissionError: + pass + + self.enroll_image('whorl', expected_result='enroll-failed') - GLib.source_remove(timeout) + def test_verify_invalid_storage_dir(self): + self.enroll_image('whorl') + os.chmod(self.state_dir, mode=0o000) + self.addCleanup(os.chmod, self.state_dir, mode=0o700) - self.device.disconnect(signal_id) + try: + os.open(os.path.join(self.state_dir, "testfile"), os.O_CREAT | os.O_WRONLY) + self.skipTest('Permissions aren\'t respected (CI environment?)') + except PermissionError: + pass + + with self.assertFprintError('NoEnrolledPrints'): + self.device.VerifyStart('(s)', 'any') + + def test_enroll_stop_cancels(self): + self.device.EnrollStart('(s)', 'left-index-finger') + self.device.EnrollStop() + self.wait_for_result(expected='enroll-failed') + + def test_verify_stop_cancels(self): + self.enroll_image('whorl') + self.device.VerifyStart('(s)', 'any') + self.device.VerifyStop() + self.wait_for_result(expected='verify-no-match') + + def test_verify_finger_stop_cancels(self): + self.enroll_image('whorl', finger='left-thumb') + self.device.VerifyStart('(s)', 'left-thumb') + self.device.VerifyStop() + + def test_busy_device_release_on_enroll(self): + self.device.EnrollStart('(s)', 'left-index-finger') self.device.Release() - self.daemon_stop() - self.polkitd_stop() + self.wait_for_result(expected='enroll-failed') - def test_enroll_delete2(self): - self.polkitd_start() - self.daemon_start() + def test_busy_device_release_on_verify(self): + self.enroll_image('whorl', finger='left-index-finger') + self.device.VerifyStart('(s)', 'any') - if self.device is None: - self.daemon_stop() - self.polkitd_stop() - self.skipTest("Need virtual_image device to run the test") + self.device.Release() + self.wait_for_result(expected='verify-no-match') - def timeout_cb(*args): - # Note: With meson we could just rely on it to kill us - print("Test timed out, hard exiting") - sys.exit(1) + def test_busy_device_release_on_verify_finger(self): + self.enroll_image('whorl', finger='left-middle-finger') + self.device.VerifyStart('(s)', 'left-middle-finger') - timeout = GLib.timeout_add(get_timeout('test') * 1000, timeout_cb) + self.device.Release() + self.wait_for_result(expected='verify-no-match') - self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername', - 'net.reactivated.fprint.device.enroll', - 'net.reactivated.fprint.device.verify']) + def test_enroll_stop_not_started(self): + with self.assertFprintError('NoActionInProgress'): + self.device.EnrollStop() - def signal_cb(proxy, sender, signal, params): - print(signal, params) - if signal == 'EnrollStatus': - self._abort = params[1] - self._last_result = params[0] + def test_verify_stop_not_started(self): + with self.assertFprintError('NoActionInProgress'): + self.device.VerifyStop() - if not self._abort and self._last_result == 'enroll-stage-passed': - self.send_image('whorl') - elif self._abort: - pass - else: - self._abort = True - self._last_result = 'Unexpected signal values' - print('Unexpected signal values') - elif signal == 'VerifyFingerSelected': - pass - elif signal == 'VerifyStatus': - self._abort = True - self._last_result = params[0] - self._verify_stopped = params[1] - else: - self._abort = True - self._last_result = 'Unexpected signal' + def test_verify_finger_match(self): + self.enroll_image('whorl', finger='left-thumb') + self.device.VerifyStart('(s)', 'left-thumb') + self.send_image('whorl') + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-match') + self.device.VerifyStop() - signal_id = self.device.connect('g-signal', signal_cb) + def test_verify_finger_no_match(self): + self.enroll_image('whorl', finger='left-thumb') + self.device.VerifyStart('(s)', 'left-thumb') + self.send_image('tented_arch') + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-no-match') + self.device.VerifyStop() - self.device.Claim('(s)', 'testuser') + def test_verify_finger_no_match_restart(self): + self.enroll_image('whorl', finger='left-thumb') + self.device.VerifyStart('(s)', 'left-thumb') + self.send_image('tented_arch') + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-no-match') + self.device.VerifyStop() - self.device.EnrollStart('(s)', 'right-index-finger') + # Immediately starting again after a no-match must work + self.device.VerifyStart('(s)', 'left-thumb') + self.send_image('whorl') + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-match') + self.device.VerifyStop() + def test_verify_wrong_finger_match(self): + self.enroll_image('whorl', finger='left-thumb') + self.device.VerifyStart('(s)', 'left-toe') self.send_image('whorl') + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-match') + self.device.VerifyStop() + + def test_verify_wrong_finger_no_match(self): + self.enroll_image('whorl', finger='right-thumb') + self.device.VerifyStart('(s)', 'right-toe') + self.send_image('tented_arch') + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-no-match') + self.device.VerifyStop() + + def test_verify_any_finger_match(self): + second_image = self.enroll_multiple_images(return_index=1) + self.device.VerifyStart('(s)', 'any') + self.send_image(second_image) + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-match') + self.device.VerifyStop() + + def test_verify_any_finger_no_match(self): + enrolled, _map = self.enroll_multiple_images() + verify_image = 'tented_arch' + self.assertNotIn(verify_image, enrolled) + self.device.VerifyStart('(s)', 'any') + self.send_image(verify_image) + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-no-match') + self.device.VerifyStop() + + def test_verify_finger_not_enrolled(self): + self.enroll_image('whorl', finger='left-thumb') + with self.assertFprintError('NoEnrolledPrints'): + self.device.VerifyStart('(s)', 'right-thumb') + + def test_unallowed_enroll_start(self): + self._polkitd_obj.SetAllowed(['']) + + with self.assertFprintError('PermissionDenied'): + self.device.EnrollStart('(s)', 'right-index-finger') + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.enroll']) + self.enroll_image('whorl') + + def test_always_allowed_enroll_stop(self): + self.device.EnrollStart('(s)', 'right-index-finger') + + self._polkitd_obj.SetAllowed(['']) + + self.device.EnrollStop() + + def test_unallowed_verify_start(self): + self._polkitd_obj.SetAllowed(['']) + + with self.assertFprintError('PermissionDenied'): + self.device.VerifyStart('(s)', 'any') + + def test_always_allowed_verify_stop(self): + self.enroll_image('whorl') + self.device.VerifyStart('(s)', 'any') + + self._polkitd_obj.SetAllowed(['']) + self.device.VerifyStop() + + def test_list_enrolled_fingers_current_user(self): + self.enroll_image('whorl') + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.verify']) + + with self.assertFprintError('NoEnrolledPrints'): + self.device.ListEnrolledFingers('(s)', '') + + with self.assertFprintError('NoEnrolledPrints'): + self.device.ListEnrolledFingers('(s)', self.get_current_user()) + + def test_unallowed_list_enrolled_fingers(self): + self.enroll_image('whorl') + + self._polkitd_obj.SetAllowed(['']) + with self.assertFprintError('PermissionDenied'): + self.device.ListEnrolledFingers('(s)', 'testuser') + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername']) + with self.assertFprintError('PermissionDenied'): + self.device.ListEnrolledFingers('(s)', 'testuser') + + def test_unallowed_list_enrolled_fingers_current_user(self): + self.enroll_image('whorl') + self._polkitd_obj.SetAllowed(['']) + with self.assertFprintError('PermissionDenied'): + self.device.ListEnrolledFingers('(s)', '') + + with self.assertFprintError('PermissionDenied'): + self.device.ListEnrolledFingers('(s)', self.get_current_user()) + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername']) + with self.assertFprintError('PermissionDenied'): + self.device.ListEnrolledFingers('(s)', '') + + with self.assertFprintError('PermissionDenied'): + self.device.ListEnrolledFingers('(s)', self.get_current_user()) + + def test_unallowed_delete_enrolled_fingers(self): + self.enroll_image('whorl') + + self._polkitd_obj.SetAllowed(['']) + with self.assertFprintError('PermissionDenied'): + self.device.DeleteEnrolledFingers('(s)', 'testuser') + + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername']) + with self.assertFprintError('PermissionDenied'): + self.device.DeleteEnrolledFingers('(s)', 'testuser') + + def test_unallowed_delete_enrolled_fingers2(self): + self.enroll_image('whorl') + + self._polkitd_obj.SetAllowed(['']) + with self.assertFprintError('PermissionDenied'): + self.device.DeleteEnrolledFingers2() + + def test_delete_enrolled_fingers_from_other_client(self): + with self.assertFprintError('AlreadyInUse'): + self.call_device_method_from_other_client('DeleteEnrolledFingers', ['testuser']) + + def test_release_from_other_client(self): + with self.assertFprintError('AlreadyInUse'): + self.call_device_method_from_other_client('Release') + + def test_enroll_start_from_other_client(self): + with self.assertFprintError('AlreadyInUse'): + self.call_device_method_from_other_client('EnrollStart', ['left-index-finger']) + + def test_verify_start_from_other_client(self): + with self.assertFprintError('AlreadyInUse'): + self.call_device_method_from_other_client('VerifyStart', ['any']) + + def test_verify_start_finger_from_other_client(self): + with self.assertFprintError('AlreadyInUse'): + self.call_device_method_from_other_client('VerifyStart', ['left-thumb']) + + +class FPrintdVirtualDeviceEnrollTests(FPrintdVirtualDeviceBaseTest): + + def setUp(self): + super().setUp() self._abort = False - while not self._abort: - ctx.iteration(True) + self.device.Claim('(s)', 'testuser') + self.device.EnrollStart('(s)', 'left-middle-finger') + + def tearDown(self): + self.device.EnrollStop() + self.device.Release() + super().tearDown() + + def assertEnrollRetry(self, device_error, expected_error): + self.send_retry(retry_error=device_error) + self.wait_for_result(expected=expected_error) + + def assertEnrollError(self, device_error, expected_error): + self.send_error(error=device_error) + self.wait_for_result(expected=expected_error) + + def test_enroll_retry_general(self): + self.assertEnrollRetry(FPrint.DeviceRetry.GENERAL, 'enroll-retry-scan') + + def test_enroll_retry_too_short(self): + self.assertEnrollRetry(FPrint.DeviceRetry.TOO_SHORT, 'enroll-swipe-too-short') + + def test_enroll_retry_remove_finger(self): + self.assertEnrollRetry(FPrint.DeviceRetry.REMOVE_FINGER, 'enroll-remove-and-retry') - assert self._last_result == 'enroll-completed' + def test_enroll_retry_center_finger(self): + self.assertEnrollRetry(FPrint.DeviceRetry.CENTER_FINGER, 'enroll-finger-not-centered') + def test_enroll_error_general(self): + self.assertEnrollError(FPrint.DeviceError.GENERAL, 'enroll-unknown-error') + + def test_enroll_error_not_supported(self): + self.assertEnrollError(FPrint.DeviceError.NOT_SUPPORTED, 'enroll-unknown-error') + + def test_enroll_error_not_open(self): + self.assertEnrollError(FPrint.DeviceError.NOT_OPEN, 'enroll-unknown-error') + + def test_enroll_error_already_open(self): + self.assertEnrollError(FPrint.DeviceError.ALREADY_OPEN, 'enroll-unknown-error') + + def test_enroll_error_busy(self): + self.assertEnrollError(FPrint.DeviceError.BUSY, 'enroll-unknown-error') + + def test_enroll_error_proto(self): + self.assertEnrollError(FPrint.DeviceError.PROTO, 'enroll-disconnected') + + def test_enroll_error_data_invalid(self): + self.assertEnrollError(FPrint.DeviceError.DATA_INVALID, 'enroll-unknown-error') + + def test_enroll_error_data_not_found(self): + self.assertEnrollError(FPrint.DeviceError.DATA_NOT_FOUND, 'enroll-unknown-error') + + def test_enroll_error_data_full(self): + self.assertEnrollError(FPrint.DeviceError.DATA_FULL, 'enroll-data-full') + + def test_enroll_start_during_enroll(self): + with self.assertFprintError('AlreadyInUse'): + self.device.EnrollStart('(s)', 'left-thumb') + + def test_verify_start_during_enroll(self): self.device.EnrollStop() + self.wait_for_result() + self.enroll_image('whorl') + self.device.EnrollStart('(s)', 'right-thumb') + with self.assertFprintError('AlreadyInUse'): + self.device.VerifyStart('(s)', 'any') - assert os.path.exists(os.path.join(self.state_dir, 'testuser/virtual_image/0/7')) + def test_verify_stop_during_enroll(self): + with self.assertFprintError('AlreadyInUse'): + self.device.VerifyStop() + + def test_enroll_stop_from_other_client(self): + with self.assertFprintError('AlreadyInUse'): + self.call_device_method_from_other_client('EnrollStop') - # And delete the print(s) again using the new API - self.device.DeleteEnrolledFingers2() - assert not os.path.exists(os.path.join(self.state_dir, 'testuser/virtual_image/0/7')) +class FPrintdVirtualDeviceVerificationTests(FPrintdVirtualDeviceBaseTest): - GLib.source_remove(timeout) + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.enroll_finger = 'left-middle-finger' + cls.verify_finger = cls.enroll_finger - self.device.disconnect(signal_id) + def setUp(self): + super().setUp() + self.device.Claim('(s)', 'testuser') + self.enroll_image('whorl', finger=self.enroll_finger) + self.device.VerifyStart('(s)', self.verify_finger) + def tearDown(self): + self.device.VerifyStop() self.device.Release() - self.daemon_stop() - self.polkitd_stop() + super().tearDown() + + def assertVerifyRetry(self, device_error, expected_error): + self.send_retry(retry_error=device_error) + self.wait_for_result() + self.assertFalse(self._verify_stopped) + self.assertEqual(self._last_result, expected_error) + + def assertVerifyError(self, device_error, expected_error): + self.send_error(error=device_error) + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, expected_error) + + def test_verify_retry_general(self): + self.assertVerifyRetry(FPrint.DeviceRetry.GENERAL, 'verify-retry-scan') + + def test_verify_retry_too_short(self): + self.assertVerifyRetry(FPrint.DeviceRetry.TOO_SHORT, 'verify-swipe-too-short') + + def test_verify_retry_remove_finger(self): + self.assertVerifyRetry(FPrint.DeviceRetry.REMOVE_FINGER, 'verify-remove-and-retry') + + def test_verify_retry_center_finger(self): + self.assertVerifyRetry(FPrint.DeviceRetry.CENTER_FINGER, 'verify-finger-not-centered') + + def test_verify_error_general(self): + self.assertVerifyError(FPrint.DeviceError.GENERAL, 'verify-unknown-error') + + def test_verify_error_not_supported(self): + self.assertVerifyError(FPrint.DeviceError.NOT_SUPPORTED, 'verify-unknown-error') + + def test_verify_error_not_open(self): + self.assertVerifyError(FPrint.DeviceError.NOT_OPEN, 'verify-unknown-error') + + def test_verify_error_already_open(self): + self.assertVerifyError(FPrint.DeviceError.ALREADY_OPEN, 'verify-unknown-error') + + def test_verify_error_busy(self): + self.assertVerifyError(FPrint.DeviceError.BUSY, 'verify-unknown-error') + + def test_verify_error_proto(self): + self.assertVerifyError(FPrint.DeviceError.PROTO, 'verify-disconnected') + + def test_verify_error_data_invalid(self): + self.assertVerifyError(FPrint.DeviceError.DATA_INVALID, 'verify-unknown-error') + + def test_verify_error_data_not_found(self): + self.assertVerifyError(FPrint.DeviceError.DATA_NOT_FOUND, 'verify-unknown-error') + + def test_verify_error_data_full(self): + self.assertVerifyError(FPrint.DeviceError.DATA_FULL, 'verify-unknown-error') + + def test_verify_start_during_verify(self): + with self.assertFprintError('AlreadyInUse'): + self.device.VerifyStart('(s)', self.verify_finger) + + def test_enroll_start_during_verify(self): + with self.assertFprintError('AlreadyInUse'): + self.device.EnrollStart('(s)', 'right-thumb') + + def test_enroll_stop_during_verify(self): + with self.assertFprintError('AlreadyInUse'): + self.device.EnrollStop() + + def test_verify_stop_from_other_client(self): + with self.assertFprintError('AlreadyInUse'): + self.call_device_method_from_other_client('VerifyStop') + + +class FPrintdVirtualDeviceIdentificationTests(FPrintdVirtualDeviceVerificationTests): + '''This class will just repeat the tests of FPrintdVirtualDeviceVerificationTests + but with 'any' finger parameter (leading to an identification, when possible + under the hood). + ''' + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.verify_finger = 'any' + + +class FPrindConcurrentPolkitRequestsTest(FPrintdVirtualDeviceBaseTest): + + def wait_for_hanging_clients(self): + while not self._polkitd_obj.HaveHangingCalls(): + pass + self.assertTrue(self._polkitd_obj.HaveHangingCalls()) + + def start_hanging_gdbus_claim(self, user='testuser'): + gdbus = self.gdbus_device_method_call_process('Claim', [user]) + self.assertIsNone(gdbus.poll()) + self.wait_for_hanging_clients() + self.addCleanup(gdbus.kill) + return gdbus + + def test_hanging_claim_does_not_block_new_claim_external_client(self): + self._polkitd_obj.SetAllowed([ + 'net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.enroll' ]) + self._polkitd_obj.SimulateHang(True) + self._polkitd_obj.SetDelay(0.5) + + gdbus = self.start_hanging_gdbus_claim() + + self._polkitd_obj.SimulateHang(False) + self.device.Claim('(s)', self.get_current_user()) + + self.assertIsNone(gdbus.poll()) + self._polkitd_obj.ReleaseHangingCalls() + + gdbus.wait() + with self.assertFprintError('AlreadyInUse'): + raise GLib.GError(gdbus.stdout.read()) + + self.device.Release() + + def test_hanging_claim_does_not_block_new_claim(self): + self._polkitd_obj.SetAllowed([ + 'net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.enroll' ]) + self._polkitd_obj.SimulateHang(True) + self._polkitd_obj.SetDelay(0.5) + + self.call_device_method_async('Claim', '(s)', ['']) + self.wait_for_hanging_clients() + + self._polkitd_obj.SimulateHang(False) + self.device.Claim('(s)', self.get_current_user()) + + self._polkitd_obj.ReleaseHangingCalls() + + with self.assertFprintError('AlreadyInUse'): + self.wait_for_device_reply() + + self.device.Release() + + def test_hanging_claim_enroll_does_not_block_new_claim(self): + self._polkitd_obj.SetAllowed([ + 'net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.enroll' ]) + self._polkitd_obj.SimulateHangActions([ + 'net.reactivated.fprint.device.enroll']) + self._polkitd_obj.SetDelay(0.5) + + gdbus = self.start_hanging_gdbus_claim() + + self._polkitd_obj.SimulateHangActions(['']) + self.device.Claim('(s)', self.get_current_user()) + + self.assertIsNone(gdbus.poll()) + self._polkitd_obj.ReleaseHangingCalls() + + gdbus.wait() + with self.assertFprintError('AlreadyInUse'): + raise GLib.GError(gdbus.stdout.read()) + + self.device.Release() + + def test_hanging_claim_does_not_block_new_release(self): + self._polkitd_obj.SetAllowed(['net.reactivated.fprint.device.setusername']) + self._polkitd_obj.SimulateHang(True) + + gdbus = self.gdbus_device_method_call_process('Claim', ['testuser']) + self.addCleanup(gdbus.kill) + + self.wait_for_hanging_clients() + with self.assertFprintError('ClaimDevice'): + self.device.Release() + + self.assertIsNone(gdbus.poll()) + + def test_hanging_claim_does_not_block_list(self): + self._polkitd_obj.SetAllowed([ + 'net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.enroll', + 'net.reactivated.fprint.device.verify']) + + self.device.Claim('(s)', '') + self.enroll_image('whorl', finger='left-thumb') + self.device.Release() + + self._polkitd_obj.SimulateHangActions([ + 'net.reactivated.fprint.device.setusername']) + + gdbus = self.start_hanging_gdbus_claim() + + self.assertEqual(self.device.ListEnrolledFingers('(s)', + self.get_current_user()), ['left-thumb']) + + self.assertIsNone(gdbus.poll()) + + def test_hanging_claim_can_proceed_when_released(self): + self._polkitd_obj.SetAllowed([ + 'net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.verify']) + + self._polkitd_obj.SimulateHangActions([ + 'net.reactivated.fprint.device.setusername']) + + gdbus = self.start_hanging_gdbus_claim() + + self._polkitd_obj.SimulateHangActions(['']) + self.device.Claim('(s)', 'testuser') + self.device.Release() + + self.assertIsNone(gdbus.poll()) + + self._polkitd_obj.ReleaseHangingCalls() + gdbus.wait() + + self.assertEqual(gdbus.returncode, 0) + + def test_hanging_claim_does_not_block_empty_list(self): + self._polkitd_obj.SetAllowed([ + 'net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.enroll', + 'net.reactivated.fprint.device.verify']) + + self._polkitd_obj.SimulateHangActions([ + 'net.reactivated.fprint.device.setusername']) + + gdbus = self.start_hanging_gdbus_claim() + + with self.assertFprintError('NoEnrolledPrints'): + self.device.ListEnrolledFingers('(s)', self.get_current_user()) + + self.assertIsNone(gdbus.poll()) + + def test_hanging_claim_does_not_block_verification(self): + self._polkitd_obj.SetAllowed([ + 'net.reactivated.fprint.device.setusername', + 'net.reactivated.fprint.device.enroll', + 'net.reactivated.fprint.device.verify']) + + self.device.Claim('(s)', '') + self.enroll_image('whorl', finger='left-thumb') + self.device.Release() + + self._polkitd_obj.SimulateHangActions([ + 'net.reactivated.fprint.device.setusername']) + + gdbus = self.start_hanging_gdbus_claim() + + self.device.Claim('(s)', '') + self.device.VerifyStart('(s)', 'any') + self.send_image('whorl') + self.wait_for_result() + self.assertTrue(self._verify_stopped) + self.assertEqual(self._last_result, 'verify-match') + self.device.VerifyStop() + self.device.Release() + + self.assertIsNone(gdbus.poll()) + + +class FPrintdUtilsTest(FPrintdVirtualDeviceBaseTest): + + @classmethod + def setUpClass(cls): + super().setUpClass() + utils = { + 'delete': None, + 'enroll': None, + 'list': None, + 'verify': None, + } + + for util in utils: + util_bin = 'fprintd-{}'.format(util) + if 'FPRINT_BUILD_DIR' in os.environ: + print('Testing local build') + build_dir = os.environ['FPRINT_BUILD_DIR'] + path = os.path.join(build_dir, '../utils', util_bin) + elif 'UNDER_JHBUILD' in os.environ: + print('Testing JHBuild version') + jhbuild_prefix = os.environ['JHBUILD_PREFIX'] + path = os.path.join(jhbuild_prefix, 'bin', util_bin) + else: + # Assume it is in path + utils[util] = util_bin + continue + + assert os.path.exists(path), 'failed to find {} in {}'.format(util, path) + utils[util] = path + + cls.utils = utils + cls.utils_proc = {} + + def util_start(self, name, args=[]): + env = os.environ.copy() + env['G_DEBUG'] = 'fatal-criticals' + env['STATE_DIRECTORY'] = self.state_dir + env['RUNTIME_DIRECTORY'] = self.run_dir + + argv = [self.utils[name]] + args + valgrind = os.getenv('VALGRIND') + if valgrind is not None: + argv.insert(0, 'valgrind') + argv.insert(1, '--leak-check=full') + if os.path.exists(valgrind): + argv.insert(2, '--suppressions=%s' % valgrind) + self.valgrind = True + self.utils_proc[name] = subprocess.Popen(argv, + env=env, + stdout=None, + stderr=subprocess.STDOUT) + self.addCleanup(self.utils_proc[name].wait) + self.addCleanup(self.utils_proc[name].terminate) + return self.utils_proc[name] + + def test_vanished_client_operation_is_cancelled(self): + self.device.Claim('(s)', self.get_current_user()) + self.enroll_image('whorl') + self.device.Release() + + verify = self.util_start('verify') + time.sleep(1) + verify.terminate() + self.assertLess(verify.wait(), 128) + time.sleep(1) + + self.device.Claim('(s)', self.get_current_user()) + self.device.Release() + + def test_already_claimed_same_user_delete_enrolled_fingers(self): + self.device.DeleteEnrolledFingers('(s)', 'testuser') + + def test_already_claimed_other_user_delete_enrolled_fingers(self): + self.device.DeleteEnrolledFingers('(s)', 'nottestuser') + + + +def list_tests(): + import unittest_inspector + return unittest_inspector.list_tests(sys.modules[__name__]) if __name__ == '__main__': if len(sys.argv) == 2 and sys.argv[1] == "list-tests": @@ -463,4 +1540,12 @@ if __name__ == '__main__': print("%s %s" % (machine, human), end="\n") sys.exit(0) - unittest.main(verbosity=2) + prog = unittest.main(verbosity=2, exit=False) + if prog.result.errors or prog.result.failures: + sys.exit(1) + + # Translate to skip error + if prog.result.testsRun == len(prog.result.skipped): + sys.exit(77) + + sys.exit(0) diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..495acbc --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,126 @@ +# 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 = [ + 'fprintd', + 'test_fprintd_utils', +] + +foreach t: tests + python_tests += [ + { + 'name': t, + 'file': files(meson.current_source_dir() / t + '.py')[0], + 'env': [ + 'G_DEBUG=fatal-criticals', + 'G_MESSAGES_DEBUG=all', + 'FPRINT_BUILD_DIR=' + meson.build_root() / 'src', + '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 + +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', + is_default: true, + env: test_envs, + timeout_multiplier: timeout_multiplier +) + +if not address_sanitizer and find_program('valgrind', required: false).found() + glib_share = glib_dep.get_pkgconfig_variable('prefix') / 'share' / glib_dep.name() + glib_suppressions = glib_share + '/valgrind/glib.supp' + add_test_setup('valgrind', + env: [ + 'G_SLICE=always-malloc', + 'VALGRIND=' + glib_suppressions, + ], + timeout_multiplier: 5 + ) +endif + diff --git a/tests/output_checker.py b/tests/output_checker.py new file mode 100644 index 0000000..50d40bc --- /dev/null +++ b/tests/output_checker.py @@ -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 . +# Authors: +# Benjamin Berg + +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 + diff --git a/tests/pam/meson.build b/tests/pam/meson.build new file mode 100644 index 0000000..dc0b071 --- /dev/null +++ b/tests/pam/meson.build @@ -0,0 +1,59 @@ +subdir('services') + +tests = [ + '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 + python_tests += [ + { + 'name': t, + 'file': files(meson.current_source_dir() / t + '.py')[0], + 'is_parallel': false, + 'env': [ + 'TOPBUILDDIR=' + meson.build_root(), + 'TOPSRCDIR=' + meson.source_root(), + 'LD_PRELOAD=' + ' '.join(pam_tests_ld_preload), + 'PAM_WRAPPER=1', + 'PAM_WRAPPER_DEBUGLEVEL=2', + 'PAM_WRAPPER_SERVICE_DIR=' + meson.current_build_dir() / 'services', + 'G_DEBUG=fatal-warnings', + ], + 'depends': [ + pam_fprintd, + pam_service_file, + ], + 'suite': ['PAM'], + } + ] +endforeach diff --git a/tests/pam/services/fprintd-pam-test.in b/tests/pam/services/fprintd-pam-test.in new file mode 100644 index 0000000..f3fb5c3 --- /dev/null +++ b/tests/pam/services/fprintd-pam-test.in @@ -0,0 +1 @@ +auth required @FPRINTDPAMPATH@ debug timeout=10 diff --git a/tests/pam/services/meson.build b/tests/pam/services/meson.build new file mode 100644 index 0000000..b9e66e1 --- /dev/null +++ b/tests/pam/services/meson.build @@ -0,0 +1,13 @@ +# Meson doesn't allow to have configure_file's as targets we depend on... Meh! +pam_service_file = custom_target('pam_test_service_file', + output: 'null', + command: 'true', + depends: pam_fprintd, + depend_files: configure_file( + input: 'fprintd-pam-test.in', + output: 'fprintd-pam-test', + configuration: configuration_data({ + 'FPRINTDPAMPATH': pam_fprintd.full_path(), + }), + ), +) diff --git a/tests/pam/test_pam_fprintd.py b/tests/pam/test_pam_fprintd.py new file mode 100755 index 0000000..e05ad4c --- /dev/null +++ b/tests/pam/test_pam_fprintd.py @@ -0,0 +1,298 @@ +#!/usr/bin/python3 + +# 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__ = 'Bastien Nocera' +__email__ = 'hadess@hadess.net' +__copyright__ = '(c) 2020 Red Hat Inc.' +__license__ = 'LGPL 3+' + +import tempfile +import unittest +import sys +import subprocess +import dbus +import dbusmock +import glob +import os +import shutil +import time +import pypamtest + +PAM_SUCCESS = 0 +PAM_AUTH_ERR = 7 +PAM_AUTHINFO_UNAVAIL = 9 +PAM_USER_UNKNOWN = 10 +PAM_MAXTRIES = 11 + +class TestPamFprintd(dbusmock.DBusTestCase): + '''Test pam_fprintd''' + + @classmethod + def start_monitor(klass): + '''Start dbus-monitor''' + + workdir = os.environ['TOPBUILDDIR'] + '/tests/pam/' + klass.monitor_log = open(os.path.join(workdir, 'dbus-monitor.log'), 'wb', buffering=0) + klass.monitor = subprocess.Popen(['dbus-monitor', '--monitor', '--system'], + stdout=klass.monitor_log, + stderr=subprocess.STDOUT) + + @classmethod + def stop_monitor(klass): + '''Stop dbus-monitor''' + + assert klass.monitor + klass.monitor.terminate() + klass.monitor.wait() + + klass.monitor_log.flush() + klass.monitor_log.close() + + @classmethod + def setUpClass(klass): + klass.start_system_bus() + klass.start_monitor() + klass.dbus_con = klass.get_dbus(True) + + template_path = './' + if 'TOPSRCDIR' in os.environ: + template_path = os.environ['TOPSRCDIR'] + '/tests/' + klass.template_name = template_path + 'dbusmock/fprintd.py' + print ('Using template from %s' % klass.template_name) + + @classmethod + def tearDownClass(klass): + 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): + (self.p_mock, self.obj_fprintd_manager) = self.spawn_server_template( + self.template_name, {}) + self.obj_fprintd_mock = dbus.Interface(self.obj_fprintd_manager, 'net.reactivated.Fprint.Manager.Mock') + + def tearDown(self): + self.p_mock.terminate() + self.p_mock.wait() + + def setup_device(self): + device_path = self.obj_fprintd_mock.AddDevice('FDO Trigger Finger Laser Reader', 3, 'swipe') + self.device_mock = self.dbus_con.get_object('net.reactivated.Fprint', device_path) + 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): + self.setup_device() + script = [ + ( 'verify-match', True, 2 ) + ] + self.device_mock.SetVerifyScript(script) + + tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_SUCCESS) + 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_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): + 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.SetEnrolledFingers('toto', ['left-middle-finger', 'right-middle-finger']) + script = [ + ( 'verify-match', True, 2 ) + ] + sandpaper_device_mock.SetVerifyScript(script) + + # Add a 2nd device + self.setup_device() + + tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_SUCCESS) + res = pypamtest.run_pamtest("toto", "fprintd-pam-test", [tc], [ 'unused' ]) + + self.assertRegex(res.info[0], r'Place your left middle finger on FDO Sandpaper Reader') + self.assertEqual(len(res.errors), 0) + + def test_pam_fprintd_last_try_auth(self): + self.setup_device() + script = [ + ( 'verify-no-match', True, 1 ), + ( 'verify-no-match', True, 1 ), + ( 'verify-match', True, 1 ), + ] + self.device_mock.SetVerifyScript(script) + + tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=PAM_SUCCESS) + 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), 2) + self.assertRegex(res.errors[0], r'Failed to match fingerprint') + self.assertRegex(res.errors[1], r'Failed to match fingerprint') + + def test_pam_fprintd_failed_auth(self): + self.setup_device() + script = [ + ( '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[1], 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): + self.setup_device() + + 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[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 'PAM_WRAPPER_SERVICE_DIR' not in os.environ: + print('Cannot run test without environment set correctly, run "meson test" instead') + sys.exit(1) + # set stream to sys.stderr to get debug output + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) diff --git a/tests/test_fprintd_utils.py b/tests/test_fprintd_utils.py new file mode 100755 index 0000000..7dd2003 --- /dev/null +++ b/tests/test_fprintd_utils.py @@ -0,0 +1,302 @@ +#!/usr/bin/python3 + +# 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__ = 'Bastien Nocera' +__email__ = 'hadess@hadess.net' +__copyright__ = '(c) 2020 Red Hat Inc.' +__license__ = 'LGPL 3+' + +import tempfile +import unittest +import sys +import subprocess +import dbus +import dbus.mainloop.glib +import dbusmock +import os +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) + +class TestFprintdUtilsBase(dbusmock.DBusTestCase): + '''Test fprintd utilities''' + + @classmethod + def setUpClass(klass): + klass.start_system_bus() + klass.dbus_con = klass.get_dbus(True) + klass.sleep_time = 0.5 + + template_path = './' + if 'TOPSRCDIR' in os.environ: + template_path = os.environ['TOPSRCDIR'] + '/tests/' + klass.template_name = template_path + 'dbusmock/fprintd.py' + print ('Using template from %s' % klass.template_name) + + klass.tools_prefix = '' + if 'FPRINT_BUILD_DIR' in os.environ: + klass.tools_prefix = os.environ['FPRINT_BUILD_DIR'] + '/../utils/' + print ('Using tools from %s' % klass.tools_prefix) + else: + print ('Using tools from $PATH') + + klass.wrapper_args = [] + klass.valgrind = False + if 'VALGRIND' in os.environ: + valgrind = os.environ['VALGRIND'] + if valgrind is not None: + klass.valgrind = True + klass.sleep_time *= 4 + klass.wrapper_args = ['valgrind', '--leak-check=full'] + if os.path.exists(valgrind): + klass.wrapper_args += ['--suppressions={}'.format(valgrind)] + + if 'ADDRESS_SANITIZER' in os.environ: + klass.sleep_time *= 2 + + def setUp(self): + super().setUp() + (self.p_mock, self.obj_fprintd_manager) = self.spawn_server_template( + self.template_name, {}) + # set log to nonblocking + self.obj_fprintd_mock = dbus.Interface(self.obj_fprintd_manager, 'net.reactivated.Fprint.Manager.Mock') + + def tearDown(self): + self.p_mock.terminate() + self.p_mock.wait() + super().tearDown() + + def setup_device(self): + self.device_path = self.obj_fprintd_mock.AddDevice( + 'FDO Trigger Finger Laser Reader', 3, 'swipe') + 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): + process, out = self.start_utility_process('enroll', ['-f', 'right-index-finger', 'toto']) + + out.check_line(rb'right-index-finger', 0) + + self.device_mock.EmitEnrollStatus('enroll-completed', True) + + out.check_line(rb'Enroll result: enroll-completed', self.sleep_time) + + 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): + 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() + + def start_verify_process(self, user='toto', finger=None, nowait=False): + args = [user] + if finger: + args += ['-f', finger] + + self.process, self.output = self.start_utility_process('verify', args) + if nowait: + return + + preamble = self.output.check_line(b'Verify started!') + + out = b''.join(preamble) + + 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) + time.sleep(self.sleep_time) + self.assertVerifyMatch(True) + + def test_fprintd_verify_enrolled_fingers(self): + for finger in self.enrolled_fingers: + 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): + script = [ + ( 'verify-match', True, 2 ) + ] + self.device_mock.SetVerifyScript(script) + time.sleep(2) + + self.start_verify_process() + time.sleep(2 + self.sleep_time) + self.assertVerifyMatch(True) + + def test_fprintd_multiple_verify_fails(self): + self.start_verify_process() + + self.start_verify_process(nowait=True) + self.output.check_line_re(rb'Device already in use by [A-z]+', timeout=self.sleep_time) + +if __name__ == '__main__': + # avoid writing to stderr + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) diff --git a/tests/unittest_inspector.py b/tests/unittest_inspector.py new file mode 100755 index 0000000..0d5d3a6 --- /dev/null +++ b/tests/unittest_inspector.py @@ -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 . +# Authors: +# Marco Trevisan + +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) diff --git a/update-transifex.sh b/update-transifex.sh new file mode 100755 index 0000000..33512a1 --- /dev/null +++ b/update-transifex.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +test -f .tx/config || exit 1 + +echo Pulling translations from Transifex +tx --root `dirname $0` pull --all --force --skip + +echo Pushing strings to Transifex +tx push --source diff --git a/utils/delete.c b/utils/delete.c index 5705848..49948f0 100644 --- a/utils/delete.c +++ b/utils/delete.c @@ -1,17 +1,18 @@ /* * fprintd example to delete fingerprints * Copyright (C) 2008 Daniel Drake + * Copyright (C) 2020 Marco Trevisan * * 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. @@ -19,114 +20,152 @@ #include #include -#include -#include "manager-dbus-glue.h" -#include "device-dbus-glue.h" +#include +#include "fprintd-dbus.h" -static DBusGProxy *manager = NULL; -static DBusGConnection *connection = NULL; +static FprintDBusManager *manager = NULL; +static GDBusConnection *connection = NULL; -static void create_manager(void) +static void +create_manager (void) { - GError *error = NULL; - - connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); - if (connection == NULL) { - g_print("Failed to connect to session bus: %s\n", error->message); - exit (1); - } - - manager = dbus_g_proxy_new_for_name(connection, - "net.reactivated.Fprint", "/net/reactivated/Fprint/Manager", - "net.reactivated.Fprint.Manager"); + g_autoptr(GError) error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (connection == NULL) + { + g_print ("Failed to connect to session bus: %s\n", error->message); + exit (1); + } + + manager = fprint_dbus_manager_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "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 delete_fingerprints(DBusGProxy *dev, const char *username) +static void +delete_fingerprints (FprintDBusDevice *dev, const char *username) { - GError *error = NULL; - 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); - } - - if (!net_reactivated_Fprint_Device_delete_enrolled_fingers(dev, username, &error)) { - if (dbus_g_error_has_name (error, "net.reactivated.Fprint.Error.NoEnrolledPrints") == FALSE) { - g_print("ListEnrolledFingers failed: %s\n", error->message); - exit (1); - } else { - g_print ("No fingerprints to delete on %s\n", g_value_get_string (g_hash_table_lookup (props, "name"))); - } - } else { - g_print ("Fingerprints deleted on %s\n", g_value_get_string (g_hash_table_lookup (props, "name"))); - } - g_hash_table_destroy (props); - g_object_unref (p); + 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); + exit (1); + } + + if (!fprint_dbus_device_call_delete_enrolled_fingers2_sync (dev, 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")) + { + 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); + } + else + { + 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 (!fprint_dbus_device_call_release_sync (dev, NULL, &error)) + { + g_print ("ReleaseDevice failed: %s\n", error->message); + exit (1); + } } -static void process_devices(char **argv) +static void +process_devices (char **argv) { - GError *error = NULL; - GPtrArray *devices; - char *path; - guint i; - - if (!net_reactivated_Fprint_Manager_get_devices(manager, &devices, &error)) { - g_print("list_devices failed: %s\n", error->message); - exit (1); - } - - if (devices->len == 0) { - g_print("No devices found\n"); - exit(1); - } - - g_print("found %d devices\n", devices->len); - for (i = 0; i < devices->len; i++) { - path = g_ptr_array_index(devices, i); - g_print("Device at %s\n", path); - } - - for (i = 0; i < devices->len; i++) { - guint j; - DBusGProxy *dev; - - path = g_ptr_array_index(devices, i); - g_print("Using device %s\n", path); - - /* FIXME use for_name_owner?? */ - dev = dbus_g_proxy_new_for_name(connection, "net.reactivated.Fprint", - path, "net.reactivated.Fprint.Device"); - - for (j = 1; argv[j] != NULL; j++) - delete_fingerprints (dev, argv[j]); - - g_object_unref (dev); - } - - g_ptr_array_foreach(devices, (GFunc) g_free, NULL); - g_ptr_array_free(devices, TRUE); + g_autoptr(GError) error = NULL; + g_auto(GStrv) devices = NULL; + char *path; + guint num_devices; + guint i; + + if (!fprint_dbus_manager_call_get_devices_sync (manager, &devices, + NULL, &error)) + { + g_print ("Impossible to get devices: %s\n", error->message); + exit (1); + } + + num_devices = g_strv_length (devices); + if (num_devices == 0) + { + g_print ("No devices available\n"); + exit (1); + } + + g_print ("found %u devices\n", num_devices); + for (i = 0; devices[i] != NULL; i++) + { + path = devices[i]; + g_print ("Device at %s\n", path); + } + + for (i = 0; devices[i] != NULL; i++) + { + g_autoptr(FprintDBusDevice) dev = NULL; + guint j; + + path = devices[i]; + g_print ("Using device %s\n", path); + + /* NOTE: We should handle error cases! */ + dev = fprint_dbus_device_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "net.reactivated.Fprint", + path, NULL, NULL); + + for (j = 1; argv[j] != NULL; j++) + delete_fingerprints (dev, argv[j]); + } } -int main(int argc, char **argv) +int +main (int argc, char **argv) { -#if !GLIB_CHECK_VERSION (2, 36, 0) - g_type_init(); -#endif + setlocale (LC_ALL, ""); - create_manager(); + create_manager (); - if (argc < 2) { - g_print ("Usage: %s [usernames...]\n", argv[0]); - return 1; - } + if (argc < 2) + { + g_print ("Usage: %s [usernames...]\n", argv[0]); + return 1; + } - process_devices (argv); + process_devices (argv); - return 0; + return 0; } - diff --git a/utils/enroll.c b/utils/enroll.c index 370f7e3..84098ab 100644 --- a/utils/enroll.c +++ b/utils/enroll.c @@ -1,185 +1,225 @@ /* * fprintd example to enroll right index finger * Copyright (C) 2008 Daniel Drake + * Copyright (C) 2020 Marco Trevisan * * 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. */ +#define _GNU_SOURCE #include #include -#include -#include "manager-dbus-glue.h" -#include "device-dbus-glue.h" -#include "marshal.h" +#include +#include "fprintd-dbus.h" #define N_(x) x #define TR(x) x #include "fingerprint-strings.h" -static DBusGProxy *manager = NULL; -static DBusGConnection *connection = NULL; -static char *finger_name = "right-index-finger"; +static FprintDBusManager *manager = NULL; +static GDBusConnection *connection = NULL; +static char *finger_name = NULL; static char **usernames = NULL; -static void create_manager(void) +static void +create_manager (void) { - GError *error = NULL; - - connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); - if (connection == NULL) { - g_print("Failed to connect to session bus: %s\n", error->message); - exit (1); - } + g_autoptr(GError) error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (connection == NULL) + { + g_print ("Failed to connect to session bus: %s\n", error->message); + exit (1); + } + + manager = fprint_dbus_manager_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "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); + } +} - manager = dbus_g_proxy_new_for_name(connection, - "net.reactivated.Fprint", "/net/reactivated/Fprint/Manager", - "net.reactivated.Fprint.Manager"); +static FprintDBusDevice * +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); + } + + g_print ("Using device %s\n", path); + + dev = fprint_dbus_device_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "net.reactivated.Fprint", + path, NULL, &error); + + if (error) + { + g_print ("failed to connect to device: %s\n", error->message); + exit (1); + } + + if (!fprint_dbus_device_call_claim_sync (dev, username, NULL, &error)) + { + g_print ("failed to claim device: %s\n", error->message); + exit (1); + } + return g_steal_pointer (&dev); } -static DBusGProxy *open_device(const char *username) +static void +enroll_result (GObject *object, const char *result, gboolean done, void *user_data) { - GError *error = NULL; - 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); - } - - if (path == NULL) { - g_print("No devices found\n"); - exit(1); - } - - g_print("Using device %s\n", path); - - /* FIXME use for_name_owner?? */ - dev = dbus_g_proxy_new_for_name(connection, "net.reactivated.Fprint", - path, "net.reactivated.Fprint.Device"); - - g_free (path); - - if (!net_reactivated_Fprint_Device_claim(dev, username, &error)) { - g_print("failed to claim device: %s\n", error->message); - exit (1); - } - return dev; + gboolean *enroll_completed = user_data; + + g_print ("Enroll result: %s\n", result); + if (done != FALSE) + *enroll_completed = TRUE; } -static void enroll_result(GObject *object, const char *result, gboolean done, void *user_data) +static void +proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) { - gboolean *enroll_completed = user_data; - g_print("Enroll result: %s\n", result); - if (done != FALSE) - *enroll_completed = TRUE; + 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(DBusGProxy *dev) +static void +do_enroll (FprintDBusDevice *dev) { - GError *error = NULL; - gboolean enroll_completed = FALSE; - gboolean found; - guint i; - - dbus_g_proxy_add_signal(dev, "EnrollStatus", G_TYPE_STRING, G_TYPE_BOOLEAN, NULL); - dbus_g_proxy_connect_signal(dev, "EnrollStatus", G_CALLBACK(enroll_result), - &enroll_completed, NULL); - - found = FALSE; - for (i = 0; fingers[i].dbus_name != NULL; i++) { - if (g_strcmp0 (fingers[i].dbus_name, finger_name) == 0) { - found = TRUE; - break; - } - } - if (!found) { - GString *s; - - s = g_string_new (NULL); - 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++) { - g_string_append_printf (s, "%s", fingers[i].dbus_name); - if (fingers[i + 1].dbus_name != NULL) - g_string_append (s, ", "); - } - g_warning ("%s", s->str); - g_string_free (s, TRUE); - exit (1); - } - - g_print("Enrolling %s finger.\n", finger_name); - if (!net_reactivated_Fprint_Device_enroll_start(dev, finger_name, &error)) { - g_print("EnrollStart failed: %s\n", error->message); - exit (1); - } - - while (!enroll_completed) - g_main_context_iteration(NULL, TRUE); - - dbus_g_proxy_disconnect_signal(dev, "EnrollStatus", - G_CALLBACK(enroll_result), &enroll_completed); - - if (!net_reactivated_Fprint_Device_enroll_stop(dev, &error)) { - g_print("VerifyStop failed: %s\n", error->message); - exit(1); - } + g_autoptr(GError) error = NULL; + gboolean enroll_completed = FALSE; + gboolean found; + guint i; + + g_signal_connect (dev, "g-signal", G_CALLBACK (proxy_signal_cb), + &enroll_completed); + + found = FALSE; + for (i = 0; fingers[i].dbus_name != NULL; i++) + { + if (g_strcmp0 (fingers[i].dbus_name, finger_name) == 0) + { + found = TRUE; + break; + } + } + if (!found) + { + g_autoptr(GString) s = NULL; + + s = g_string_new (NULL); + 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++) + { + g_string_append_printf (s, "%s", fingers[i].dbus_name); + if (fingers[i + 1].dbus_name != NULL) + g_string_append (s, ", "); + } + g_warning ("%s", s->str); + exit (1); + } + + g_print ("Enrolling %s finger.\n", finger_name); + if (!fprint_dbus_device_call_enroll_start_sync (dev, finger_name, NULL, + &error)) + { + g_print ("EnrollStart failed: %s\n", error->message); + exit (1); + } + + while (!enroll_completed) + g_main_context_iteration (NULL, TRUE); + + g_signal_handlers_disconnect_by_func (dev, proxy_signal_cb, &enroll_result); + + if (!fprint_dbus_device_call_enroll_stop_sync (dev, NULL, &error)) + { + g_print ("VerifyStop failed: %s\n", error->message); + exit (1); + } } -static void release_device(DBusGProxy *dev) +static void +release_device (FprintDBusDevice *dev) { - GError *error = NULL; - if (!net_reactivated_Fprint_Device_release(dev, &error)) { - g_print("ReleaseDevice failed: %s\n", error->message); - exit (1); - } + g_autoptr(GError) error = NULL; + if (!fprint_dbus_device_call_release_sync (dev, NULL, &error)) + { + g_print ("ReleaseDevice failed: %s\n", error->message); + exit (1); + } } static const GOptionEntry entries[] = { - { "finger", 'f', 0, G_OPTION_ARG_STRING, &finger_name, "Finger selected to verify (default is automatic)", NULL }, - { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &usernames, NULL, "[username]" }, - { NULL } + { "finger", 'f', 0, G_OPTION_ARG_STRING, &finger_name, "Finger selected to verify (default is automatic)", NULL }, + { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &usernames, NULL, "[username]" }, + { NULL } }; -int main(int argc, char **argv) +int +main (int argc, char **argv) { - GOptionContext *context; - GError *err = NULL; - DBusGProxy *dev; + g_autoptr(FprintDBusDevice) dev = NULL; + GOptionContext *context; -#if !GLIB_CHECK_VERSION (2, 36, 0) - g_type_init(); -#endif + g_autoptr(GError) err = NULL; - dbus_g_object_register_marshaller (fprintd_marshal_VOID__STRING_BOOLEAN, - G_TYPE_NONE, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INVALID); + setlocale (LC_ALL, ""); - context = g_option_context_new ("Enroll a fingerprint"); - g_option_context_add_main_entries (context, entries, NULL); + context = g_option_context_new ("Enroll a fingerprint"); + g_option_context_add_main_entries (context, entries, NULL); - if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) { - g_print ("couldn't parse command-line options: %s\n", err->message); - g_error_free (err); - return 1; - } + if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) + { + g_print ("couldn't parse command-line options: %s\n", err->message); + return 1; + } - create_manager(); + if (finger_name == NULL) + finger_name = g_strdup ("right-index-finger"); - dev = open_device(usernames ? usernames[0] : NULL); - do_enroll(dev); - release_device(dev); - return 0; -} + create_manager (); + dev = open_device (usernames ? usernames[0] : ""); + do_enroll (dev); + release_device (dev); + g_free (finger_name); + g_strfreev (usernames); + return 0; +} diff --git a/utils/list.c b/utils/list.c index d969752..df6422c 100644 --- a/utils/list.c +++ b/utils/list.c @@ -1,17 +1,18 @@ /* * fprintd example to list enrolled fingerprints * Copyright (C) 2008 Daniel Drake + * Copyright (C) 2020 Marco Trevisan * * 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. @@ -19,130 +20,143 @@ #include #include -#include -#include "manager-dbus-glue.h" -#include "device-dbus-glue.h" +#include +#include "fprintd-dbus.h" -static DBusGProxy *manager = NULL; -static DBusGConnection *connection = NULL; +static FprintDBusManager *manager = NULL; +static GDBusConnection *connection = NULL; -static void create_manager(void) +static void +create_manager (void) { - GError *error = NULL; - - connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); - if (connection == NULL) { - g_print("Failed to connect to session bus: %s\n", error->message); - exit (1); - } - - manager = dbus_g_proxy_new_for_name(connection, - "net.reactivated.Fprint", "/net/reactivated/Fprint/Manager", - "net.reactivated.Fprint.Manager"); + g_autoptr(GError) error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (connection == NULL) + { + g_print ("Failed to connect to session bus: %s\n", error->message); + exit (1); + } + + manager = fprint_dbus_manager_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "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; - char **fingers; - GHashTable *props; - DBusGProxy *p; - guint i; - - if (!net_reactivated_Fprint_Device_list_enrolled_fingers(dev, username, &fingers, &error)) { - if (dbus_g_error_has_name (error, "net.reactivated.Fprint.Error.NoEnrolledPrints") == FALSE) { - g_print("ListEnrolledFingers failed: %s\n", error->message); - exit (1); - } else { - fingers = NULL; - } - } - - 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); - } - - 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; - } - - g_print("Fingerprints for user %s on %s (%s):\n", - username, - g_value_get_string (g_hash_table_lookup (props, "name")), - g_value_get_string (g_hash_table_lookup (props, "scan-type"))); - g_hash_table_destroy (props); - g_object_unref (p); - - for (i = 0; fingers[i] != NULL; i++) { - g_print(" - #%d: %s\n", i, fingers[i]); - } - - g_strfreev (fingers); + g_autoptr(GError) error = NULL; + g_auto(GStrv) fingers = NULL; + guint i; + + if (!fprint_dbus_device_call_list_enrolled_fingers_sync (dev, username, + &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); + exit (1); + } + } + + if (fingers == NULL || g_strv_length (fingers) == 0) + { + g_print ("User %s has no fingers enrolled for %s.\n", username, + fprint_dbus_device_get_name (dev)); + return; + } + + g_print ("Fingerprints for user %s on %s (%s):\n", + username, + fprint_dbus_device_get_name (dev), + fprint_dbus_device_get_scan_type (dev)); + + for (i = 0; fingers[i] != NULL; i++) + g_print (" - #%d: %s\n", i, fingers[i]); } -static void process_devices(char **argv) +static void +process_devices (char **argv) { - GError *error = NULL; - GPtrArray *devices; - char *path; - guint i; - - if (!net_reactivated_Fprint_Manager_get_devices(manager, &devices, &error)) { - g_print("list_devices failed: %s\n", error->message); - exit (1); - } - - if (devices->len == 0) { - g_print("No devices found\n"); - exit(1); - } - - g_print("found %d devices\n", devices->len); - for (i = 0; i < devices->len; i++) { - path = g_ptr_array_index(devices, i); - g_print("Device at %s\n", path); - } - - for (i = 0; i < devices->len; i++) { - guint j; - DBusGProxy *dev; - - path = g_ptr_array_index(devices, i); - g_print("Using device %s\n", path); - - /* FIXME use for_name_owner?? */ - dev = dbus_g_proxy_new_for_name(connection, "net.reactivated.Fprint", - path, "net.reactivated.Fprint.Device"); - - for (j = 1; argv[j] != NULL; j++) - list_fingerprints (dev, argv[j]); - - g_object_unref (dev); - } - - g_ptr_array_foreach(devices, (GFunc) g_free, NULL); - g_ptr_array_free(devices, TRUE); + g_auto(GStrv) devices = NULL; + g_autoptr(GError) error = NULL; + char *path; + guint num_devices; + guint i; + + if (!fprint_dbus_manager_call_get_devices_sync (manager, &devices, NULL, + &error)) + { + g_print ("Impossible to get devices: %s\n", error->message); + exit (1); + } + + num_devices = g_strv_length (devices); + if (num_devices == 0) + { + g_print ("No devices available\n"); + exit (1); + } + + g_print ("found %u devices\n", num_devices); + for (i = 0; devices[i] != NULL; i++) + { + path = devices[i]; + g_print ("Device at %s\n", path); + } + + for (i = 0; devices[i] != NULL; i++) + { + g_autoptr(FprintDBusDevice) dev = NULL; + guint j; + + path = devices[i]; + g_print ("Using device %s\n", path); + + /* NOTE: We should handle error cases! */ + dev = fprint_dbus_device_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "net.reactivated.Fprint", + path, NULL, NULL); + + for (j = 1; argv[j] != NULL; j++) + list_fingerprints (dev, argv[j]); + } } -int main(int argc, char **argv) +int +main (int argc, char **argv) { -#if !GLIB_CHECK_VERSION (2, 36, 0) - g_type_init(); -#endif + setlocale (LC_ALL, ""); - create_manager(); + create_manager (); - if (argc < 2) { - g_print ("Usage: %s [usernames...]\n", argv[0]); - return 1; - } + if (argc < 2) + { + g_print ("Usage: %s [usernames...]\n", argv[0]); + return 1; + } - process_devices (argv); + process_devices (argv); - return 0; + return 0; } - diff --git a/utils/meson.build b/utils/meson.build new file mode 100644 index 0000000..4056c79 --- /dev/null +++ b/utils/meson.build @@ -0,0 +1,34 @@ +libfprintd_utils_dep = declare_dependency( + include_directories: [ + include_directories('../src'), + include_directories('../pam'), + ], + dependencies: [ + glib_dep, + gio_dep, + gio_unix_dep, + ], + sources: [ + fprintd_dbus_sources, + ], + link_with: [ + libfprintd_private + ], +) + +utils = [ + 'delete', + 'enroll', + 'list', + 'verify', +] + +fprintd_utils = [] + +foreach util: utils + fprintd_utils += executable('fprintd-' + util, + sources: util + '.c', + dependencies: libfprintd_utils_dep, + install: true, + ) +endforeach diff --git a/utils/verify.c b/utils/verify.c index c828c80..fc0372b 100644 --- a/utils/verify.c +++ b/utils/verify.c @@ -1,17 +1,18 @@ /* * fprintd example to verify a fingerprint * Copyright (C) 2008 Daniel Drake + * Copyright (C) 2020 Marco Trevisan * * 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. @@ -20,194 +21,286 @@ #include #include #include -#include -#include "manager-dbus-glue.h" -#include "device-dbus-glue.h" -#include "marshal.h" +#include +#include +#include "fprintd-dbus.h" -static DBusGProxy *manager = NULL; -static DBusGConnection *connection = NULL; +static FprintDBusManager *manager = NULL; +static GDBusConnection *connection = NULL; static char *finger_name = NULL; static gboolean g_fatal_warnings = FALSE; static char **usernames = NULL; -static void create_manager(void) +static void +create_manager (void) { - GError *error = NULL; + g_autoptr(GError) error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (connection == NULL) + { + g_print ("Failed to connect to session bus: %s\n", error->message); + exit (1); + } + + manager = fprint_dbus_manager_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "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); + } +} - connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); - if (connection == NULL) { - g_print("Failed to connect to session bus: %s\n", error->message); - exit (1); - } +static FprintDBusDevice * +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); + } + + g_print ("Using device %s\n", path); + + dev = fprint_dbus_device_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "net.reactivated.Fprint", + path, NULL, &error); + + if (error) + { + g_print ("failed to connect to device: %s\n", error->message); + exit (1); + } + + if (!fprint_dbus_device_call_claim_sync (dev, username, NULL, &error)) + { + g_print ("failed to claim device: %s\n", error->message); + exit (1); + } + + return g_steal_pointer (&dev); +} - manager = dbus_g_proxy_new_for_name(connection, - "net.reactivated.Fprint", "/net/reactivated/Fprint/Manager", - "net.reactivated.Fprint.Manager"); +static void +find_finger (FprintDBusDevice *dev, const char *username) +{ + g_autoptr(GError) error = NULL; + g_auto(GStrv) fingers = NULL; + guint i; + + if (!fprint_dbus_device_call_list_enrolled_fingers_sync (dev, username, + &fingers, + NULL, &error)) + { + g_print ("ListEnrolledFingers failed: %s\n", error->message); + exit (1); + } + + if (fingers == NULL || g_strv_length (fingers) == 0) + { + g_print ("No fingers enrolled for this device.\n"); + exit (1); + } + + g_print ("Listing enrolled fingers:\n"); + for (i = 0; fingers[i] != NULL; 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) + finger_name = g_strdup (fingers[0]); } -static DBusGProxy *open_device(const char *username) +struct VerifyState { - GError *error = NULL; - 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); - } - - if (path == NULL) { - g_print("No devices found\n"); - exit(1); - } - - g_print("Using device %s\n", path); - - /* FIXME use for_name_owner?? */ - dev = dbus_g_proxy_new_for_name(connection, "net.reactivated.Fprint", - path, "net.reactivated.Fprint.Device"); - - g_free (path); - - if (!net_reactivated_Fprint_Device_claim(dev, username, &error)) { - g_print("failed to claim device: %s\n", error->message); - exit (1); - } - - return dev; + 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"); + if (done != FALSE) + verify_state->completed = TRUE; } -static void find_finger(DBusGProxy *dev, const char *username) +static void +verify_finger_selected (GObject *object, const char *name, void *user_data) { - GError *error = NULL; - char **fingers; - guint i; - - if (!net_reactivated_Fprint_Device_list_enrolled_fingers(dev, username, &fingers, &error)) { - g_print("ListEnrolledFingers failed: %s\n", error->message); - exit (1); - } - - if (fingers == NULL || g_strv_length (fingers) == 0) { - g_print("No fingers enrolled for this device.\n"); - exit(1); - } - - g_print("Listing enrolled fingers:\n"); - for (i = 0; fingers[i] != NULL; i++) { - g_print(" - #%d: %s\n", i, fingers[i]); - } - - if (finger_name == NULL || strcmp (finger_name, "any") == 0) { - g_free (finger_name); - finger_name = g_strdup (fingers[0]); - } - - g_strfreev (fingers); + g_print ("Verifying: %s\n", name); } -static void verify_result(GObject *object, const char *result, gboolean done, void *user_data) +static void +verify_started_cb (GObject *obj, + GAsyncResult *res, + gpointer user_data) { - gboolean *verify_completed = user_data; - g_print("Verify result: %s (%s)\n", result, done ? "done" : "not done"); - if (done != FALSE) - *verify_completed = TRUE; + struct VerifyState *verify_state = user_data; + + if (fprint_dbus_device_call_verify_start_finish (FPRINT_DBUS_DEVICE (obj), res, &verify_state->error)) + { + g_print ("Verify started!\n"); + verify_state->started = TRUE; + } } -static void verify_finger_selected(GObject *object, const char *name, void *user_data) +static void +proxy_signal_cb (GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) { - g_print("Verifying: %s\n", name); + 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(DBusGProxy *dev) +static void +do_verify (FprintDBusDevice *dev) { - GError *error = NULL; - gboolean verify_completed = FALSE; - - dbus_g_proxy_add_signal(dev, "VerifyStatus", G_TYPE_STRING, G_TYPE_BOOLEAN, NULL); - dbus_g_proxy_add_signal(dev, "VerifyFingerSelected", G_TYPE_INT, NULL); - dbus_g_proxy_connect_signal(dev, "VerifyStatus", G_CALLBACK(verify_result), - &verify_completed, NULL); - 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)) { - g_print("VerifyStart failed: %s\n", error->message); - exit (1); - } - - while (!verify_completed) - 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_print("VerifyStop failed: %s\n", error->message); - exit (1); - } + 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); + } + + /* VerifyStatus signals are processing, wait for completion. */ + while (!verify_state.completed) + g_main_context_iteration (NULL, TRUE); + + + 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); + exit (1); + } } -static void release_device(DBusGProxy *dev) +static void +release_device (FprintDBusDevice *dev) { - GError *error = NULL; - if (!net_reactivated_Fprint_Device_release(dev, &error)) { - g_print("ReleaseDevice failed: %s\n", error->message); - exit (1); - } + g_autoptr(GError) error = NULL; + if (!fprint_dbus_device_call_release_sync (dev, NULL, &error)) + { + g_print ("ReleaseDevice failed: %s\n", error->message); + exit (1); + } } static const GOptionEntry entries[] = { - { "finger", 'f', 0, G_OPTION_ARG_STRING, &finger_name, "Finger selected to verify (default is automatic)", NULL }, - {"g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal", NULL}, - { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &usernames, NULL, "[username]" }, - { NULL } + { "finger", 'f', 0, G_OPTION_ARG_STRING, &finger_name, "Finger selected to verify (default is automatic)", NULL }, + {"g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal", NULL}, + { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &usernames, NULL, "[username]" }, + { NULL } }; -int main(int argc, char **argv) +int +main (int argc, char **argv) { - GOptionContext *context; - GError *err = NULL; - DBusGProxy *dev; - char *username; - -#if !GLIB_CHECK_VERSION (2, 36, 0) - g_type_init(); -#endif - - 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"); - g_option_context_add_main_entries (context, entries, NULL); - - if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) { - g_print ("couldn't parse command-line options: %s\n", err->message); - g_error_free (err); - return 1; - } - - if (usernames == NULL) { - username = ""; - } else { - username = usernames[0]; - } - - if (g_fatal_warnings) { - GLogLevelFlags fatal_mask; - - fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); - fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; - g_log_set_always_fatal (fatal_mask); - } - - create_manager(); - - dev = open_device(username); - find_finger(dev, username); - do_verify(dev); - release_device(dev); - return 0; + g_autoptr(FprintDBusDevice) dev = NULL; + g_autoptr(GError) err = NULL; + GOptionContext *context; + const char *username = NULL; + + setlocale (LC_ALL, ""); + + context = g_option_context_new ("Verify a fingerprint"); + g_option_context_add_main_entries (context, entries, NULL); + + if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) + { + g_print ("couldn't parse command-line options: %s\n", err->message); + return 1; + } + + if (usernames == NULL) + username = ""; + else + username = usernames[0]; + + if (g_fatal_warnings) + { + GLogLevelFlags fatal_mask; + + fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); + fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; + g_log_set_always_fatal (fatal_mask); + } + + create_manager (); + + dev = open_device (username); + find_finger (dev, username); + do_verify (dev); + release_device (dev); + return 0; } -