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