1 /*
2 * Copyright (C) 2009 - 2013 Vivien Malerba <malerba@gnome-db.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 /*#define DEBUG_WEB_PROV*/
21
22 #include <glib/gi18n-lib.h>
23 #include "gda-web-util.h"
24 #include <string.h>
25 #include "../reuseable/reuse-all.h"
26
27 /* Use the RSA reference implementation included in the RFC-1321, http://www.freesoft.org/CIE/RFC/1321/ */
28 #include "global.h"
29 #include "md5.h"
30
31 #ifdef HAVE_INTTYPES_H
32 #include <inttypes.h>
33 #endif
34
35 #define PAD_LEN 64 /* PAD length */
36 #define SIG_LEN 16 /* MD5 digest length */
37 /*
38 * From RFC 2104
39 */
40 static void
hmac_md5(uint8_t * text,int text_len,uint8_t * key,int key_len,uint8_t * hmac)41 hmac_md5 (uint8_t* text, /* pointer to data stream */
42 int text_len, /* length of data stream */
43 uint8_t* key, /* pointer to authentication key */
44 int key_len, /* length of authentication key */
45 uint8_t *hmac) /* returned hmac-md5 */
46 {
47 MD5_CTX md5c;
48 uint8_t k_ipad[PAD_LEN]; /* inner padding - key XORd with ipad */
49 uint8_t k_opad[PAD_LEN]; /* outer padding - key XORd with opad */
50 uint8_t keysig[SIG_LEN];
51 int i;
52
53 /* if key is longer than PAD length, reset it to key=MD5(key) */
54 if (key_len > PAD_LEN) {
55 MD5_CTX md5key;
56
57 MD5Init (&md5key);
58 MD5Update (&md5key, key, key_len);
59 MD5Final (keysig, &md5key);
60
61 key = keysig;
62 key_len = SIG_LEN;
63 }
64
65 /*
66 * the HMAC_MD5 transform looks like:
67 *
68 * MD5(Key XOR opad, MD5(Key XOR ipad, text))
69 *
70 * where Key is an n byte key
71 * ipad is the byte 0x36 repeated 64 times
72
73 * opad is the byte 0x5c repeated 64 times
74 * and text is the data being protected
75 */
76
77 /* Zero pads and store key */
78 memset (k_ipad, 0, PAD_LEN);
79 memcpy (k_ipad, key, key_len);
80 memcpy (k_opad, k_ipad, PAD_LEN);
81
82 /* XOR key with ipad and opad values */
83 for (i=0; i<PAD_LEN; i++) {
84 k_ipad[i] ^= 0x36;
85 k_opad[i] ^= 0x5c;
86 }
87
88 /* perform inner MD5 */
89 MD5Init (&md5c); /* start inner hash */
90 MD5Update (&md5c, k_ipad, PAD_LEN); /* hash inner pad */
91 MD5Update (&md5c, text, text_len); /* hash text */
92 MD5Final (hmac, &md5c); /* store inner hash */
93
94 /* perform outer MD5 */
95 MD5Init (&md5c); /* start outer hash */
96 MD5Update (&md5c, k_opad, PAD_LEN); /* hash outer pad */
97 MD5Update (&md5c, hmac, SIG_LEN); /* hash inner hash */
98 MD5Final (hmac, &md5c); /* store results */
99 }
100
101 static gboolean
check_hash(const gchar * key,const gchar * data,const gchar * expected_hash)102 check_hash (const gchar *key, const gchar *data, const gchar *expected_hash)
103 {
104 uint8_t hmac[16];
105 GString *md5str;
106 gint i;
107 gboolean retval = TRUE;
108
109 hmac_md5 ((uint8_t *) data, strlen (data),
110 (uint8_t *) key, strlen (key), hmac);
111 md5str = g_string_new ("");
112 for (i = 0; i < 16; i++)
113 g_string_append_printf (md5str, "%02x", hmac[i]);
114
115 if (strcmp (md5str->str, expected_hash))
116 retval = FALSE;
117
118 g_string_free (md5str, TRUE);
119 return retval;
120 }
121
122 /*
123 * If there is a mismatch or an error, then gda_connection_add_event_string() is used
124 *
125 * - Modifies @sbuffer (to separate HASH from XML part)
126 * - if all OK, extracs the <challenge> value and replace cdata->next_challenge with it (or simply
127 * reset cdata->next_challenge to NULL)
128 *
129 * Returns: a new #xmlDocPtr, or %NULL on error
130 */
131 static xmlDocPtr
decode_buffer_response(GdaConnection * cnc,WebConnectionData * cdata,SoupBuffer * sbuffer,gchar * out_status_chr,guint * out_counter_id)132 decode_buffer_response (GdaConnection *cnc, WebConnectionData *cdata, SoupBuffer *sbuffer,
133 gchar *out_status_chr, guint *out_counter_id)
134 {
135 xmlDocPtr doc;
136 gchar *ptr, *response;
137
138 if (out_status_chr)
139 *out_status_chr = 0;
140 if (out_counter_id)
141 *out_counter_id = 0;
142
143 g_assert (sbuffer);
144 response = (gchar*) sbuffer->data;
145
146 for (ptr = response; *ptr && (*ptr != '\n'); ptr++);
147 if (*ptr != '\n') {
148 gda_connection_add_event_string (cnc, _("Could not parse server's reponse"));
149 return NULL;
150 }
151 *ptr = 0;
152 ptr++;
153
154 if ((cdata->key && !check_hash (cdata->key, ptr, response)) &&
155 (cdata->server_secret && !check_hash (cdata->server_secret, ptr, response))) {
156 gda_connection_add_event_string (cnc, _("Invalid response hash"));
157 return NULL;
158 }
159 doc = xmlParseMemory (ptr, strlen (ptr));
160
161 if (!doc) {
162 gda_connection_add_event_string (cnc, _("Could not parse server's reponse"));
163 return NULL;
164 }
165 else {
166 xmlNodePtr node, root;
167
168 root = xmlDocGetRootElement (doc);
169 for (node = root->children; node; node = node->next) {
170 if (!strcmp ((gchar*) node->name, "session")) {
171 xmlChar *contents;
172 contents = xmlNodeGetContent (node);
173 g_free (cdata->session_id);
174 cdata->session_id = g_strdup ((gchar*) contents);
175 xmlFree (contents);
176 }
177 else if (!strcmp ((gchar*) node->name, "challenge")) {
178 xmlChar *contents;
179 if (cdata->next_challenge) {
180 g_free (cdata->next_challenge);
181 cdata->next_challenge = NULL;
182 }
183 contents = xmlNodeGetContent (node);
184 cdata->next_challenge = g_strdup ((gchar*) contents);
185 xmlFree (contents);
186 }
187 else if (out_status_chr && !strcmp ((gchar*) node->name, "status")) {
188 xmlChar *contents;
189 contents = xmlNodeGetContent (node);
190 *out_status_chr = *contents;
191 xmlFree (contents);
192 }
193 else if (out_counter_id && !strcmp ((gchar*) node->name, "counter")) {
194 xmlChar *contents;
195 contents = xmlNodeGetContent (node);
196 *out_counter_id = atoi ((gchar*) contents);
197 xmlFree (contents);
198 }
199 else if (!cdata->server_id && !strcmp ((gchar*) node->name, "servertype")) {
200 xmlChar *contents;
201 contents = xmlNodeGetContent (node);
202 cdata->server_id = g_strdup ((gchar*) contents);
203 xmlFree (contents);
204
205 cdata->reuseable = _gda_provider_reuseable_new (cdata->server_id);
206 #ifdef DEBUG_WEB_PROV
207 g_print ("REUSEABLE [%s]: %p\n", cdata->server_id, cdata->reuseable);
208 #endif
209 }
210 else if (!cdata->server_version && !strcmp ((gchar*) node->name, "serverversion")) {
211 xmlChar *contents;
212 contents = xmlNodeGetContent (node);
213 cdata->server_version = g_strdup ((gchar*) contents);
214 xmlFree (contents);
215 #ifdef DEBUG_WEB_PROV
216 g_print ("SERVER version [%s]\n", cdata->server_version);
217 #endif
218 }
219 }
220 }
221
222 return doc;
223 }
224
225 typedef struct {
226 GdaConnection *cnc;
227 WebConnectionData *cdata;
228 } ThreadData;
229
230 /* executed in sub thread */
231 static void
worker_got_chunk_cb(SoupMessage * msg,SoupBuffer * chunk,ThreadData * thdata)232 worker_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, ThreadData *thdata)
233 {
234 xmlDocPtr doc;
235 gchar *data, *ptr;
236
237 data = g_strndup (chunk->data, chunk->length);
238 soup_message_body_set_accumulate (msg->response_body, FALSE);
239
240 #ifdef DEBUG_WEB_PROV
241 if (*data)
242 g_print (">>>> WORKER\n%s\n<<<< WORKER\n", data);
243 #endif
244 if (!thdata->cdata->session_id) {
245 ptr = strstr (data, "</reply>");
246 if (ptr) {
247 gchar status;
248 guint counter_id;
249 ptr += 8;
250 *ptr = 0;
251 doc = decode_buffer_response (thdata->cnc, thdata->cdata, chunk, &status, &counter_id);
252 if (!doc || (status != 'O')) {
253 /* this cannot happen at the moment */
254 g_assert_not_reached ();
255 if (doc)
256 xmlFreeDoc (doc);
257 }
258 else {
259 gda_mutex_lock (thdata->cdata->mutex);
260 g_assert (thdata->cdata->worker_counter == counter_id);
261 gda_mutex_unlock (thdata->cdata->mutex);
262 xmlFreeDoc (doc);
263 }
264 }
265 }
266 g_free (data);
267 }
268
269 /* executed in sub thread */
270 static gpointer
start_worker_in_sub_thread(ThreadData * thdata)271 start_worker_in_sub_thread (ThreadData *thdata)
272 {
273 SoupMessage *msg;
274 gulong sigid;
275 gboolean runagain = TRUE;
276
277 while (runagain) {
278 GString *real_url;
279 gda_mutex_lock (thdata->cdata->mutex);
280 real_url = g_string_new (thdata->cdata->worker_url);
281 if (thdata->cdata->session_id)
282 g_string_append_printf (real_url, "?%s", thdata->cdata->session_id);
283 thdata->cdata->worker_running = TRUE;
284 if (thdata->cdata->worker_counter == 0)
285 thdata->cdata->worker_counter = 1;
286 else
287 thdata->cdata->worker_counter ++;
288 gda_mutex_unlock (thdata->cdata->mutex);
289
290 msg = soup_message_new ("GET", real_url->str);
291 /*g_print ("=== WORKER Request URL: [%s]\n", real_url->str);*/
292 if (!msg) {
293 g_warning (_("Invalid HOST/SCRIPT '%s'"), real_url->str);
294 g_string_free (real_url, TRUE);
295 gda_mutex_lock (thdata->cdata->mutex);
296 thdata->cdata->worker_running = FALSE;
297 gda_mutex_unlock (thdata->cdata->mutex);
298 g_free (thdata);
299 return NULL;
300 }
301 g_string_free (real_url, TRUE);
302
303 sigid = g_signal_connect (msg, "got-chunk",
304 G_CALLBACK (worker_got_chunk_cb), thdata);
305 guint res;
306 res = soup_session_send_message (thdata->cdata->worker_session, msg);
307
308 gda_mutex_lock (thdata->cdata->mutex);
309 thdata->cdata->worker_running = FALSE;
310 runagain = thdata->cdata->worker_needed;
311 runagain = runagain && SOUP_STATUS_IS_SUCCESSFUL (res);
312 gda_mutex_unlock (thdata->cdata->mutex);
313
314 g_signal_handler_disconnect (msg, sigid);
315 g_object_unref (msg);
316 }
317
318 g_free (thdata);
319 #ifdef DEBUG_WEB_PROV
320 g_print ("Worker closed!\n");
321 #endif
322
323 /* end of sub thread */
324 return NULL;
325 }
326
327 static void
start_worker(GdaConnection * cnc,WebConnectionData * cdata)328 start_worker (GdaConnection *cnc, WebConnectionData *cdata)
329 {
330 ThreadData *thdata;
331
332 thdata = g_new0 (ThreadData, 1); /* freed by sub thread */
333 thdata->cnc = cnc;
334 thdata->cdata = cdata;
335
336 /* set cdata->worker_running to TRUE to avoid having to add a delay */
337 gda_mutex_lock (cdata->mutex);
338 cdata->worker_running = TRUE;
339 gda_mutex_unlock (cdata->mutex);
340
341 if (! g_thread_new ("web-worker", (GThreadFunc) start_worker_in_sub_thread,
342 thdata)) {
343 g_free (thdata);
344 gda_connection_add_event_string (cnc, _("Can't start new thread"));
345 return;
346 }
347
348 gint nb_retries;
349 for (nb_retries = 0; nb_retries < 10; nb_retries++) {
350 gboolean wait_over;
351 gda_mutex_lock (cdata->mutex);
352 wait_over = !cdata->worker_running || cdata->session_id;
353 gda_mutex_unlock (cdata->mutex);
354 if (wait_over)
355 break;
356 else
357 g_usleep (200000);
358 }
359
360 gda_mutex_lock (cdata->mutex);
361 if (!cdata->session_id) {
362 /* there was an error */
363 cdata->worker_running = FALSE;
364 }
365 gda_mutex_unlock (cdata->mutex);
366 }
367
368 /*
369 * Adds a HASH to the message using @hash_key, or adds "NOHASH" if @hash_key is %NULL
370 *
371 * If there is an error, then gda_connection_add_event_string() is called
372 *
373 * @out_status_chr, if NOT NULL will contain the 1st char of the <status> node's contents
374 */
375 xmlDocPtr
_gda_web_send_message_to_frontend(GdaConnection * cnc,WebConnectionData * cdata,WebMessageType msgtype,const gchar * message,const gchar * hash_key,gchar * out_status_chr)376 _gda_web_send_message_to_frontend (GdaConnection *cnc, WebConnectionData *cdata,
377 WebMessageType msgtype, const gchar *message,
378 const gchar *hash_key, gchar *out_status_chr)
379 {
380 SoupMessage *msg;
381 guint status;
382 gchar *h_message;
383 gchar *real_url;
384 static gint counter = 0;
385
386 if (out_status_chr)
387 *out_status_chr = 0;
388
389 /* handle the need to run the worker to get an initial sessionID */
390 gda_mutex_lock (cdata->mutex);
391 cdata->worker_needed = TRUE;
392 if (!cdata->worker_running && !cdata->session_id) {
393 gda_mutex_unlock (cdata->mutex);
394 start_worker (cnc, cdata);
395
396 gda_mutex_lock (cdata->mutex);
397 if (! cdata->worker_running) {
398 gda_connection_add_event_string (cnc, _("Could not run PHP script on the server"));
399 cdata->worker_needed = FALSE;
400 gda_mutex_unlock (cdata->mutex);
401 return NULL;
402 }
403 }
404
405 /* prepare new message */
406 g_assert (cdata->session_id);
407 real_url = g_strdup_printf ("%s?%s&c=%d", cdata->front_url, cdata->session_id, counter++);
408 gda_mutex_unlock (cdata->mutex);
409 msg = soup_message_new ("POST", real_url);
410 if (!msg) {
411 gda_connection_add_event_string (cnc, _("Invalid HOST/SCRIPT '%s'"), real_url);
412 g_free (real_url);
413 return NULL;
414 }
415 g_free (real_url);
416
417 /* check context */
418 gda_mutex_lock (cdata->mutex);
419 if (gda_connection_get_transaction_status (cnc) &&
420 (!cdata->worker_running ||
421 ((msgtype == MESSAGE_EXEC) && (cdata->last_exec_counter != cdata->worker_counter)))) {
422 /* update cdata->last_exec_counter so next statement can be run */
423 cdata->last_exec_counter = cdata->worker_counter;
424
425 gda_connection_add_event_string (cnc, _("The transaction has been automatically rolled back"));
426 g_object_unref (msg);
427
428 gda_connection_internal_reset_transaction_status (cnc);
429 gda_mutex_unlock (cdata->mutex);
430 return NULL;
431 }
432 if (! cdata->worker_running) {
433 gda_mutex_unlock (cdata->mutex);
434 start_worker (cnc, cdata);
435
436 gda_mutex_lock (cdata->mutex);
437 if (! cdata->worker_running) {
438 gda_connection_add_event_string (cnc, _("Could not run PHP script on the server"));
439 g_object_unref (msg);
440 gda_mutex_unlock (cdata->mutex);
441 return NULL;
442 }
443 }
444 gda_mutex_unlock (cdata->mutex);
445
446 /* finalize and send message */
447 if (hash_key) {
448 uint8_t hmac[16];
449 GString *md5str;
450 gint i;
451
452 hmac_md5 ((uint8_t *) message, strlen (message),
453 (uint8_t *) hash_key, strlen (hash_key), hmac);
454 md5str = g_string_new ("");
455 for (i = 0; i < 16; i++)
456 g_string_append_printf (md5str, "%02x", hmac[i]);
457 g_string_append_c (md5str, '\n');
458 g_string_append (md5str, message);
459 h_message = g_string_free (md5str, FALSE);
460 }
461 else
462 h_message = g_strdup_printf ("NOHASH\n%s", message);
463
464 #ifdef DEBUG_WEB_PROV
465 g_print ("=== START of request ===\n%s\n=== END of request ===\n", h_message);
466 #endif
467 soup_message_set_request (msg, "text/plain",
468 SOUP_MEMORY_COPY, h_message, strlen (h_message));
469 g_free (h_message);
470 g_object_set (G_OBJECT (cdata->front_session), SOUP_SESSION_TIMEOUT, 20, NULL);
471 status = soup_session_send_message (cdata->front_session, msg);
472
473 gda_mutex_lock (cdata->mutex);
474 cdata->worker_needed = FALSE;
475 gda_mutex_unlock (cdata->mutex);
476
477 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
478 gda_connection_add_event_string (cnc, msg->reason_phrase);
479 g_object_unref (msg);
480 return NULL;
481 }
482
483 xmlDocPtr doc;
484 guint counter_id;
485 doc = _gda_web_decode_response (cnc, cdata, msg->response_body, out_status_chr, &counter_id);
486 g_object_unref (msg);
487
488 gda_mutex_lock (cdata->mutex);
489 if (msgtype == MESSAGE_EXEC)
490 cdata->last_exec_counter = counter_id;
491 gda_mutex_unlock (cdata->mutex);
492
493 return doc;
494 }
495
496
497 /*
498 * If there is a mismatch or an error, then gda_connection_add_event_string() is used
499 *
500 * - if all OK, extracs the <challenge> value and replace cdata->next_challenge with it (or simply
501 * reset cdata->next_challenge to NULL)
502 *
503 * Returns: a new #xmlDocPtr, or %NULL on error
504 */
505 xmlDocPtr
_gda_web_decode_response(GdaConnection * cnc,WebConnectionData * cdata,SoupMessageBody * body,gchar * out_status_chr,guint * out_counter_id)506 _gda_web_decode_response (GdaConnection *cnc, WebConnectionData *cdata, SoupMessageBody *body,
507 gchar *out_status_chr, guint *out_counter_id)
508 {
509 SoupBuffer *sbuffer;
510 xmlDocPtr doc;
511 sbuffer = soup_message_body_flatten (body);
512 #ifdef DEBUG_WEB_PROV
513 g_print ("=== START of response ===\n%s\n=== END of response ===\n", (gchar*) sbuffer->data);
514 #endif
515 doc = decode_buffer_response (cnc, cdata, sbuffer, out_status_chr, out_counter_id);
516 soup_buffer_free (sbuffer);
517 return doc;
518 }
519
520 /*
521 * Creates a new string
522 */
523 gchar *
_gda_web_compute_token(WebConnectionData * cdata)524 _gda_web_compute_token (WebConnectionData *cdata)
525 {
526 uint8_t hmac[16];
527 GString *md5str;
528 gint i;
529
530 g_return_val_if_fail (cdata->next_challenge && cdata->key, NULL);
531
532 hmac_md5 ((uint8_t *) cdata->next_challenge, strlen (cdata->next_challenge),
533 (uint8_t *) cdata->key, strlen (cdata->key), hmac);
534 md5str = g_string_new ("");
535 for (i = 0; i < 16; i++)
536 g_string_append_printf (md5str, "%02x", hmac[i]);
537
538 return g_string_free (md5str, FALSE);
539 }
540
541 /*
542 * Cleans any remaining data on the web server
543 */
544 void
_gda_web_do_server_cleanup(GdaConnection * cnc,WebConnectionData * cdata)545 _gda_web_do_server_cleanup (GdaConnection *cnc, WebConnectionData *cdata)
546 {
547 SoupMessage *msg;
548 guint status;
549 gchar *real_url;
550 gint nb_retries;
551
552 /* wait for worker to finish */
553 gda_mutex_lock (cdata->mutex);
554 for (nb_retries = 0; (nb_retries < 10) && cdata->worker_running; nb_retries ++) {
555 gda_mutex_unlock (cdata->mutex);
556 g_usleep (50000);
557 gda_mutex_lock (cdata->mutex);
558 }
559 gda_mutex_unlock (cdata->mutex);
560
561 real_url = g_strdup_printf ("%s/gda-clean.php?%s", cdata->server_base_url, cdata->session_id);
562 msg = soup_message_new ("GET", real_url);
563 if (!msg) {
564 gda_connection_add_event_string (cnc, _("Invalid HOST/SCRIPT '%s'"), real_url);
565 g_free (real_url);
566 return;
567 }
568 g_free (real_url);
569
570 g_object_set (G_OBJECT (cdata->front_session), SOUP_SESSION_TIMEOUT, 5, NULL);
571 status = soup_session_send_message (cdata->front_session, msg);
572 g_object_unref (msg);
573
574 if (!SOUP_STATUS_IS_SUCCESSFUL (status))
575 g_warning (_("Error cleaning data on the server for session %s"), cdata->session_id);
576 #ifdef DEBUG_WEB_PROV
577 else
578 g_print ("CLEANUP DONE!\n");
579 #endif
580 }
581
582 /**
583 * _gda_web_set_connection_error_from_xmldoc
584 *
585 * Handles errors reported by @doc, and ser @error if not %NULL
586 *
587 * Returns: a #GdaConnectionEvent, which must not be modified or freed
588 */
589 GdaConnectionEvent *
_gda_web_set_connection_error_from_xmldoc(GdaConnection * cnc,xmlDocPtr doc,GError ** error)590 _gda_web_set_connection_error_from_xmldoc (GdaConnection *cnc, xmlDocPtr doc, GError **error)
591 {
592 xmlNodePtr node, root;
593 GdaConnectionEvent *ev = NULL;
594
595 g_return_val_if_fail (doc, NULL);
596
597 root = xmlDocGetRootElement (doc);
598 for (node = root->children; node; node = node->next) {
599 if (!strcmp ((gchar*) node->name, "status")) {
600 xmlChar *prop;
601 prop = xmlGetProp (node, BAD_CAST "error");
602 if (prop) {
603 ev = gda_connection_add_event_string (cnc, (gchar*) prop);
604 xmlFree (prop);
605 }
606 else
607 ev = gda_connection_add_event_string (cnc, _("Non detailled error"));
608 break;
609 }
610 }
611
612 if (ev && error) {
613 g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
614 GDA_SERVER_PROVIDER_STATEMENT_EXEC_ERROR, "%s",
615 gda_connection_event_get_description (ev));
616 }
617
618 return ev;
619 }
620
621 /*
622 * Actually closes the connection from Libgda's point of view
623 */
624 void
_gda_web_change_connection_to_closed(GdaConnection * cnc,WebConnectionData * cdata)625 _gda_web_change_connection_to_closed (GdaConnection *cnc, WebConnectionData *cdata)
626 {
627 cdata->forced_closing = TRUE;
628 gda_connection_close_no_warning (cnc);
629 }
630
631 /*
632 * Free connection's specific data
633 */
634 void
_gda_web_free_cnc_data(WebConnectionData * cdata)635 _gda_web_free_cnc_data (WebConnectionData *cdata)
636 {
637 if (!cdata)
638 return;
639
640 if (cdata->reuseable) {
641 g_assert (cdata->reuseable->operations);
642 if (cdata->reuseable->operations->re_reset_data)
643 cdata->reuseable->operations->re_reset_data (cdata->reuseable);
644 g_free (cdata->reuseable);
645 }
646 g_free (cdata->server_id);
647 g_free (cdata->server_version);
648 g_free (cdata->server_base_url);
649 g_free (cdata->front_url);
650 g_free (cdata->worker_url);
651 if (cdata->mutex)
652 gda_mutex_free (cdata->mutex);
653 if (cdata->worker_session)
654 g_object_unref (cdata->worker_session);
655 if (cdata->front_session)
656 g_object_unref (cdata->front_session);
657 g_free (cdata->session_id);
658 g_free (cdata->server_secret);
659 g_free (cdata->key);
660 g_free (cdata->next_challenge);
661
662 g_free (cdata);
663 }
664