/* Copyright © Nils O.Selåsdal Comments/bug reports/anything goes to NOS@Utel.no License is BSD, see the LICENSE file */ #include #include #include #include #include #include #include #include #include "ghasher.h" #include "hash.xpm" #include "ghasher-glade.c" static gboolean hash_some(struct SumContext *s_ctx); /*event/source id if running calculation(idle function) */ static gint current_idle_id = -1; const char *const hashfunctions[] = { "md2", "md4", "md5", "sha1", "sha", "ripemd160", "dss1" }; /*called when the calculation(idle function) is finished */ static void hash_idle_destroy(gpointer p) { struct GHApp *app = p; current_idle_id = -1; gtk_widget_set_sensitive(GTK_WIDGET(app->addbutton), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(app->digestdropdown), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(app->cancelbutton), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(app->clearbutton), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(app->digestbutton), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(app->savebutton), TRUE); //gtk_widget_set_sensitive(GTK_WIDGET(app->verifybutton), TRUE); } static void destroy_sum_context(struct SumContext *s_ctx) { unsigned int md_len; unsigned char b[EVP_MAX_MD_SIZE]; /*fun thought: OpenSSL decides to extend this, and ghasher */ char b_hex[EVP_MAX_MD_SIZE * 2 + 1]; /*is compiled with an older lib.*/ if (s_ctx == NULL) return; close(s_ctx->currentfile); md_len = 0; /*openssl prior to 0.9.7 needs a Final else we leak memory afaik, above 0.9.7 openssl provides a _cleanup function. */ #if OPENSSL_VERSION_NUMBER < 0x00907000L EVP_DigestFinal(&s_ctx->ctx, b, &md_len); #endif if (s_ctx->allok) { #if OPENSSL_VERSION_NUMBER >= 0x00907000L EVP_DigestFinal(&s_ctx->ctx, b, &md_len); #endif hex_dump(b, md_len, b_hex, md_len * 2 + 1); set_hashfield(GTK_TREE_VIEW(s_ctx->app->listview), b_hex, g_ptr_array_index(s_ctx->app->files, s_ctx->app->currentfile - 1)); } #if OPENSSL_VERSION_NUMBER >= 0x00907000L EVP_MD_CTX_cleanup(&s_ctx->ctx); #endif g_free(s_ctx); } struct SumContext *new_sum_context(struct GHApp *app, const gchar *filename) { struct SumContext *s_ctx; s_ctx = g_malloc0(sizeof(*s_ctx)); if (s_ctx == NULL || stat(filename, &s_ctx->statbuf) == -1) { gchar *error; error = g_strdup_printf("Failed: %s", g_strerror(errno)); set_hashfield(GTK_TREE_VIEW(app->listview), error, g_ptr_array_index(app->files, app->currentfile)); g_free(error); g_free(s_ctx); return NULL; } s_ctx->md = EVP_get_digestbyname(app->digesttype); if (s_ctx->md == NULL) { gchar *error; error = g_strdup_printf("Digest type '%s' is not supported.", app->digesttype); set_hashfield(GTK_TREE_VIEW(app->listview), error, g_ptr_array_index(app->files, app->currentfile)); g_free(error); g_free(s_ctx); return NULL; } EVP_DigestInit(&s_ctx->ctx, s_ctx->md); s_ctx->currentfile = open(filename, O_RDONLY); if (s_ctx->currentfile < 0) { gchar *error; error = g_strdup_printf("Failed: %s", g_strerror(errno)); set_hashfield(GTK_TREE_VIEW(app->listview), error, g_ptr_array_index(app->files, app->currentfile)); g_free(error); g_free(s_ctx); return NULL; } s_ctx->allok = FALSE; s_ctx->finished = FALSE; s_ctx->app = app; return s_ctx; } static gboolean hash_idle(gpointer p) { struct GHApp *app = p; if (app->currentsum == NULL || app->currentsum->finished) { gchar *filename; if ((gint) app->files->len <= app->currentfile) { destroy_sum_context(app->currentsum); app->currentsum = NULL; app->currentfile = 0; gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app->progress), "Digesting finished"); return FALSE; } filename = g_ptr_array_index(app->files, app->currentfile); destroy_sum_context(app->currentsum); app->currentsum = new_sum_context(app, filename); app->currentfile++; } if (app->currentsum) hash_some(app->currentsum); return TRUE; } #define BLOCKSZ 3 static gboolean hash_some(struct SumContext *s_ctx) { unsigned char buffer[s_ctx->statbuf.st_blksize * BLOCKSZ]; int cnt = 42; /*Meaning of life.*/ while (cnt--) { ssize_t ret; ret = read(s_ctx->currentfile, buffer, s_ctx->statbuf.st_blksize * BLOCKSZ); if (ret > 0) { EVP_DigestUpdate(&s_ctx->ctx, buffer, (unsigned long) ret); s_ctx->read_acc += (off_t) ret; } else if (ret == 0) { s_ctx->allok = TRUE; s_ctx->finished = TRUE; gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(s_ctx->app->progress), 1.0); } else if (ret == -1) { gchar *error; if (errno == EINTR) { return TRUE; } error = g_strdup_printf("Failed: %s", g_strerror(errno)); set_hashfield(GTK_TREE_VIEW(s_ctx->app->listview), error, g_ptr_array_index(s_ctx->app->files, s_ctx->app->currentfile - 1)); g_free(error); s_ctx->finished = TRUE; } if (s_ctx->finished) return FALSE; } gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(s_ctx->app->progress), (float) s_ctx->read_acc / (float) s_ctx->statbuf.st_size); return TRUE; } static void clearbutton_clicked(GtkButton *button, struct GHApp *app) { g_ptr_array_free(app->files,TRUE); app->files = g_ptr_array_new(); app->currentfile = 0; fill_list_view(GTK_TREE_VIEW(app->listview), app->files); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app->progress), "..."); gtk_widget_set_sensitive(GTK_WIDGET(app->addbutton), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(app->digestdropdown), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(app->digestbutton), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(app->cancelbutton), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(app->clearbutton), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(app->savebutton), FALSE); //gtk_widget_set_sensitive(GTK_WIDGET(app->verifybutton), FALSE); } static void savebutton_clicked(GtkButton *button, struct GHApp *app) { GtkWidget *file_selector; gint ret; static gchar *lastdir = NULL; file_selector = gtk_file_chooser_dialog_new("Select a file to digest. ", GTK_WINDOW(app->window), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_SAVE,GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,NULL); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_selector), TRUE); /*we only handle kernel filesystems*/ if (lastdir != NULL) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_selector), lastdir); ret = gtk_dialog_run(GTK_DIALOG(file_selector)); if (ret == GTK_RESPONSE_OK) { gchar *filename; FILE *f; filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_selector)); f = fopen(filename,"w"); if(f != NULL) { GString *sums; size_t w = 0; sums = listview_get_data(app); while(w < (size_t)sums->len){ size_t wt; wt = fwrite(sums->str+w,1,sums->len-w,f); if(wt <= 0 || ferror(f)){ show_error_dialog(app->window,"Writing '%s' failed:\n%s",filename,g_strerror(errno)); break; } w += wt; } g_string_free(sums,TRUE); fclose(f); } else show_error_dialog(app->window,"Writing '%s' failed:\n%s",filename,g_strerror(errno)); g_free(filename); g_free(lastdir); lastdir = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(file_selector)); } gtk_widget_destroy(file_selector); } static void digestbutton_clicked(GtkButton *button, struct GHApp *app) { fill_list_view(GTK_TREE_VIEW(app->listview), app->files); current_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, hash_idle, app, hash_idle_destroy); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app->progress), "Digesting..."); gtk_widget_set_sensitive(GTK_WIDGET(app->addbutton), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(app->digestdropdown), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(app->digestbutton), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(app->cancelbutton), TRUE); //gtk_widget_set_sensitive(GTK_WIDGET(app->verifybutton), FALSE); } static void cancelbutton_clicked(GtkButton *button, struct GHApp *app) { if (current_idle_id != -1) { /*wtf ?*/ g_source_remove(current_idle_id); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app->progress), "Digesting cancelled"); destroy_sum_context(app->currentsum); app->currentfile = 0; app->currentsum = NULL; } } static void addbutton_clicked(GtkButton *button, gpointer userdata) { struct GHApp *app = userdata; GtkWidget *file_selector; gint ret; static gchar *lastdir = NULL; /*So, anything to eat in this joint*/ file_selector = gtk_file_chooser_dialog_new("Select a file to digest. ", GTK_WINDOW(app->window), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_ADD,GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,NULL); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_selector), TRUE); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_selector), TRUE); /*we only handle kernel filesystems*/ if (lastdir != NULL) { gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_selector), lastdir); } /*Hmm, let me see the menu please */ ret = gtk_dialog_run(GTK_DIALOG(file_selector)); if (ret == GTK_RESPONSE_OK) { GSList *filelist, *tmp; /*Can you please take my order now ?*/ filelist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_selector)); tmp = filelist; if(tmp){ gtk_widget_set_sensitive(GTK_WIDGET(app->digestbutton), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(app->clearbutton), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(app->savebutton), FALSE); //gtk_widget_set_sensitive(GTK_WIDGET(app->verifybutton), FALSE); } while (tmp) { g_ptr_array_add(app->files, tmp->data); tmp = tmp->next; } g_slist_free(filelist); app->currentfile = 0; fill_list_view(GTK_TREE_VIEW(app->listview), app->files); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app->progress), "..."); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(app->progress), 0.0); g_free(lastdir); lastdir = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(file_selector)); } /*Let me have that menu back please.*/ gtk_widget_destroy(file_selector); } static void setup_window_icon(GtkWidget *window) { GdkPixbuf *icon; icon = gdk_pixbuf_new_from_xpm_data((const char **) hash_xpm); if (icon != NULL) { gtk_window_set_icon(GTK_WINDOW(window), icon); g_object_unref(icon); } } static void print_usage() { g_print("Copyright © Nils O.Selåsdal \n"); g_print("Usage: ghasher [-h] [-d type] [filename ...]\n"); g_print("\t-d calculate 'type' digest where 'type' is md2|md4|md5|sha1|sha|ripemd160|dss1\n"); g_print("\t-h this text\n"); g_print("\tfilename(s) name of file(s) to digest\n"); } static void parse_options(int argc, char *argv[], struct GHApp *app) { int c; app->digesttype = "md5"; /*default is md5*/ app->files = g_ptr_array_new(); app->currentfile = 0; app->currentsum = NULL; while ((c = getopt(argc, argv, "d:h")) != -1) { switch (c) { case 'h': print_usage(); exit(1); case 'd': app->digesttype = optarg; break; case '?': break; default: g_print("Unknown option %c\n", c); break; } } while (optind < argc) { g_ptr_array_add(app->files, g_strdup(argv[optind])); optind++; } } /*A hack: */ static gboolean start_hash(gpointer userdata) { struct GHApp *app = userdata; g_signal_emit_by_name(G_OBJECT(app->digestbutton), "clicked", app, NULL); return TRUE; } static void digest_changed(GtkComboBox *box, gpointer userdata) { struct GHApp *app = userdata; gint index; g_object_get(G_OBJECT(box), "active", &index, NULL); app->digesttype = hashfunctions[index]; fill_list_view(GTK_TREE_VIEW(app->listview),app->files); } static GtkWidget *hashdropdown(struct GHApp *app, GtkWidget *combo) { guint i; for (i = 0; i < G_N_ELEMENTS(hashfunctions); i++) { gtk_combo_box_append_text(GTK_COMBO_BOX(combo), hashfunctions[i]); if (!g_ascii_strncasecmp(app->digesttype, hashfunctions[i], MIN(strlen(app->digesttype), strlen(hashfunctions[i])))) g_object_set(G_OBJECT(combo), "active", i, NULL); } g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(digest_changed), app); return combo; } int main(int argc, char *argv[]) { struct GHApp app; GladeXML *xml; gtk_init(&argc, &argv); parse_options(argc, argv, &app); OpenSSL_add_all_digests(); xml = glade_xml_new_from_buffer(ui,strlen(ui),"mainWindow",NULL); app.window = glade_xml_get_widget(xml, "mainWindow"); g_signal_connect(G_OBJECT(app.window), "delete-event", G_CALLBACK(gtk_main_quit), NULL); app.digestdropdown = hashdropdown(&app,glade_xml_get_widget(xml, "digestdropdown")); app.listview = create_list_view(glade_xml_get_widget(xml, "treeview")); app.progress = glade_xml_get_widget(xml, "progress"); app.addbutton = glade_xml_get_widget(xml, "addtool"); g_signal_connect(G_OBJECT(app.addbutton), "clicked", G_CALLBACK(addbutton_clicked), &app); app.digestbutton = glade_xml_get_widget(xml, "digesttool"); g_signal_connect(G_OBJECT(app.digestbutton), "clicked", G_CALLBACK(digestbutton_clicked), &app); app.cancelbutton = glade_xml_get_widget(xml, "canceltool"); g_signal_connect(G_OBJECT(app.cancelbutton), "clicked", G_CALLBACK(cancelbutton_clicked), &app); app.clearbutton = glade_xml_get_widget(xml, "cleartool"); g_signal_connect(G_OBJECT(app.clearbutton), "clicked", G_CALLBACK(clearbutton_clicked), &app); app.savebutton = glade_xml_get_widget(xml, "savetool"); g_signal_connect(G_OBJECT(app.savebutton), "clicked", G_CALLBACK(savebutton_clicked), &app); app.pathbutton = glade_xml_get_widget(xml, "pathtool"); g_signal_connect(G_OBJECT(app.pathbutton), "toggled", G_CALLBACK(pathbutton_toggled), &app); //app.verifybutton = glade_xml_get_widget(xml, "verifytool"); //g_signal_connect(G_OBJECT(app.verifybutton), "clicked", G_CALLBACK(verifybutton_clicked), &app); g_signal_connect(G_OBJECT(glade_xml_get_widget(xml, "quittool")), "clicked", G_CALLBACK(gtk_main_quit), NULL); gtk_widget_show_all(app.window); setup_window_icon(app.window); if (app.files->len > 0) gtk_init_add(start_hash, &app); g_object_unref(xml); gtk_main(); return 0; }