diff --git a/src/conf.c b/src/conf.c index c0e7528..575a77f 100644 --- a/src/conf.c +++ b/src/conf.c @@ -29,9 +29,11 @@ static void pusb_conf_options_get_from(t_pusb_options *opts, pusb_xpath_get_string_from(doc, from, "option[@name='hostname']", opts->hostname, sizeof(opts->hostname)); pusb_xpath_get_string_from(doc, from, "option[@name='system_otp_directory']", - opts->system_otp_directory, sizeof(opts->system_otp_directory)); + opts->system_otp_directory, + sizeof(opts->system_otp_directory)); pusb_xpath_get_string_from(doc, from, "option[@name='device_otp_directory']", - opts->device_otp_directory, sizeof(opts->device_otp_directory)); + opts->device_otp_directory, + sizeof(opts->device_otp_directory)); pusb_xpath_get_bool_from(doc, from, "option[@name='debug']", &(opts->debug)); pusb_xpath_get_bool_from(doc, from, "option[@name='enable']", @@ -112,6 +114,9 @@ static int pusb_conf_parse_device(t_pusb_options *opts, xmlDoc *doc) if (!pusb_conf_device_get_property(opts, doc, "serial", opts->device.serial, sizeof(opts->device.serial))) return (0); + pusb_conf_device_get_property(opts, doc, "volume_uuid", + opts->device.volume_uuid, + sizeof(opts->device.volume_uuid)); return (1); } @@ -136,14 +141,14 @@ int pusb_conf_init(t_pusb_options *opts) static void pusb_conf_dump(t_pusb_options *opts) { log_debug("Configuration dump:\n"); - log_debug("enable:\t\t\t%d\n", opts->enable); - log_debug("probe_timeout:\t\t%d\n", opts->probe_timeout); - log_debug("try_otp:\t\t\t%d\n", opts->try_otp); - log_debug("enforce_otp:\t\t%d\n", opts->enforce_otp); - log_debug("debug:\t\t\t%d\n", opts->debug); - log_debug("hostname:\t\t%s\n", opts->hostname); - log_debug("system_otp_directory:\t%s\n", opts->system_otp_directory); - log_debug("device_otp_directory:\t%s\n", opts->device_otp_directory); + log_debug("enable\t\t\t: %s\n", opts->enable ? "true" : "false"); + log_debug("debug\t\t\t: %s\n", opts->debug ? "true" : "false"); + log_debug("try_otp\t\t\t: %s\n", opts->try_otp ? "true" : "false"); + log_debug("enforce_otp\t\t: %s\n", opts->enforce_otp ? "true" : "false"); + log_debug("probe_timeout\t\t: %d\n", opts->probe_timeout); + log_debug("hostname\t\t\t: %s\n", opts->hostname); + log_debug("system_otp_directory\t: %s\n", opts->system_otp_directory); + log_debug("device_otp_directory\t: %s\n", opts->device_otp_directory); } diff --git a/src/conf.h b/src/conf.h index 0345a18..b805e68 100644 --- a/src/conf.h +++ b/src/conf.h @@ -28,6 +28,7 @@ typedef struct pusb_device char vendor[32]; char model[32]; char serial[64]; + char volume_uuid[32]; } t_pusb_device; typedef struct pusb_options diff --git a/src/conf.xml b/src/conf.xml index 836dea7..2073046 100644 --- a/src/conf.xml +++ b/src/conf.xml @@ -1,27 +1,20 @@ - + - SanDisk - Cruzer Titanium - SanDisk_Cruzer_Titanium_SNDKB882652FC4A03701 - - - - - SanDisk Corp. Cruzer Titanium SNDKB882652FC4A03701 + 3B69-1AFD @@ -32,7 +25,7 @@ - foobar2 + foobar diff --git a/src/device.c b/src/device.c index b6fa18e..d00c41f 100644 --- a/src/device.c +++ b/src/device.c @@ -17,6 +17,7 @@ #include #include +#include #include #include "conf.h" #include "hal.h" @@ -24,73 +25,51 @@ #include "otp.h" #include "device.h" -static LibHalDrive *pusb_device_get_storage(t_pusb_options *opts, LibHalContext *ctx, - const char *udi) +static int pusb_device_connected(t_pusb_options *opts, LibHalContext *ctx) { - char *phy_udi = NULL; - char *storage_udi = NULL; - int maxloop = 0; - LibHalDrive *drive = NULL; + char *udi = NULL; - log_info("Probing storage device (this could take a while)...\n"); - while (!(phy_udi = pusb_hal_find_item(ctx, - "info.parent", udi, - "info.bus", "usb", - NULL))) - usleep(250000); - maxloop = ((opts->probe_timeout * 1000000) / 250000); - while (maxloop > 0 && - (!(storage_udi = pusb_hal_find_item(ctx, - "storage.physical_device", phy_udi, - "info.category", "storage", - NULL)) || strstr(storage_udi, "temp"))) - { - if (storage_udi) - libhal_free_string(storage_udi); - --maxloop; - usleep(250000); - } - libhal_free_string(phy_udi); - if (storage_udi) + log_debug("Searching for \"%s\" in the hardware database...\n", + opts->device.name); + udi = pusb_hal_find_item(ctx, + "usb_device.serial", opts->device.serial, + "usb_device.vendor", opts->device.vendor, + "info.product", opts->device.model, + NULL); + if (!udi) { - drive = libhal_drive_from_udi(ctx, storage_udi); - libhal_free_string(storage_udi); + log_error("Device \"%s\" is not connected.\n", + opts->device.name); + return (0); } - return (drive); + libhal_free_string(udi); + log_info("Device \"%s\" is connected (good).\n", opts->device.name); + return (1); } int pusb_device_check(t_pusb_options *opts) { DBusConnection *dbus = NULL; LibHalContext *ctx = NULL; - LibHalDrive *drive = NULL; - char *udi = NULL; int retval = 0; log_debug("Connecting to HAL...\n"); if (!(dbus = pusb_hal_dbus_connect())) return (0); + if (!(ctx = pusb_hal_init(dbus))) { pusb_hal_dbus_disconnect(dbus); return (0); } - log_debug("Searching for \"%s\" in the hardware database...\n", - opts->device.name); - udi = pusb_hal_find_item(ctx, - "usb_device.serial", opts->device.serial, - "usb_device.vendor", opts->device.vendor, - "info.product", opts->device.model, - NULL); - if (!udi) + + if (!pusb_device_connected(opts, ctx)) { - log_error("Device \"%s\" is not connected.\n", - opts->device.name); pusb_hal_dbus_disconnect(dbus); libhal_ctx_free(ctx); return (0); } - log_info("Device \"%s\" is connected (good).\n", opts->device.name); + if (!opts->try_otp && !opts->enforce_otp) { log_debug("One time pad is disabled, no more verifications to do.\n"); @@ -99,12 +78,9 @@ int pusb_device_check(t_pusb_options *opts) else { log_info("Performing one time pad verification...\n"); - if (!(drive = pusb_device_get_storage(opts, ctx, udi))) - retval = !opts->enforce_otp; - else - retval = pusb_otp_check(opts, ctx, drive); + retval = pusb_otp_check(opts, ctx); } - libhal_free_string(udi); + pusb_hal_dbus_disconnect(dbus); libhal_ctx_free(ctx); return (retval); diff --git a/src/device.h b/src/device.h index 05b07dc..a6a162f 100644 --- a/src/device.h +++ b/src/device.h @@ -18,6 +18,6 @@ #ifndef PUSB_DEVICE_H_ # define PUSB_DEVICE_H_ -int pusb_device_check(t_pusb_options *opts); +int pusb_device_check(t_pusb_options *opts); #endif /* !PUSB_DEVICE_H_ */ diff --git a/src/hal.c b/src/hal.c index 7c18252..02026fa 100644 --- a/src/hal.c +++ b/src/hal.c @@ -37,17 +37,17 @@ DBusConnection *pusb_hal_dbus_connect(void) return (dbus); } -void pusb_hal_dbus_disconnect(DBusConnection *dbus) +void pusb_hal_dbus_disconnect(DBusConnection *dbus) { dbus_connection_close(dbus); dbus_connection_unref(dbus); dbus_shutdown(); } -LibHalContext *pusb_hal_init(DBusConnection *dbus) +LibHalContext *pusb_hal_init(DBusConnection *dbus) { - DBusError error; - LibHalContext *ctx = NULL; + DBusError error; + LibHalContext *ctx = NULL; dbus_error_init(&error); if (!(ctx = libhal_ctx_new())) @@ -70,17 +70,17 @@ LibHalContext *pusb_hal_init(DBusConnection *dbus) return (ctx); } -void pusb_hal_destroy(LibHalContext *ctx) +void pusb_hal_destroy(LibHalContext *ctx) { libhal_ctx_free(ctx); } -char *pusb_hal_get_property(LibHalContext *ctx, - const char *udi, - const char *name) +char *pusb_hal_get_property(LibHalContext *ctx, + const char *udi, + const char *name) { - DBusError error; - char *data; + DBusError error; + char *data; dbus_error_init(&error); data = libhal_device_get_property_string(ctx, udi, @@ -94,13 +94,13 @@ char *pusb_hal_get_property(LibHalContext *ctx, return (data); } -int pusb_hal_check_property(LibHalContext *ctx, - const char *udi, - const char *name, - const char *value) +int pusb_hal_check_property(LibHalContext *ctx, + const char *udi, + const char *name, + const char *value) { - char *data; - int retval; + char *data; + int retval; data = pusb_hal_get_property(ctx, udi, name); if (!data) @@ -163,7 +163,7 @@ char *pusb_hal_find_item(LibHalContext *ctx, for (i = 0; i < n_devices; ++i) { char *key = NULL; - int match = 0; + int match = 1; va_start(ap, value); while ((key = va_arg(ap, char *))) diff --git a/src/hal.h b/src/hal.h index 22ade4b..274bdd7 100644 --- a/src/hal.h +++ b/src/hal.h @@ -18,20 +18,20 @@ #ifndef PUSB_HAL_H_ # define PUSB_HAL_H_ -DBusConnection *pusb_hal_dbus_connect(void); -void pusb_hal_dbus_disconnect(DBusConnection *dbus); -LibHalContext *pusb_hal_init(DBusConnection *dbus); -void pusb_hal_destroy(LibHalContext *ctx); -char *pusb_hal_get_property(LibHalContext *ctx, - const char *udi, - const char *name); -int pusb_hal_check_property(LibHalContext *ctx, - const char *udi, - const char *name, - const char *value); -char *pusb_hal_find_item(LibHalContext *ctx, - const char *property, - const char *value, - ...); +DBusConnection *pusb_hal_dbus_connect(void); +void pusb_hal_dbus_disconnect(DBusConnection *dbus); +LibHalContext *pusb_hal_init(DBusConnection *dbus); +void pusb_hal_destroy(LibHalContext *ctx); +char *pusb_hal_get_property(LibHalContext *ctx, + const char *udi, + const char *name); +int pusb_hal_check_property(LibHalContext *ctx, + const char *udi, + const char *name, + const char *value); +char *pusb_hal_find_item(LibHalContext *ctx, + const char *property, + const char *value, + ...); #endif /* !PUSB_HAL_H_ */ diff --git a/src/log.h b/src/log.h index ad6b6b9..f8e8400 100644 --- a/src/log.h +++ b/src/log.h @@ -17,9 +17,9 @@ #ifndef PUSB_LOG_H_ # define PUSB_LOG_H_ +# define log_debug(s, ...) __log_debug(__FILE__, __LINE__, s, ##__VA_ARGS__) void __log_debug(const char *file, int line, const char *fmt, ...); -#define log_debug(s, ...) __log_debug(__FILE__, __LINE__, s, ##__VA_ARGS__) void log_error(const char *fmt, ...); void log_info(const char *fmt, ...); diff --git a/src/otp.c b/src/otp.c index 9823c94..0b78f1f 100644 --- a/src/otp.c +++ b/src/otp.c @@ -28,8 +28,8 @@ #include "volume.h" #include "otp.h" -static FILE *pusb_otp_open_device(t_pusb_options *opts, LibHalVolume *volume, - const char *mode) +static FILE *pusb_otp_open_device(t_pusb_options *opts, + LibHalVolume *volume, const char *mode) { FILE *f; char *path; @@ -39,8 +39,8 @@ static FILE *pusb_otp_open_device(t_pusb_options *opts, LibHalVolume *volume, mnt_point = (char *)libhal_volume_get_mount_point(volume); if (!mnt_point) return (NULL); - path_size = strlen(mnt_point) + 1 + strlen(opts->device_otp_directory) + 1 + \ - strlen(opts->hostname) + strlen(".otp") + 1; + path_size = strlen(mnt_point) + 1 + strlen(opts->device_otp_directory) + \ + 1 + strlen(opts->hostname) + strlen(".otp") + 1; if (!(path = malloc(path_size))) { log_error("malloc error!\n"); @@ -59,7 +59,7 @@ static FILE *pusb_otp_open_device(t_pusb_options *opts, LibHalVolume *volume, return (f); } -static FILE *pusb_otp_open_system(t_pusb_options *opts, const char *mode) +static FILE *pusb_otp_open_system(t_pusb_options *opts, const char *mode) { FILE *f; char *path; @@ -85,12 +85,13 @@ static FILE *pusb_otp_open_system(t_pusb_options *opts, const char *mode) return (f); } -static void pusb_otp_update(t_pusb_options *opts, LibHalVolume *volume) +static void pusb_otp_update(t_pusb_options *opts, + LibHalVolume *volume) { - FILE *f_device = NULL; - FILE *f_system = NULL; - int magic[1024]; - int i; + FILE *f_device = NULL; + FILE *f_system = NULL; + int magic[1024]; + int i; if (!(f_device = pusb_otp_open_device(opts, volume, "w+"))) { @@ -118,13 +119,13 @@ static void pusb_otp_update(t_pusb_options *opts, LibHalVolume *volume) log_debug("One time pads updated.\n"); } -static int pusb_otp_compare(t_pusb_options *opts, LibHalVolume *volume) +static int pusb_otp_compare(t_pusb_options *opts, LibHalVolume *volume) { - FILE *f_device = NULL; - FILE *f_system = NULL; - int magic_device[1024]; - int magic_system[1024]; - int retval; + FILE *f_device = NULL; + FILE *f_system = NULL; + int magic_device[1024]; + int magic_system[1024]; + int retval; if (!(f_system = pusb_otp_open_system(opts, "r"))) return (1); @@ -134,22 +135,23 @@ static int pusb_otp_compare(t_pusb_options *opts, LibHalVolume *volume) return (0); } log_debug("Loading device pad...\n"); - fread(magic_device, sizeof(int), sizeof(magic_device) / sizeof(int), f_device); + fread(magic_device, sizeof(int), sizeof(magic_device) / sizeof(int), + f_device); log_debug("Loading system pad...\n"); - fread(magic_system, sizeof(int), sizeof(magic_system) / sizeof(int), f_system); + fread(magic_system, sizeof(int), sizeof(magic_system) / sizeof(int), + f_system); retval = memcmp(magic_system, magic_device, sizeof(magic_system)); fclose(f_system); fclose(f_device); return (retval == 0); } -int pusb_otp_check(t_pusb_options *opts, LibHalContext *ctx, - LibHalDrive *drive) +int pusb_otp_check(t_pusb_options *opts, LibHalContext *ctx) { LibHalVolume *volume = NULL; int retval; - volume = pusb_volume_find(opts, ctx, drive); + volume = pusb_volume_get(opts, ctx); if (!volume) return (!opts->enforce_otp); retval = pusb_otp_compare(opts, volume); diff --git a/src/otp.h b/src/otp.h index b8a02b2..91e40e2 100644 --- a/src/otp.h +++ b/src/otp.h @@ -18,7 +18,6 @@ #ifndef PUSB_OTP_H_ # define PUSB_OTP_H_ -int pusb_otp_check(t_pusb_options *opts, LibHalContext *ctx, - LibHalDrive *drive); +int pusb_otp_check(t_pusb_options *opts, LibHalContext *ctx); #endif /* !PUSB_OTP_H_ */ diff --git a/src/test.c b/src/test.c index 56a1018..a7910b2 100644 --- a/src/test.c +++ b/src/test.c @@ -20,7 +20,7 @@ #include "log.h" #include "device.h" -int main(int argc, char **argv) +int main(int argc, char **argv) { t_pusb_options opts; int retval; diff --git a/src/volume.c b/src/volume.c index a927307..1f6c4a9 100644 --- a/src/volume.c +++ b/src/volume.c @@ -25,14 +25,16 @@ #include #include "conf.h" #include "log.h" +#include "hal.h" #include "volume.h" -static int pusb_volume_mount(t_pusb_options *opts, LibHalVolume **volume, - LibHalContext *ctx) +static int pusb_volume_mount(t_pusb_options *opts, LibHalVolume **volume, + LibHalContext *ctx) { char command[1024]; char tempname[32]; const char *devname; + const char *udi; snprintf(tempname, sizeof(tempname), "pam_usb%d", getpid()); if (!(devname = libhal_volume_get_device_file(*volume))) @@ -50,93 +52,74 @@ static int pusb_volume_mount(t_pusb_options *opts, LibHalVolume **volume, log_error("Mount failed\n"); return (0); } - else + udi = libhal_volume_get_udi(*volume); + if (!udi) { - const char *udi; - - udi = libhal_volume_get_udi(*volume); - if (!udi) - { - log_error("Unable to retrieve volume UDI\n"); - return (0); - } - udi = strdup(udi); - libhal_volume_free(*volume); - *volume = libhal_volume_from_udi(ctx, udi); - free((char *)udi); + log_error("Unable to retrieve volume UDI\n"); + return (0); } + udi = strdup(udi); + libhal_volume_free(*volume); + *volume = libhal_volume_from_udi(ctx, udi); + free((char *)udi); log_debug("Mount succeeded.\n"); return (1); } -static int __pusb_volume_find(t_pusb_options *opts, LibHalContext *ctx, - LibHalDrive *drive, LibHalVolume **out) +static LibHalVolume *pusb_volume_probe(t_pusb_options *opts, + LibHalContext *ctx) { - char **volumes; - int n_volumes = 0; - int i; + LibHalVolume *volume = NULL; + int maxtries = 0; + int i; - *out = NULL; - volumes = libhal_drive_find_all_volumes(ctx, drive, &n_volumes); - if (!n_volumes) + log_debug("Searching for volume with uuid %s\n", opts->device.volume_uuid); + maxtries = ((opts->probe_timeout * 1000000) / 250000); + for (i = 0; i < maxtries; ++i) { - libhal_free_string_array(volumes); - log_debug("No volumes found\n"); - return (1); - } - for (i = 0; i < n_volumes; ++i) - { - LibHalVolume *volume; + char *udi = NULL; - volume = libhal_volume_from_udi(ctx, - volumes[i]); - if (!volume) - continue; - if (libhal_volume_should_ignore(volume)) + if (i == 1) + log_info("Probing volume (this could take a while)...\n"); + udi = pusb_hal_find_item(ctx, + "volume.uuid", opts->device.volume_uuid, + NULL); + if (!udi) { - libhal_volume_free(volume); + usleep(250000); continue; } - *out = volume; - libhal_free_string_array(volumes); - if (libhal_volume_is_mounted(volume)) - { - log_debug("Volume is already mounted\n"); - return (1); - } - else - { - if (pusb_volume_mount(opts, &volume, ctx)) - return (1); - return (0); - } + volume = libhal_volume_from_udi(ctx, udi); + libhal_free_string(udi); + if (!libhal_volume_should_ignore(volume)) + return (volume); libhal_volume_free(volume); + usleep(250000); } - libhal_free_string_array(volumes); - return (1); + return (NULL); } -LibHalVolume *pusb_volume_find(t_pusb_options *opts, LibHalContext *ctx, - LibHalDrive *drive) +LibHalVolume *pusb_volume_get(t_pusb_options *opts, LibHalContext *ctx) { - LibHalVolume *volume = NULL; - int maxtries = 0; - int i; + LibHalVolume *volume; - maxtries = ((opts->probe_timeout * 1000000) / 250000); - for (i = 0; i < maxtries; ++i) - { - log_debug("Waiting for volumes to come up...\n"); - if (!__pusb_volume_find(opts, ctx, drive, &volume)) - return (NULL); - if (volume) - break; - usleep(250000); - } - return (volume); + if (!(volume = pusb_volume_probe(opts, ctx))) + return (NULL); + log_debug("Found volume %s\n", opts->device.volume_uuid); + if (libhal_volume_is_mounted(volume)) + { + log_debug("Volume is already mounted.\n"); + return (volume); + } + if (!pusb_volume_mount(opts, &volume, ctx)) + { + libhal_volume_free(volume); + return (NULL); + } + return (volume); } -void pusb_volume_destroy(LibHalVolume *volume) +void pusb_volume_destroy(LibHalVolume *volume) { const char *mntpoint; diff --git a/src/volume.h b/src/volume.h index 991bef4..b9dadf0 100644 --- a/src/volume.h +++ b/src/volume.h @@ -18,8 +18,7 @@ #ifndef VOLUME_H_ # define VOLUME_H_ -LibHalVolume *pusb_volume_find(t_pusb_options *opts, LibHalContext *ctx, - LibHalDrive *drive); -void pusb_volume_destroy(LibHalVolume *volume); +LibHalVolume *pusb_volume_get(t_pusb_options *opts, LibHalContext *ctx); +void pusb_volume_destroy(LibHalVolume *volume); #endif /* !VOLUME_H_ */ diff --git a/src/xpath.c b/src/xpath.c index ffc95e2..0e19abd 100644 --- a/src/xpath.c +++ b/src/xpath.c @@ -80,14 +80,14 @@ int pusb_xpath_get_string(xmlDocPtr doc, const char *path, return (1); } -int pusb_xpath_get_string_from(xmlDocPtr doc, - const char *base, - const char *path, - char *value, size_t size) +int pusb_xpath_get_string_from(xmlDocPtr doc, + const char *base, + const char *path, + char *value, size_t size) { - char *xpath = NULL; - size_t xpath_size; - int retval; + char *xpath = NULL; + size_t xpath_size; + int retval; xpath_size = strlen(base) + strlen(path) + 1; if (!(xpath = malloc(xpath_size))) @@ -127,10 +127,10 @@ int pusb_xpath_get_bool(xmlDocPtr doc, const char *path, int *value) return (0); } -int pusb_xpath_get_bool_from(xmlDocPtr doc, - const char *base, - const char *path, - int *value) +int pusb_xpath_get_bool_from(xmlDocPtr doc, + const char *base, + const char *path, + int *value) { char *xpath = NULL; size_t xpath_size; @@ -151,9 +151,9 @@ int pusb_xpath_get_bool_from(xmlDocPtr doc, return (retval); } -int pusb_xpath_get_int(xmlDocPtr doc, const char *path, int *value) +int pusb_xpath_get_int(xmlDocPtr doc, const char *path, int *value) { - char ret[64]; /* strlen("false") + 1 */ + char ret[64]; /* strlen("false") + 1 */ if (!pusb_xpath_get_string(doc, path, ret, sizeof(ret))) return (0); @@ -161,10 +161,10 @@ int pusb_xpath_get_int(xmlDocPtr doc, const char *path, int *value) return (1); } -int pusb_xpath_get_int_from(xmlDocPtr doc, - const char *base, - const char *path, - int *value) +int pusb_xpath_get_int_from(xmlDocPtr doc, + const char *base, + const char *path, + int *value) { char *xpath = NULL; size_t xpath_size; diff --git a/src/xpath.h b/src/xpath.h index f7f5761..578ffcd 100644 --- a/src/xpath.h +++ b/src/xpath.h @@ -19,17 +19,17 @@ # define PUSB_XPATH_H_ # include -int pusb_xpath_get_string(xmlDocPtr doc, const char *path, char *value, - size_t size); -int pusb_xpath_get_bool(xmlDocPtr doc, const char *path, int *value); -int pusb_xpath_get_string_from(xmlDocPtr doc, const char *base, - const char *path, char *value, size_t size); -int pusb_xpath_get_bool_from(xmlDocPtr doc, const char *base, const char *path, - int *value); -int pusb_xpath_get_int(xmlDocPtr doc, const char *path, int *value); -int pusb_xpath_get_int_from(xmlDocPtr doc, - const char *base, - const char *path, - int *value); +int pusb_xpath_get_string(xmlDocPtr doc, const char *path, char *value, + size_t size); +int pusb_xpath_get_bool(xmlDocPtr doc, const char *path, int *value); +int pusb_xpath_get_string_from(xmlDocPtr doc, const char *base, + const char *path, char *value, size_t size); +int pusb_xpath_get_bool_from(xmlDocPtr doc, const char *base, + const char *path, int *value); +int pusb_xpath_get_int(xmlDocPtr doc, const char *path, int *value); +int pusb_xpath_get_int_from(xmlDocPtr doc, + const char *base, + const char *path, + int *value); #endif /* !PUSB_XPATH_H_ */