/* Copyright © Nils O.Selåsdal <NOS@Utel.no>
Comments/bug reports/anything goes to NOS@Utel.no
License is BSD, see the LICENSE file
*/
#include <sys/types.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <glade/glade.h>
#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 <NOS@Utel.no> \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;
}
syntax highlighted by Code2HTML, v. 0.9.1