/* * Fingerprint data handling and storage * Copyright (C) 2007 Daniel Drake * * This library 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 library 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include "fp_internal.h" #define DIR_PERMS 0700 /** @defgroup print_data Stored prints * Stored prints are represented by a structure named fp_print_data. * Stored prints are originally obtained from an enrollment function such as * fp_enroll_finger(). * * This page documents the various operations you can do with a stored print. * Note that by default, "stored prints" are not actually stored anywhere * except in RAM. For the simple scenarios, libfprint provides a simple API * for you to save and load the stored prints referring to a single user in * their home directory. For more advanced users, libfprint provides APIs for * you to convert print data to a byte string, and to reconstruct stored prints * from such data at a later point. You are welcome to store these byte strings * in any fashion that suits you. */ static char *base_store = NULL; static void storage_setup(void) { const char *homedir; homedir = g_getenv("HOME"); if (!homedir) homedir = g_get_home_dir(); if (!homedir) return; base_store = g_build_filename(homedir, ".fprint/prints", NULL); g_mkdir_with_parents(base_store, DIR_PERMS); /* FIXME handle failure */ } void fpi_data_exit(void) { g_free(base_store); } #define FP_FINGER_IS_VALID(finger) \ ((finger) >= LEFT_THUMB && (finger) <= RIGHT_LITTLE) /* for debug messages only */ static const char *finger_num_to_str(enum fp_finger finger) { const char *names[] = { [LEFT_THUMB] = "left thumb", [LEFT_INDEX] = "left index", [LEFT_MIDDLE] = "left middle", [LEFT_RING] = "left ring", [LEFT_LITTLE] = "left little", [RIGHT_THUMB] = "right thumb", [RIGHT_INDEX] = "right index", [RIGHT_MIDDLE] = "right middle", [RIGHT_RING] = "right ring", [RIGHT_LITTLE] = "right little", }; if (!FP_FINGER_IS_VALID(finger)) return "UNKNOWN"; return names[finger]; } static struct fp_print_data *print_data_new(uint16_t driver_id, uint32_t devtype, enum fp_print_data_type type, size_t length) { struct fp_print_data *data = g_malloc(sizeof(*data) + length); fp_dbg("length=%zd driver=%02x devtype=%04x", length, driver_id, devtype); memset(data, 0, sizeof(*data)); data->driver_id = driver_id; data->devtype = devtype; data->type = type; data->length = length; return data; } struct fp_print_data *fpi_print_data_new(struct fp_dev *dev, size_t length) { struct fp_print_data *data = g_malloc(sizeof(*data) + length); memset(data, 0, sizeof(*data)); return print_data_new(dev->drv->id, dev->devtype, fpi_driver_get_data_type(dev->drv), length); } /** \ingroup print_data * Convert a stored print into a unified representation inside a data buffer. * You can then store this data buffer in any way that suits you, and load * it back at some later time using fp_print_data_from_data(). * \param data the stored print * \param ret output location for the data buffer. Must be freed with free() * after use. * \returns the size of the freshly allocated buffer, or 0 on error. */ API_EXPORTED size_t fp_print_data_get_data(struct fp_print_data *data, unsigned char **ret) { struct fpi_print_data_fp1 *buf; size_t buflen; fp_dbg(""); buflen = sizeof(*buf) + data->length; buf = malloc(buflen); if (!buf) return 0; *ret = (unsigned char *) buf; buf->prefix[0] = 'F'; buf->prefix[1] = 'P'; buf->prefix[2] = '1'; buf->driver_id = GUINT16_TO_LE(data->driver_id); buf->devtype = GUINT32_TO_LE(data->devtype); buf->data_type = data->type; memcpy(buf->data, data->data, data->length); return buflen; } /** \ingroup print_data * Load a stored print from a data buffer. The contents of said buffer must * be the untouched contents of a buffer previously supplied to you by the * fp_print_data_get_data() function. * \param buf the data buffer * \param buflen the length of the buffer * \returns the stored print represented by the data, or NULL on error. Must * be freed with fp_print_data_free() after use. */ API_EXPORTED struct fp_print_data *fp_print_data_from_data(unsigned char *buf, size_t buflen) { struct fpi_print_data_fp1 *raw = (struct fpi_print_data_fp1 *) buf; size_t print_data_len; struct fp_print_data *data; fp_dbg("buffer size %zd", buflen); if (buflen < sizeof(*raw)) return NULL; if (strncmp(raw->prefix, "FP1", 3) != 0) { fp_dbg("bad header prefix"); return NULL; } print_data_len = buflen - sizeof(*raw); data = print_data_new(GUINT16_FROM_LE(raw->driver_id), GUINT32_FROM_LE(raw->devtype), raw->data_type, print_data_len); memcpy(data->data, raw->data, print_data_len); return data; } static char *get_path_to_storedir(uint16_t driver_id, uint32_t devtype) { char idstr[5]; char devtypestr[9]; g_snprintf(idstr, sizeof(idstr), "%04x", driver_id); g_snprintf(devtypestr, sizeof(devtypestr), "%08x", devtype); return g_build_filename(base_store, idstr, devtypestr, NULL); } static char *__get_path_to_print(uint16_t driver_id, uint32_t devtype, enum fp_finger finger) { char *dirpath; char *path; char fingername[2]; g_snprintf(fingername, 2, "%x", finger); dirpath = get_path_to_storedir(driver_id, devtype); path = g_build_filename(dirpath, fingername, NULL); g_free(dirpath); return path; } static char *get_path_to_print(struct fp_dev *dev, enum fp_finger finger) { return __get_path_to_print(dev->drv->id, dev->devtype, finger); } /** \ingroup print_data * Saves a stored print to disk, assigned to a specific finger. Even though * you are limited to storing only the 10 human fingers, this is a * per-device-type limit. For example, you can store the users right index * finger from a DigitalPersona scanner, and you can also save the right index * finger from a UPEK scanner. When you later come to load the print, the right * one will be automatically selected. * * This function will unconditionally overwrite a fingerprint previously * saved for the same finger and device type. The print is saved in a hidden * directory beneath the current user's home directory. * \param data the stored print to save to disk * \param finger the finger that this print corresponds to * \returns 0 on success, non-zero on error. */ API_EXPORTED int fp_print_data_save(struct fp_print_data *data, enum fp_finger finger) { GError *err = NULL; char *path; char *dirpath; unsigned char *buf; size_t len; int r; if (!base_store) storage_setup(); fp_dbg("save %s print from driver %04x", finger_num_to_str(finger), data->driver_id); len = fp_print_data_get_data(data, &buf); if (!len) return -ENOMEM; path = __get_path_to_print(data->driver_id, data->devtype, finger); dirpath = g_path_get_dirname(path); r = g_mkdir_with_parents(dirpath, DIR_PERMS); if (r < 0) { fp_err("couldn't create storage directory"); g_free(path); g_free(dirpath); return r; } fp_dbg("saving to %s", path); g_file_set_contents(path, buf, len, &err); free(buf); g_free(dirpath); g_free(path); if (err) { r = err->code; fp_err("save failed: %s", err->message); g_error_free(err); /* FIXME interpret error codes */ return r; } return 0; } gboolean fpi_print_data_compatible(uint16_t driver_id1, uint32_t devtype1, enum fp_print_data_type type1, uint16_t driver_id2, uint32_t devtype2, enum fp_print_data_type type2) { if (driver_id1 != driver_id2) { fp_dbg("driver ID mismatch: %02x vs %02x", driver_id1, driver_id2); return FALSE; } if (devtype1 != devtype2) { fp_dbg("devtype mismatch: %04x vs %04x", devtype1, devtype2); return FALSE; } if (type1 != type2) { fp_dbg("type mismatch: %d vs %d", type1, type2); return FALSE; } return TRUE; } static int load_from_file(char *path, struct fp_print_data **data) { gsize length; gchar *contents; GError *err = NULL; struct fp_print_data *fdata; fp_dbg("from %s", path); g_file_get_contents(path, &contents, &length, &err); if (err) { int r = err->code; fp_err("%s load failed: %s", path, err->message); g_error_free(err); /* FIXME interpret more error codes */ if (r == G_FILE_ERROR_NOENT) return -ENOENT; else return r; } fdata = fp_print_data_from_data(contents, length); g_free(contents); if (!fdata) return -EIO; *data = fdata; return 0; } /** \ingroup print_data * Loads a previously stored print from disk. The print must have been saved * earlier using the fp_print_data_save() function. * * A return code of -ENOENT indicates that the fingerprint requested could not * be found. Other error codes (both positive and negative) are possible for * obscure error conditions (e.g. corruption). * * \param dev the device you are loading the print for * \param finger the finger of the file you are loading * \param data output location to put the corresponding stored print. Must be * freed with fp_print_data_free() after use. * \returns 0 on success, non-zero on error */ API_EXPORTED int fp_print_data_load(struct fp_dev *dev, enum fp_finger finger, struct fp_print_data **data) { gchar *path; struct fp_print_data *fdata; int r; if (!base_store) storage_setup(); path = get_path_to_print(dev, finger); r = load_from_file(path, &fdata); g_free(path); if (r) return r; if (!fp_dev_supports_print_data(dev, fdata)) { fp_err("print data is not compatible!"); fp_print_data_free(fdata); return -EINVAL; } *data = fdata; return 0; } /** \ingroup print_data * Removes a stored print from disk previously saved with fp_print_data_save(). * \param dev the device that the print belongs to * \param finger the finger of the file you are deleting * \returns 0 on success, negative on error */ API_EXPORTED int fp_print_data_delete(struct fp_dev *dev, enum fp_finger finger) { int r; gchar *path = get_path_to_print(dev, finger); fp_dbg("remove finger %d at %s", finger, path); r = g_unlink(path); g_free(path); if (r < 0) fp_dbg("unlink failed with error %d", r); /* FIXME: cleanup empty directory */ return r; } /** \ingroup print_data * Attempts to load a stored print based on a \ref dscv_print * "discovered print" record. * * A return code of -ENOENT indicates that the file referred to by the * discovered print could not be found. Other error codes (both positive and * negative) are possible for obscure error conditions (e.g. corruption). * * \param print the discovered print * \param data output location to point to the corresponding stored print. Must * be freed with fp_print_data_free() after use. * \returns 0 on success, non-zero on error. */ API_EXPORTED int fp_print_data_from_dscv_print(struct fp_dscv_print *print, struct fp_print_data **data) { return load_from_file(print->path, data); } /** \ingroup print_data * Frees a stored print. Must be called when you are finished using the print. * \param data the stored print to destroy. If NULL, function simply returns. */ API_EXPORTED void fp_print_data_free(struct fp_print_data *data) { g_free(data); } /** \ingroup print_data * Gets the \ref driver_id "driver ID" for a stored print. The driver ID * indicates which driver the print originally came from. The print is * only usable with a device controlled by that driver. * \param data the stored print * \returns the driver ID of the driver compatible with the print */ API_EXPORTED uint16_t fp_print_data_get_driver_id(struct fp_print_data *data) { return data->driver_id; } /** \ingroup print_data * Gets the \ref devtype "devtype" for a stored print. The devtype represents * which type of device under the parent driver is compatible with the print. * \param data the stored print * \returns the devtype of the device range compatible with the print */ API_EXPORTED uint32_t fp_print_data_get_devtype(struct fp_print_data *data) { return data->devtype; } /** @defgroup dscv_print Print discovery * The \ref print_data "stored print" documentation detailed a simple API * for storing per-device prints for a single user, namely * fp_print_data_save(). It also detailed a load function, * fp_print_data_load(), but usage of this function is limited to scenarios * where you know which device you would like to use, and you know which * finger you are looking to verify. * * In other cases, it would be more useful to be able to enumerate all * previously saved prints, potentially even before device discovery. These * functions are designed to offer this functionality to you. * * Discovered prints are stored in a dscv_print structure, and you * can use functions documented below to access some information about these * prints. You can determine if a discovered print appears to be compatible * with a device using functions such as fp_dscv_dev_supports_dscv_print() and * fp_dev_supports_dscv_print(). * * When you are ready to use the print, you can load it into memory in the form * of a stored print by using the fp_print_data_from_dscv_print() function. * * You may have noticed the use of the word "appears" in the above paragraphs. * libfprint performs print discovery simply by examining the file and * directory structure of libfprint's private data store. It does not examine * the actual prints themselves. Just because a print has been discovered * and appears to be compatible with a certain device does not necessarily mean * that it is usable; when you come to load or use it, under unusual * circumstances it may turn out that the print is corrupt or not for the * device that it appeared to be. Also, it is possible that the print may have * been deleted by the time you come to load it. */ static GSList *scan_dev_store_dir(char *devpath, uint16_t driver_id, uint32_t devtype, GSList *list) { GError *err = NULL; const gchar *ent; struct fp_dscv_print *print; GDir *dir = g_dir_open(devpath, 0, &err); if (!dir) { fp_err("opendir %s failed: %s", devpath, err->message); g_error_free(err); return list; } while ((ent = g_dir_read_name(dir))) { /* ent is an 1 hex character fp_finger code */ guint64 val; enum fp_finger finger; gchar *endptr; if (*ent == 0 || strlen(ent) != 1) continue; val = g_ascii_strtoull(ent, &endptr, 16); if (endptr == ent || !FP_FINGER_IS_VALID(val)) { fp_dbg("skipping print file %s", ent); continue; } finger = (enum fp_finger) val; print = g_malloc(sizeof(*print)); print->driver_id = driver_id; print->devtype = devtype; print->path = g_build_filename(devpath, ent, NULL); print->finger = finger; list = g_slist_prepend(list, print); } g_dir_close(dir); return list; } static GSList *scan_driver_store_dir(char *drvpath, uint16_t driver_id, GSList *list) { GError *err = NULL; const gchar *ent; GDir *dir = g_dir_open(drvpath, 0, &err); if (!dir) { fp_err("opendir %s failed: %s", drvpath, err->message); g_error_free(err); return list; } while ((ent = g_dir_read_name(dir))) { /* ent is an 8 hex character devtype */ guint64 val; uint32_t devtype; gchar *endptr; gchar *path; if (*ent == 0 || strlen(ent) != 8) continue; val = g_ascii_strtoull(ent, &endptr, 16); if (endptr == ent) { fp_dbg("skipping devtype %s", ent); continue; } devtype = (uint32_t) val; path = g_build_filename(drvpath, ent, NULL); list = scan_dev_store_dir(path, driver_id, devtype, list); g_free(path); } g_dir_close(dir); return list; } /** \ingroup dscv_print * Scans the users home directory and returns a list of prints that were * previously saved using fp_print_data_save(). * \returns a NULL-terminated list of discovered prints, must be freed with * fp_dscv_prints_free() after use. */ API_EXPORTED struct fp_dscv_print **fp_discover_prints(void) { GDir *dir; const gchar *ent; GError *err = NULL; GSList *tmplist = NULL; GSList *elem; unsigned int tmplist_len; struct fp_dscv_print **list; unsigned int i; if (!base_store) storage_setup(); dir = g_dir_open(base_store, 0, &err); if (!dir) { fp_err("opendir %s failed: %s", base_store, err->message); g_error_free(err); return NULL; } while ((ent = g_dir_read_name(dir))) { /* ent is a 4 hex digit driver_id */ gchar *endptr; gchar *path; guint64 val; uint16_t driver_id; if (*ent == 0 || strlen(ent) != 4) continue; val = g_ascii_strtoull(ent, &endptr, 16); if (endptr == ent) { fp_dbg("skipping drv id %s", ent); continue; } driver_id = (uint16_t) val; path = g_build_filename(base_store, ent, NULL); tmplist = scan_driver_store_dir(path, driver_id, tmplist); g_free(path); } g_dir_close(dir); tmplist_len = g_slist_length(tmplist); list = g_malloc(sizeof(*list) * (tmplist_len + 1)); elem = tmplist; for (i = 0; i < tmplist_len; i++, elem = g_slist_next(elem)) list[i] = elem->data; list[tmplist_len] = NULL; /* NULL-terminate */ g_slist_free(tmplist); return list; } /** \ingroup dscv_print * Frees a list of discovered prints. This function also frees the discovered * prints themselves, so make sure you do not use any discovered prints * after calling this function. * \param prints the list of discovered prints. If NULL, function simply * returns. */ API_EXPORTED void fp_dscv_prints_free(struct fp_dscv_print **prints) { int i; struct fp_dscv_print *print; if (!prints) return; for (i = 0; (print = prints[i]); i++) { if (print) g_free(print->path); g_free(print); } g_free(prints); } /** \ingroup dscv_print * Gets the \ref driver_id "driver ID" for a discovered print. The driver ID * indicates which driver the print originally came from. The print is only * usable with a device controlled by that driver. * \param print the discovered print * \returns the driver ID of the driver compatible with the print */ API_EXPORTED uint16_t fp_dscv_print_get_driver_id(struct fp_dscv_print *print) { return print->driver_id; } /** \ingroup dscv_print * Gets the \ref devtype "devtype" for a discovered print. The devtype * represents which type of device under the parent driver is compatible * with the print. * \param print the discovered print * \returns the devtype of the device range compatible with the print */ API_EXPORTED uint32_t fp_dscv_print_get_devtype(struct fp_dscv_print *print) { return print->devtype; } /** \ingroup dscv_print * Gets the finger code for a discovered print. * \param print discovered print * \returns a finger code from #fp_finger */ API_EXPORTED enum fp_finger fp_dscv_print_get_finger(struct fp_dscv_print *print) { return print->finger; } /** \ingroup dscv_print * Removes a discovered print from disk. After successful return of this * function, functions such as fp_dscv_print_get_finger() will continue to * operate as before, however calling fp_print_data_from_dscv_print() will * fail for obvious reasons. * \param print the discovered print to remove from disk * \returns 0 on success, negative on error */ API_EXPORTED int fp_dscv_print_delete(struct fp_dscv_print *print) { int r; fp_dbg("remove at %s", print->path); r = g_unlink(print->path); if (r < 0) fp_dbg("unlink failed with error %d", r); /* FIXME: cleanup empty directory */ return r; }