Commit 7647546c authored by jlaval's avatar jlaval
Browse files

Initial commit

parents
Copyright (c) 2011 Jérémie Laval
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
all: main.c
gcc -Wall -ggdb -o ticket-checker `pkg-config --cflags --libs libsoup-2.4 libsoup-gnome-2.4 glib-2.0 gtk+-3.0 pango openssl gmodule-export-2.0` -Wl,--export-dynamic main.c new_dialog.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <pango/pango-attributes.h>
#include <libsoup/soup.h>
#include <libsoup/soup-gnome.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include "new_dialog.h"
#define MAX_HASH_SIZE 4
GIOChannel* channel = NULL;
GtkWindow* window = NULL;
GtkLabel *firstNameLbl = NULL, *nameLbl = NULL, *nicknameLbl = NULL, *statusLbl = NULL;
/* Tells if we have access to a network and thus we can query for user infos */
gboolean is_offline = FALSE;
typedef enum {
HASH_ERROR_MALFORMED,
HASH_ERROR_WRONG
} hash_error_t;
SoupSession *session = NULL;
ticket_session_t *ticket_session = NULL;
HMAC_CTX* hmac_ctx = NULL;
static void initialize_soup ()
{
session = soup_session_async_new_with_options (
#ifdef HAVE_LIBSOUP_GNOME
SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_GNOME,
#endif
NULL);
}
static void shutdown_soup ()
{
soup_session_abort (session);
}
void on_window_destroy (GObject *object, gpointer user_data)
{
gtk_main_quit();
}
void on_menu_open (GObject *object, gpointer user_data)
{
gtk_main_quit();
}
static void setup_logger ()
{
if (channel != NULL) {
g_io_channel_flush (channel, NULL);
g_io_channel_close (channel);
channel = NULL;
}
channel = g_io_channel_new_file (ticket_session->event_name, "a", NULL);
if (channel == NULL) {
g_warning ("Careful, wasn't able to create log");
return;
}
}
static void log_ticket (const gchar* code)
{
if (channel == NULL)
return;
time_t curr_time = time (NULL);
const gchar* tm = ctime((const time_t*)&curr_time);
puts(tm);
g_io_channel_write_chars (channel, tm, strlen (tm), NULL, NULL);
g_io_channel_write_chars (channel, " ", 2, NULL, NULL);
g_io_channel_write_chars (channel, code, strlen (code), NULL, NULL);
g_io_channel_write_chars (channel, "\n", 1, NULL, NULL);
g_io_channel_flush (channel, NULL);
}
void on_menu_new (GObject *object, gpointer user_data)
{
ticket_session_t *temp = ask_for_new_session (window);
if (temp == NULL)
return;
g_message ("New informations: %s, %s, %s", temp->event_name, temp->secret, ((product_item*)temp->product_ids->data)->description);
if (ticket_session != NULL) {
g_free (temp->event_name);
g_free (temp->secret);
g_list_free_full (temp->product_ids, g_free);
g_free (ticket_session);
ticket_session = NULL;
}
ticket_session = temp;
setup_logger ();
}
void on_work_offline_toggled (GObject *object, gpointer user_data)
{
is_offline = !is_offline;
if (!is_offline)
shutdown_soup ();
}
static void report_error (hash_error_t error, const gchar* code)
{
g_warning ("Hash error '%s' with value %s",
error == HASH_ERROR_WRONG ? "Wrong hash" : "Incomplete hash",
code);
gchar* message = g_strdup_printf ("Erreur, %s", error == HASH_ERROR_WRONG ? "place invalide" : "code illisible");
gtk_label_set_text (statusLbl, message);
g_free (message);
/* Red */
PangoAttribute* attr = pango_attr_background_new (65535, 0, 0);
pango_attr_list_change (gtk_label_get_attributes (statusLbl), attr);
}
static void user_infos_callback (SoupSession *session, SoupMessage *msg, gpointer user_data)
{
g_message ("Got back, %d, %s", (gint)msg->response_body->length, msg->response_body->data);
if (msg->response_body->length == 0 || msg->response_body->data[0] == '0')
return;
const gchar* response = msg->response_body->data;
gchar** part = g_strsplit (response, "|^", 3);
gtk_label_set_text (firstNameLbl, part[0]);
gtk_label_set_text (nameLbl, part[1]);
gtk_label_set_text (nicknameLbl, part[2]);
g_message ("Got back with (%s, %s, %s)", part[0], part[1], part[2]);
g_strfreev (part);
}
static void fetch_user_infos (gint user)
{
g_message ("Fetching uinfos for %d", user);
gchar* safe_secret = soup_uri_encode (ticket_session->secret, "+");
gchar* url = g_strdup_printf ("https://ae.utbm.fr/taiste/gateway.php?module=eticket-ident&id_utilisateur=%d&secret=%s",
user,
safe_secret);
g_free (safe_secret);
SoupMessage* msg = soup_message_new ("GET", url);
soup_session_queue_message (session, msg, user_infos_callback, NULL);
}
static const gchar* find_product_desc (gint product)
{
GList* node = ticket_session->product_ids;
while (node != NULL) {
product_item *item = node->data;
if (item->id == product)
return item->description;
node = g_list_next (node);
}
return NULL;
}
static void report_success (const gchar* code, gint user, gint product, gint amount)
{
gchar* message = g_strdup_printf ("Succès, %d place(s)\n%s", amount, find_product_desc (product));
gtk_label_set_text (statusLbl, message);
g_free (message);
/* Green */
PangoAttribute* attr = pango_attr_background_new (0, 65535, 0);
pango_attr_list_change (gtk_label_get_attributes (statusLbl), attr);
if (!is_offline)
fetch_user_infos (user);
log_ticket (code);
}
static gchar* to_hex (unsigned char *md, gint amount)
{
int i;
gint size = MIN(SHA_DIGEST_LENGTH, amount);
char* buf = g_malloc0((size + 1) * 2);
for (i = 0; i < MIN(SHA_DIGEST_LENGTH, amount); i++)
sprintf (buf + i*2, "%02X", md[i]);
return buf;
}
gchar* compute_hash_for (const gchar* input, const gchar* secret)
{
if (hmac_ctx == NULL) {
hmac_ctx = g_new0 (HMAC_CTX, 1);
HMAC_CTX_init (hmac_ctx);
}
return to_hex(HMAC(EVP_sha1(), (guchar*)secret, strlen (secret), (guchar*)input, strlen (input), NULL, 0), MAX_HASH_SIZE);
}
static void verify_code (gchar* code)
{
if (ticket_session == NULL)
return;
gint user = -1, product = -1, quantity = -1;
gchar *iter = NULL, *save = code;
user = g_ascii_strtoll (save, &iter, 10);
if (user <= 0 || iter == NULL || *iter == '\0') {
report_error (HASH_ERROR_MALFORMED, code);
return;
}
save = iter + 1;
product = g_ascii_strtoll (save, &iter, 10);
if (product <= 0 || iter == NULL || *iter == '\0') {
report_error (HASH_ERROR_MALFORMED, code);
return;
}
save = iter + 1;
quantity = g_ascii_strtoll (save, &iter, 10);
if (quantity <= 0 || iter == NULL || *iter == '\0') {
report_error (HASH_ERROR_MALFORMED, code);
return;
}
/* Not the right product */
if (find_product_desc (product) == NULL) {
report_error (HASH_ERROR_MALFORMED, code);
return;
}
gchar *hash = iter + 1;
gchar *input = g_strndup (code, iter - code);
g_message ("Args gathering: (%d, %d, %d), [%s] and [%s]", user, product, quantity, input, hash);
gchar* hmac = compute_hash_for (input, ticket_session->secret);
g_free (input);
g_message ("Computed HMAC is %s", hmac);
if (g_strcmp0 (hmac, hash) == 0)
report_success (code, user, product, quantity);
else
report_error (HASH_ERROR_WRONG, code);
g_free (hmac);
}
void on_code_entry_activate (GObject *object, gpointer user_data)
{
GtkEntry *entry = GTK_ENTRY (object);
gchar* code = g_strdup(gtk_entry_get_text(entry));
gtk_entry_buffer_delete_text(gtk_entry_get_buffer (entry), 0, -1);
verify_code (code);
g_free (code);
}
void authenticate_callback (SoupSession *session,
SoupMessage *msg,
SoupAuth *auth,
gboolean retrying,
gpointer user_data)
{
soup_auth_authenticate (auth, "jlaval", "barte12");
}
/*static void init_dummy_ticket_ctx ()
{
ticket_session = g_new0(ticket_session_t, 1);
ticket_session->secret = "+QtPXPlJSNcPMNR7Kltk2dwlvH/3Nrraw8MiQuihyv8=";
ticket_session->product_ids = g_list_append (ticket_session->product_ids, GINT_TO_POINTER (578));
g_signal_connect (G_OBJECT (session), "authenticate", G_CALLBACK (authenticate_callback), NULL);
}*/
int main (int argc, char *argv[])
{
GtkBuilder *builder = NULL;
gtk_init (&argc, &argv);
initialize_soup ();
//init_dummy_ticket_ctx ();
g_signal_connect (G_OBJECT (session), "authenticate", G_CALLBACK (authenticate_callback), NULL);
builder = gtk_builder_new ();
gtk_builder_add_from_file (builder, "ticketchecker.glade", NULL);
window = GTK_WINDOW (gtk_builder_get_object (builder, "mainWindow"));
firstNameLbl = GTK_LABEL (gtk_builder_get_object (builder, "firstNameLbl"));
nameLbl = GTK_LABEL (gtk_builder_get_object (builder, "nameLbl"));
nicknameLbl = GTK_LABEL (gtk_builder_get_object (builder, "nicknameLbl"));
statusLbl = GTK_LABEL (gtk_builder_get_object (builder, "statusLbl"));
g_signal_connect_swapped (G_OBJECT (window),
"delete-event",
G_CALLBACK (gtk_main_quit),
window);
gtk_builder_connect_signals (builder, NULL);
g_object_unref (G_OBJECT (builder));
gtk_widget_show (GTK_WIDGET (window));
gtk_main ();
return 0;
}
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <gtk/gtk.h>
#include "new_dialog.h"
GtkDialog *dialog = NULL;
GtkListStore *store = NULL;
GtkEntry *evtNameEntry = NULL, *idEntry = NULL, *nameEntry = NULL, *secretEntry = NULL;
GtkTreeView* view = NULL;
void on_list_name_changed (GtkCellRendererText *renderer,
gchar *path,
gchar *new_text,
gpointer user_data)
{
GtkTreeIter iter;
if (!gtk_tree_model_get_iter (GTK_TREE_MODEL(store), &iter, gtk_tree_path_new_from_string (path)))
return;
if (!gtk_list_store_iter_is_valid (store, &iter))
return;
gtk_list_store_set (store, &iter, 1, new_text, -1);
}
void on_add_button_clicked (GObject *object, gpointer user_data)
{
GtkTreeIter iter;
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, gtk_entry_get_text (idEntry),1, gtk_entry_get_text (nameEntry), -1);
}
void on_delete_button_clicked (GObject *object, gpointer user_data)
{
GtkTreeIter iter;
GtkTreeSelection *selection = gtk_tree_view_get_selection (view);
if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
return;
if (!gtk_list_store_iter_is_valid (store, &iter))
return;
gtk_list_store_remove (store, &iter);
}
gboolean list_model_foreach_func (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer data)
{
GList **node = data;
GValue id = { 0 }, name = { 0 };
gtk_tree_model_get_value (GTK_TREE_MODEL (store), iter, 0, g_value_init (&id, G_TYPE_STRING));
gtk_tree_model_get_value (GTK_TREE_MODEL (store), iter, 1, g_value_init (&name, G_TYPE_STRING));
product_item *item = g_new0(product_item, 1);
item->id = g_ascii_strtoll (g_value_get_string (&id), NULL, 10);
item->description = g_strdup (g_value_get_string (&name));
g_value_unset (&id);
g_value_unset (&name);
*node = g_list_prepend (*node, item);
return FALSE;
}
ticket_session_t* ask_for_new_session (GtkWindow* parent)
{
GtkBuilder *builder = NULL;
ticket_session_t *session = NULL;
builder = gtk_builder_new ();
gtk_builder_add_from_file (builder, "newcontext.glade", NULL);
dialog = GTK_DIALOG (gtk_builder_get_object (builder, "newDialog"));
store = GTK_LIST_STORE (gtk_builder_get_object (builder, "productStore"));
evtNameEntry = GTK_ENTRY (gtk_builder_get_object (builder, "evtNameEntry"));
idEntry = GTK_ENTRY (gtk_builder_get_object (builder, "idEntry"));
nameEntry = GTK_ENTRY (gtk_builder_get_object (builder, "nameEntry"));
secretEntry = GTK_ENTRY (gtk_builder_get_object (builder, "secretEntry"));
view = GTK_TREE_VIEW (gtk_builder_get_object (builder, "prodTv"));
gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view), GTK_SELECTION_SINGLE);
gtk_builder_connect_signals (builder, NULL);
g_object_unref (G_OBJECT (builder));
gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
gint result = gtk_dialog_run (dialog);
switch (result) {
case GTK_RESPONSE_APPLY:
session = g_new0(ticket_session_t, 1);
session->secret = g_strdup (gtk_entry_get_text (secretEntry));
session->event_name = g_strdup (gtk_entry_get_text (evtNameEntry));
gtk_tree_model_foreach (GTK_TREE_MODEL (store), list_model_foreach_func, &session->product_ids);
break;
default:
break;
}
gtk_widget_destroy (GTK_WIDGET (dialog));
return session;
}
#ifndef NEW_DIALOG_H
#define NEW_DIALOG_H
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <gtk/gtk.h>
typedef struct {
gint id;
const gchar* description;
} product_item;
typedef struct {
gchar* event_name;
gchar* secret; /* Contains the secret that's used in HMAC computation */
GList *product_ids;
} ticket_session_t;
ticket_session_t* ask_for_new_session (GtkWindow* parent);
#endif
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<object class="GtkDialog" id="newDialog">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox1">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">20</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area1">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label">gtk-undo</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="apply_button">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">9</property>
<child>
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">12</property>
<property name="column_spacing">8</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Nom de l'évenement :</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="evtNameEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char"></property>
<property name="invisible_char_set">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Secret :</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="secretEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char"></property>
<property name="invisible_char_set">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">7</property>
<child>
<object class="GtkBox" id="box4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkBox" id="box5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">8</property>
<child>
<object class="GtkEntry" id="idEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char"></property>
<property name="truncate_multiline">True</property>
<property name="invisible_char_set">True</property>
<property name="caps_lock_warning">False</property>
</object>
<packing>