Mercurial > pidgin.yaz
view libpurple/protocols/msn/soap2.c @ 22642:e062a1dfe6ad
Remove the "Use GSSAPI (Kerberos v5) for authentication" xmpp preference,
as it is not needed with Stu's changes in 7643bc26 which continue to the
next mechanism if GSSAPI fails.
author | Evan Schoenberg <evan.s@dreskin.net> |
---|---|
date | Tue, 15 Apr 2008 23:46:03 +0000 |
parents | fc060adb7178 |
children | f62a4a7fe365 bac263f5afe7 |
line wrap: on
line source
/** * @file soap2.c * C file for SOAP connection related process * * purple * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "internal.h" #include "soap2.h" #include "session.h" #include "debug.h" #include "xmlnode.h" #include <glib.h> #include <error.h> #define SOAP_TIMEOUT (5 * 60) typedef struct _MsnSoapRequest { char *path; MsnSoapMessage *message; MsnSoapCallback cb; gpointer cb_data; } MsnSoapRequest; typedef struct _MsnSoapConnection { MsnSession *session; char *host; time_t last_used; PurpleSslConnection *ssl; gboolean connected; guint event_handle; GString *buf; gsize handled_len; gsize body_len; int response_code; gboolean headers_done; gboolean close_when_done; MsnSoapMessage *message; GQueue *queue; MsnSoapRequest *current_request; } MsnSoapConnection; static void msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data); static gboolean msn_soap_connection_run(gpointer data); static MsnSoapConnection *msn_soap_connection_new(MsnSession *session, const char *host); static void msn_soap_connection_handle_next(MsnSoapConnection *conn); static void msn_soap_connection_destroy(MsnSoapConnection *conn); static void msn_soap_message_send_internal(MsnSession *session, MsnSoapMessage *message, const char *host, const char *path, MsnSoapCallback cb, gpointer cb_data, gboolean first); static void msn_soap_request_destroy(MsnSoapRequest *req); static void msn_soap_connection_sanitize(MsnSoapConnection *conn, gboolean disconnect); static gboolean msn_soap_cleanup_each(gpointer key, gpointer value, gpointer data) { MsnSoapConnection *conn = value; time_t *t = data; if ((*t - conn->last_used) > SOAP_TIMEOUT * 2) { purple_debug_info("soap", "cleaning up soap conn %p\n", conn); return TRUE; } return FALSE; } static gboolean msn_soap_cleanup_for_session(gpointer data) { MsnSession *sess = data; time_t t = time(NULL); purple_debug_info("soap", "session cleanup timeout\n"); if (sess->soap_table) { g_hash_table_foreach_remove(sess->soap_table, msn_soap_cleanup_each, &t); if (g_hash_table_size(sess->soap_table) == 0) { purple_timeout_remove(sess->soap_cleanup_handle); sess->soap_cleanup_handle = 0; } } return TRUE; } static MsnSoapConnection * msn_soap_get_connection(MsnSession *session, const char *host) { MsnSoapConnection *conn = NULL; if (session->soap_table) { conn = g_hash_table_lookup(session->soap_table, host); } else { session->soap_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)msn_soap_connection_destroy); } if (session->soap_cleanup_handle == 0) session->soap_cleanup_handle = purple_timeout_add(SOAP_TIMEOUT * 1000, msn_soap_cleanup_for_session, session); if (conn == NULL) { conn = msn_soap_connection_new(session, host); g_hash_table_insert(session->soap_table, conn->host, conn); } conn->last_used = time(NULL); return conn; } static MsnSoapConnection * msn_soap_connection_new(MsnSession *session, const char *host) { MsnSoapConnection *conn = g_new0(MsnSoapConnection, 1); conn->session = session; conn->host = g_strdup(host); conn->queue = g_queue_new(); return conn; } static void msn_soap_connected_cb(gpointer data, PurpleSslConnection *ssl, PurpleInputCondition cond) { MsnSoapConnection *conn = data; conn->connected = TRUE; if (conn->event_handle == 0) conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn); } static void msn_soap_error_cb(PurpleSslConnection *ssl, PurpleSslErrorType error, gpointer data) { MsnSoapConnection *conn = data; /* sslconn already frees the connection in case of error */ conn->ssl = NULL; g_hash_table_remove(conn->session->soap_table, conn->host); } static gboolean msn_soap_handle_redirect(MsnSoapConnection *conn, const char *url) { char *c; /* Skip the http:// */ if ((c = strchr(url, '/')) != NULL) url += 2; if ((c = strchr(url, '/')) != NULL) { char *host, *path; host = g_strndup(url, c - url); path = g_strdup(c); msn_soap_message_send_internal(conn->session, conn->current_request->message, host, path, conn->current_request->cb, conn->current_request->cb_data, TRUE); msn_soap_request_destroy(conn->current_request); conn->current_request = NULL; g_free(host); g_free(path); return TRUE; } return FALSE; } static gboolean msn_soap_handle_body(MsnSoapConnection *conn, MsnSoapMessage *response) { xmlnode *body = xmlnode_get_child(response->xml, "Body"); xmlnode *fault = xmlnode_get_child(response->xml, "Fault"); if (fault) { xmlnode *faultcode = xmlnode_get_child(fault, "faultcode"); if (faultcode != NULL) { char *faultdata = xmlnode_get_data(faultcode); if (g_str_equal(faultdata, "psf:Redirect")) { xmlnode *url = xmlnode_get_child(body, "redirectUrl"); if (url) { char *urldata = xmlnode_get_data(url); msn_soap_handle_redirect(conn, urldata); g_free(urldata); } g_free(faultdata); return TRUE; } else if (g_str_equal(faultdata, "wsse:FailedAuthentication")) { xmlnode *reason = xmlnode_get_child(body, "faultstring"); char *reasondata = xmlnode_get_data(reason); msn_soap_connection_sanitize(conn, TRUE); msn_session_set_error(conn->session, MSN_ERROR_AUTH, reasondata); g_free(reasondata); g_free(faultdata); return FALSE; } g_free(faultdata); } } if (fault || body) { MsnSoapRequest *request = conn->current_request; conn->current_request = NULL; request->cb(request->message, response, request->cb_data); msn_soap_request_destroy(request); } return TRUE; } static void msn_soap_read_cb(gpointer data, gint fd, PurpleInputCondition cond) { MsnSoapConnection *conn = data; int count = 0, cnt; char buf[8192]; char *linebreak; char *cursor; gboolean handled = FALSE; if (conn->message == NULL) { conn->message = msn_soap_message_new(NULL, NULL); } while ((cnt = purple_ssl_read(conn->ssl, buf, sizeof(buf))) > 0) { purple_debug_info("soap", "read %d bytes\n", cnt); count += cnt; if (conn->buf == NULL) { conn->buf = g_string_new_len(buf, cnt); } else { g_string_append_len(conn->buf, buf, cnt); } } if (cnt < 0) { if (errno != EAGAIN) { purple_debug_info("soap", "read: %s\n", g_strerror(errno)); purple_ssl_close(conn->ssl); conn->ssl = NULL; msn_soap_connection_handle_next(conn); return; } else if (count == 0) { return; } } if (cnt == 0 && count == 0) { purple_debug_info("soap", "msn_soap_read_cb() called, but no data available?\n"); purple_ssl_close(conn->ssl); conn->ssl = NULL; msn_soap_connection_handle_next(conn); return; } purple_debug_info("soap", "current %s\n", conn->buf->str); cursor = conn->buf->str + conn->handled_len; if (!conn->headers_done) { while ((linebreak = strstr(cursor, "\r\n")) != NULL) { conn->handled_len = linebreak - conn->buf->str + 2; if (conn->response_code == 0) { if (sscanf(cursor, "HTTP/1.1 %d", &conn->response_code) != 1) { /* something horribly wrong */ purple_ssl_close(conn->ssl); conn->ssl = NULL; msn_soap_connection_handle_next(conn); handled = TRUE; break; } else if (conn->response_code == 503) { msn_soap_connection_sanitize(conn, TRUE); msn_session_set_error(conn->session, MSN_ERROR_SERV_UNAVAILABLE, NULL); return; } } else if (cursor == linebreak) { /* blank line */ conn->headers_done = TRUE; cursor = conn->buf->str + conn->handled_len; break; } else { char *line = g_strndup(cursor, linebreak - cursor); char *sep = strstr(line, ": "); char *key = line; char *value; if (sep == NULL) { purple_debug_info("soap", "ignoring malformed line: %s\n", line); g_free(line); goto loop_end; } value = sep + 2; *sep = '\0'; msn_soap_message_add_header(conn->message, key, value); if ((conn->response_code == 301 || conn->response_code == 300) && strcmp(key, "Location") == 0) { msn_soap_handle_redirect(conn, value); handled = TRUE; g_free(line); break; } else if (conn->response_code == 401 && strcmp(key, "WWW-Authenticate") == 0) { char *error = strstr(value, "cbtxt="); if (error) { error += strlen("cbtxt="); } msn_soap_connection_sanitize(conn, TRUE); msn_session_set_error(conn->session, MSN_ERROR_AUTH, error ? purple_url_decode(error) : NULL); g_free(line); return; } else if (strcmp(key, "Content-Length") == 0) { conn->body_len = atoi(value); } else if (strcmp(key, "Connection") == 0) { if (strcmp(value, "close") == 0) { conn->close_when_done = TRUE; } } g_free(line); } loop_end: cursor = conn->buf->str + conn->handled_len; } } if (!handled && conn->headers_done) { if (conn->buf->len - conn->handled_len >= conn->body_len) { xmlnode *node = xmlnode_from_str(cursor, conn->body_len); if (node == NULL) { purple_debug_info("soap", "Malformed SOAP response: %s\n", cursor); } else { MsnSoapMessage *message = conn->message; conn->message = NULL; message->xml = node; if (!msn_soap_handle_body(conn, message)) return; } msn_soap_connection_handle_next(conn); } return; } if (handled) { msn_soap_connection_handle_next(conn); } } static void msn_soap_write_cb(gpointer data, gint fd, PurpleInputCondition cond) { MsnSoapConnection *conn = data; int written; g_return_if_fail(cond == PURPLE_INPUT_WRITE); written = purple_ssl_write(conn->ssl, conn->buf->str + conn->handled_len, conn->buf->len - conn->handled_len); if (written < 0 && errno == EAGAIN) return; else if (written <= 0) { purple_ssl_close(conn->ssl); conn->ssl = NULL; msn_soap_connection_handle_next(conn); return; } conn->handled_len += written; if (conn->handled_len < conn->buf->len) return; /* we are done! */ g_string_free(conn->buf, TRUE); conn->buf = NULL; conn->handled_len = 0; conn->body_len = 0; conn->response_code = 0; conn->headers_done = FALSE; conn->close_when_done = FALSE; purple_input_remove(conn->event_handle); conn->event_handle = purple_input_add(conn->ssl->fd, PURPLE_INPUT_READ, msn_soap_read_cb, conn); } static gboolean msn_soap_connection_run(gpointer data) { MsnSoapConnection *conn = data; MsnSoapRequest *req = g_queue_peek_head(conn->queue); conn->event_handle = 0; if (req) { if (conn->ssl == NULL) { conn->ssl = purple_ssl_connect(conn->session->account, conn->host, 443, msn_soap_connected_cb, msn_soap_error_cb, conn); } else if (conn->connected) { int len = -1; char *body = xmlnode_to_str(req->message->xml, &len); GSList *iter; char *authstr = NULL; g_queue_pop_head(conn->queue); conn->buf = g_string_new(""); if (conn->session->passport_info.mspauth) authstr = g_strdup_printf("Cookie: MSPAuth=%s\r\n", conn->session->passport_info.mspauth); g_string_append_printf(conn->buf, "POST %s HTTP/1.1\r\n" "SOAPAction: %s\r\n" "Content-Type:text/xml; charset=utf-8\r\n" "%s" "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n" "Accept: */*\r\n" "Host: %s\r\n" "Content-Length: %d\r\n" "Connection: Keep-Alive\r\n" "Cache-Control: no-cache\r\n", req->path, req->message->action ? req->message->action : "", authstr ? authstr : "", conn->host, len); for (iter = req->message->headers; iter; iter = iter->next) { g_string_append(conn->buf, (char *)iter->data); g_string_append(conn->buf, "\r\n"); } g_string_append(conn->buf, "\r\n"); g_string_append(conn->buf, body); purple_debug_info("soap", "%s\n", conn->buf->str); conn->handled_len = 0; conn->current_request = req; conn->event_handle = purple_input_add(conn->ssl->fd, PURPLE_INPUT_WRITE, msn_soap_write_cb, conn); msn_soap_write_cb(conn, conn->ssl->fd, PURPLE_INPUT_WRITE); g_free(authstr); g_free(body); } } return FALSE; } void msn_soap_message_send(MsnSession *session, MsnSoapMessage *message, const char *host, const char *path, MsnSoapCallback cb, gpointer cb_data) { msn_soap_message_send_internal(session, message, host, path, cb, cb_data, FALSE); } static void msn_soap_message_send_internal(MsnSession *session, MsnSoapMessage *message, const char *host, const char *path, MsnSoapCallback cb, gpointer cb_data, gboolean first) { MsnSoapConnection *conn = msn_soap_get_connection(session, host); MsnSoapRequest *req = g_new0(MsnSoapRequest, 1); req->path = g_strdup(path); req->message = message; req->cb = cb; req->cb_data = cb_data; if (first) { g_queue_push_head(conn->queue, req); } else { g_queue_push_tail(conn->queue, req); } if (conn->event_handle == 0) conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn); } static void msn_soap_connection_sanitize(MsnSoapConnection *conn, gboolean disconnect) { if (conn->event_handle) { purple_input_remove(conn->event_handle); conn->event_handle = 0; } if (conn->message) { msn_soap_message_destroy(conn->message); conn->message = NULL; } if (conn->buf) { g_string_free(conn->buf, TRUE); conn->buf = NULL; } if (conn->ssl && (disconnect || conn->close_when_done)) { purple_ssl_close(conn->ssl); conn->ssl = NULL; } if (conn->current_request) { msn_soap_request_destroy(conn->current_request); conn->current_request = NULL; } } static void msn_soap_connection_handle_next(MsnSoapConnection *conn) { msn_soap_connection_sanitize(conn, FALSE); conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn); if (conn->current_request) { MsnSoapRequest *req = conn->current_request; conn->current_request = NULL; msn_soap_connection_destroy_foreach_cb(req, conn); } } static void msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data) { MsnSoapRequest *req = item; if (req->cb) req->cb(req->message, NULL, req->cb_data); msn_soap_request_destroy(req); } static void msn_soap_connection_destroy(MsnSoapConnection *conn) { if (conn->current_request) { MsnSoapRequest *req = conn->current_request; conn->current_request = NULL; msn_soap_connection_destroy_foreach_cb(req, conn); } msn_soap_connection_sanitize(conn, TRUE); g_queue_foreach(conn->queue, msn_soap_connection_destroy_foreach_cb, conn); g_queue_free(conn->queue); g_free(conn->host); g_free(conn); } MsnSoapMessage * msn_soap_message_new(const char *action, xmlnode *xml) { MsnSoapMessage *message = g_new0(MsnSoapMessage, 1); message->action = g_strdup(action); message->xml = xml; return message; } void msn_soap_message_destroy(MsnSoapMessage *message) { if (message) { g_slist_foreach(message->headers, (GFunc)g_free, NULL); g_slist_free(message->headers); g_free(message->action); if (message->xml) xmlnode_free(message->xml); g_free(message); } } void msn_soap_message_add_header(MsnSoapMessage *req, const char *name, const char *value) { char *header = g_strdup_printf("%s: %s\r\n", name, value); req->headers = g_slist_prepend(req->headers, header); } static void msn_soap_request_destroy(MsnSoapRequest *req) { g_free(req->path); msn_soap_message_destroy(req->message); g_free(req); } xmlnode * msn_soap_xml_get(xmlnode *parent, const char *node) { xmlnode *ret = NULL; char **tokens = g_strsplit(node, "/", -1); int i; for (i = 0; tokens[i]; i++) { if ((ret = xmlnode_get_child(parent, tokens[i])) != NULL) parent = ret; else break; } g_strfreev(tokens); return ret; }