Annotation of ChivanetAimPidgin/oscarprpl/src/c/clientlogin.c, revision 1.1.1.1
1.1 snw 1: /*
2: * Purple's oscar protocol plugin
3: * This file is the legal property of its developers.
4: * Please see the AUTHORS file distributed alongside this file.
5: *
6: * This library is free software; you can redistribute it and/or
7: * modify it under the terms of the GNU Lesser General Public
8: * License as published by the Free Software Foundation; either
9: * version 2 of the License, or (at your option) any later version.
10: *
11: * This library is distributed in the hope that it will be useful,
12: * but WITHOUT ANY WARRANTY; without even the implied warranty of
13: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14: * Lesser General Public License for more details.
15: *
16: * You should have received a copy of the GNU Lesser General Public
17: * License along with this library; if not, write to the Free Software
18: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
19: */
20:
21: /**
22: * This file implements AIM's clientLogin procedure for authenticating
23: * users. This replaces the older MD5-based and XOR-based
24: * authentication methods that use SNAC family 0x0017.
25: *
26: * This doesn't use SNACs or FLAPs at all. It makes http and https
27: * POSTs to AOL to validate the user based on the password they
28: * provided to us. Upon successful authentication we request a
29: * connection to the BOS server by calling startOSCARsession. The
30: * AOL server gives us the hostname and port number to use, as well
31: * as the cookie to use to authenticate to the BOS server. And then
32: * everything else is the same as with BUCP.
33: *
34: * For details, see:
35: * http://dev.aol.com/aim/oscar/#AUTH
36: * http://dev.aol.com/authentication_for_clients
37: */
38:
39: #include "oscar.h"
40: #include "oscarcommon.h"
41:
42: #include "cipher.h"
43: #include "core.h"
44:
45: #define AIM_LOGIN_HOST "api.nina.chat"
46: #define ICQ_LOGIN_HOST "api.nina.chat"
47:
48: #define AIM_API_HOST "api.nina.chat"
49: #define ICQ_API_HOST "api.nina.chat"
50:
51: #define CLIENT_LOGIN_PAGE "/auth/clientLogin"
52: #define START_OSCAR_SESSION_PAGE "/aim/startOSCARSession"
53:
54: #define HTTPS_FORMAT_URL(host, page) "https://" host page
55:
56: static const gchar *client_login_urls[] = {
57: HTTPS_FORMAT_URL(AIM_LOGIN_HOST, CLIENT_LOGIN_PAGE),
58: HTTPS_FORMAT_URL(ICQ_LOGIN_HOST, CLIENT_LOGIN_PAGE),
59: };
60:
61: static const gchar *start_oscar_session_urls[] = {
62: HTTPS_FORMAT_URL(AIM_API_HOST, START_OSCAR_SESSION_PAGE),
63: HTTPS_FORMAT_URL(ICQ_API_HOST, START_OSCAR_SESSION_PAGE),
64: };
65:
66: static const gchar *get_client_login_url(OscarData *od)
67: {
68: return client_login_urls[od->icq ? 1 : 0];
69: }
70:
71: static const gchar *get_start_oscar_session_url(OscarData *od)
72: {
73: return start_oscar_session_urls[od->icq ? 1 : 0];
74: }
75:
76: static const char *get_client_key(OscarData *od)
77: {
78: return oscar_get_ui_info_string(
79: od->icq ? "prpl-icq-clientkey" : "prpl-aim-clientkey",
80: od->icq ? ICQ_DEFAULT_CLIENT_KEY : AIM_DEFAULT_CLIENT_KEY);
81: }
82:
83: static gchar *generate_error_message(xmlnode *resp, const char *url)
84: {
85: xmlnode *text;
86: xmlnode *status_code_node;
87: gboolean have_error_code = TRUE;
88: gchar *err = NULL;
89: gchar *details = NULL;
90:
91: status_code_node = xmlnode_get_child(resp, "statusCode");
92: if (status_code_node) {
93: gchar *status_code;
94:
95: /* We can get 200 OK here if the server omitted something we think it shouldn't have (see #12783).
96: * No point in showing the "Ok" string to the user.
97: */
98: status_code = xmlnode_get_data_unescaped(status_code_node);
99: if (purple_strequal(status_code, "200")) {
100: have_error_code = FALSE;
101: }
102: }
103: if (have_error_code && resp && (text = xmlnode_get_child(resp, "statusText"))) {
104: details = xmlnode_get_data(text);
105: }
106:
107: if (details && *details) {
108: err = g_strdup_printf(_("Received unexpected response from %s: %s"), url, details);
109: } else {
110: err = g_strdup_printf(_("Received unexpected response from %s"), url);
111: }
112:
113: g_free(details);
114: return err;
115: }
116:
117: /**
118: * @return A null-terminated base64 encoded version of the HMAC
119: * calculated using the given key and data.
120: */
121: static gchar *hmac_sha256(const char *key, const char *message)
122: {
123: PurpleCipherContext *context;
124: guchar digest[32];
125:
126: context = purple_cipher_context_new_by_name("hmac", NULL);
127: purple_cipher_context_set_option(context, "hash", "sha256");
128: purple_cipher_context_set_key(context, (guchar *)key);
129: purple_cipher_context_append(context, (guchar *)message, strlen(message));
130: purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
131: purple_cipher_context_destroy(context);
132:
133: return purple_base64_encode(digest, sizeof(digest));
134: }
135:
136: /**
137: * @return A base-64 encoded HMAC-SHA256 signature created using the
138: * technique documented at
139: * http://dev.aol.com/authentication_for_clients#signing
140: */
141: static gchar *generate_signature(const char *method, const char *url, const char *parameters, const char *session_key)
142: {
143: char *encoded_url, *signature_base_string, *signature;
144: const char *encoded_parameters;
145:
146: encoded_url = g_strdup(purple_url_encode(url));
147: encoded_parameters = purple_url_encode(parameters);
148: signature_base_string = g_strdup_printf("%s&%s&%s",
149: method, encoded_url, encoded_parameters);
150: g_free(encoded_url);
151:
152: signature = hmac_sha256(session_key, signature_base_string);
153: g_free(signature_base_string);
154:
155: return signature;
156: }
157:
158: static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie, char **tls_certname)
159: {
160: OscarData *od = purple_connection_get_protocol_data(gc);
161: xmlnode *response_node, *tmp_node, *data_node;
162: xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL, *tls_node = NULL;
163: char *tmp;
164: guint code;
165: const gchar *encryption_type = purple_account_get_string(purple_connection_get_account(gc), "encryption", OSCAR_DEFAULT_ENCRYPTION);
166:
167: /* Parse the response as XML */
168: response_node = xmlnode_from_str(response, response_len);
169: if (response_node == NULL)
170: {
171: char *msg;
172: purple_debug_error("oscar", "startOSCARSession could not parse "
173: "response as XML: %s\n", response);
174: /* Note to translators: %s in this string is a URL */
175: msg = generate_error_message(response_node,
176: get_start_oscar_session_url(od));
177: purple_connection_error_reason(gc,
178: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
179: g_free(msg);
180: return FALSE;
181: }
182:
183: /* Grab the necessary XML nodes */
184: tmp_node = xmlnode_get_child(response_node, "statusCode");
185: data_node = xmlnode_get_child(response_node, "data");
186: if (data_node != NULL) {
187: host_node = xmlnode_get_child(data_node, "host");
188: port_node = xmlnode_get_child(data_node, "port");
189: cookie_node = xmlnode_get_child(data_node, "cookie");
190: }
191:
192: /* Make sure we have a status code */
193: if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) {
194: char *msg;
195: purple_debug_error("oscar", "startOSCARSession response was "
196: "missing statusCode: %s\n", response);
197: msg = generate_error_message(response_node,
198: get_start_oscar_session_url(od));
199: purple_connection_error_reason(gc,
200: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
201: g_free(msg);
202: xmlnode_free(response_node);
203: return FALSE;
204: }
205:
206: /* Make sure the status code was 200 */
207: code = atoi(tmp);
208: if (code != 200)
209: {
210: xmlnode *status_detail_node;
211: guint status_detail = 0;
212:
213: status_detail_node = xmlnode_get_child(response_node,
214: "statusDetailCode");
215: if (status_detail_node) {
216: gchar *data = xmlnode_get_data(status_detail_node);
217: if (data) {
218: status_detail = atoi(data);
219: g_free(data);
220: }
221: }
222:
223: purple_debug_error("oscar", "startOSCARSession response statusCode "
224: "was %s: %s\n", tmp, response);
225:
226: if ((code == 401 && status_detail != 1014) || code == 607)
227: purple_connection_error_reason(gc,
228: PURPLE_CONNECTION_ERROR_OTHER_ERROR,
229: _("You have been connecting and disconnecting too "
230: "frequently. Wait ten minutes and try again. If "
231: "you continue to try, you will need to wait even "
232: "longer."));
233: else {
234: char *msg;
235: msg = generate_error_message(response_node,
236: get_start_oscar_session_url(od));
237: purple_connection_error_reason(gc,
238: PURPLE_CONNECTION_ERROR_OTHER_ERROR, msg);
239: g_free(msg);
240: }
241:
242: g_free(tmp);
243: xmlnode_free(response_node);
244: return FALSE;
245: }
246: g_free(tmp);
247:
248: /* Make sure we have everything else */
249: if (data_node == NULL || host_node == NULL || port_node == NULL || cookie_node == NULL)
250: {
251: char *msg;
252: purple_debug_error("oscar", "startOSCARSession response was missing "
253: "something: %s\n", response);
254: msg = generate_error_message(response_node,
255: get_start_oscar_session_url(od));
256: purple_connection_error_reason(gc,
257: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
258: g_free(msg);
259: xmlnode_free(response_node);
260: return FALSE;
261: }
262:
263: if (!purple_strequal(encryption_type, OSCAR_NO_ENCRYPTION)) {
264: tls_node = xmlnode_get_child(data_node, "tlsCertName");
265: if (tls_node != NULL) {
266: *tls_certname = xmlnode_get_data_unescaped(tls_node);
267: } else {
268: if (purple_strequal(encryption_type, OSCAR_OPPORTUNISTIC_ENCRYPTION)) {
269: purple_debug_warning("oscar", "We haven't received a tlsCertName to use. We will not do SSL to BOS.\n");
270: } else {
271: purple_debug_error("oscar", "startOSCARSession was missing tlsCertName: %s\n", response);
272: purple_connection_error_reason(
273: gc,
274: PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
275: _("You required encryption in your account settings, but one of the servers doesn't support it."));
276: xmlnode_free(response_node);
277: return FALSE;
278: }
279: }
280: }
281:
282: /* Extract data from the XML */
283: *host = xmlnode_get_data_unescaped(host_node);
284: tmp = xmlnode_get_data_unescaped(port_node);
285: *cookie = xmlnode_get_data_unescaped(cookie_node);
286:
287: if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || *cookie == NULL || **cookie == '\0')
288: {
289: char *msg;
290: purple_debug_error("oscar", "startOSCARSession response was missing "
291: "something: %s\n", response);
292: msg = generate_error_message(response_node,
293: get_start_oscar_session_url(od));
294: purple_connection_error_reason(gc,
295: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
296: g_free(msg);
297: g_free(*host);
298: g_free(tmp);
299: g_free(*cookie);
300: xmlnode_free(response_node);
301: return FALSE;
302: }
303:
304: *port = atoi(tmp);
305: g_free(tmp);
306:
307: return TRUE;
308: }
309:
310: static void start_oscar_session_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
311: {
312: OscarData *od;
313: PurpleConnection *gc;
314: char *host, *cookie;
315: char *tls_certname = NULL;
316: unsigned short port;
317: guint8 *cookiedata;
318: gsize cookiedata_len = 0;
319:
320: od = user_data;
321: gc = od->gc;
322:
323: od->url_data = NULL;
324:
325: if (error_message != NULL || len == 0) {
326: gchar *tmp;
327: /* Note to translators: The first %s is a URL, the second is an
328: error message. */
329: tmp = g_strdup_printf(_("Error requesting %s: %s"),
330: get_start_oscar_session_url(od), error_message ?
331: error_message : _("The server returned an empty response"));
332: purple_connection_error_reason(gc,
333: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
334: g_free(tmp);
335: return;
336: }
337:
338: if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie, &tls_certname))
339: return;
340:
341: cookiedata = purple_base64_decode(cookie, &cookiedata_len);
342: oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len, tls_certname);
343: g_free(cookiedata);
344:
345: g_free(host);
346: g_free(cookie);
347: g_free(tls_certname);
348: }
349:
350: static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime)
351: {
352: char *query_string, *signature, *url;
353: PurpleAccount *account = purple_connection_get_account(od->gc);
354: const gchar *encryption_type = purple_account_get_string(account, "encryption", OSCAR_DEFAULT_ENCRYPTION);
355:
356: /*
357: * Construct the GET parameters.
358: */
359: query_string = g_strdup_printf("a=%s"
360: "&distId=%d"
361: "&f=xml"
362: "&k=%s"
363: "&ts=%" PURPLE_TIME_T_MODIFIER
364: "&useTLS=%d",
365: purple_url_encode(token),
366: oscar_get_ui_info_int(od->icq ? "prpl-icq-distid" : "prpl-aim-distid",
367: od->icq ? ICQ_DEFAULT_DIST_ID : AIM_DEFAULT_DIST_ID),
368: get_client_key(od),
369: hosttime,
370: !purple_strequal(encryption_type, OSCAR_NO_ENCRYPTION));
371: signature = generate_signature("GET", get_start_oscar_session_url(od),
372: query_string, session_key);
373: url = g_strdup_printf("%s?%s&sig_sha256=%s", get_start_oscar_session_url(od),
374: query_string, signature);
375: g_free(query_string);
376: g_free(signature);
377:
378: /* Make the request */
379: od->url_data = purple_util_fetch_url_request_len_with_account(account,
380: url, TRUE, NULL, FALSE, NULL, FALSE, -1,
381: start_oscar_session_cb, od);
382: g_free(url);
383: }
384:
385: /**
386: * This function parses the given response from a clientLogin request
387: * and extracts the useful information.
388: *
389: * @param gc The PurpleConnection. If the response data does
390: * not indicate then purple_connection_error_reason()
391: * will be called to close this connection.
392: * @param response The response data from the clientLogin request.
393: * @param response_len The length of the above response, or -1 if
394: * @response is NUL terminated.
395: * @param token If parsing was successful then this will be set to
396: * a newly allocated string containing the token. The
397: * caller should g_free this string when it is finished
398: * with it. On failure this value will be untouched.
399: * @param secret If parsing was successful then this will be set to
400: * a newly allocated string containing the secret. The
401: * caller should g_free this string when it is finished
402: * with it. On failure this value will be untouched.
403: * @param hosttime If parsing was successful then this will be set to
404: * the time on the OpenAuth Server in seconds since the
405: * Unix epoch. On failure this value will be untouched.
406: *
407: * @return TRUE if the request was successful and we were able to
408: * extract all info we need. Otherwise FALSE.
409: */
410: static gboolean parse_client_login_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **token, char **secret, time_t *hosttime)
411: {
412: OscarData *od = purple_connection_get_protocol_data(gc);
413: xmlnode *response_node, *tmp_node, *data_node;
414: xmlnode *secret_node = NULL, *hosttime_node = NULL, *token_node = NULL, *tokena_node = NULL;
415: char *tmp;
416:
417: /* Parse the response as XML */
418: response_node = xmlnode_from_str(response, response_len);
419: if (response_node == NULL)
420: {
421: char *msg;
422: purple_debug_error("oscar", "clientLogin could not parse "
423: "response as XML: %s\n", response);
424: msg = generate_error_message(response_node,
425: get_client_login_url(od));
426: purple_connection_error_reason(gc,
427: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
428: g_free(msg);
429: return FALSE;
430: }
431:
432: /* Grab the necessary XML nodes */
433: tmp_node = xmlnode_get_child(response_node, "statusCode");
434: data_node = xmlnode_get_child(response_node, "data");
435: if (data_node != NULL) {
436: secret_node = xmlnode_get_child(data_node, "sessionSecret");
437: hosttime_node = xmlnode_get_child(data_node, "hostTime");
438: token_node = xmlnode_get_child(data_node, "token");
439: if (token_node != NULL)
440: tokena_node = xmlnode_get_child(token_node, "a");
441: }
442:
443: /* Make sure we have a status code */
444: if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) {
445: char *msg;
446: purple_debug_error("oscar", "clientLogin response was "
447: "missing statusCode: %s\n", response);
448: msg = generate_error_message(response_node,
449: get_client_login_url(od));
450: purple_connection_error_reason(gc,
451: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
452: g_free(msg);
453: xmlnode_free(response_node);
454: return FALSE;
455: }
456:
457: /* Make sure the status code was 200 */
458: if (!purple_strequal(tmp, "200"))
459: {
460: int status_code, status_detail_code = 0;
461:
462: status_code = atoi(tmp);
463: g_free(tmp);
464: tmp_node = xmlnode_get_child(response_node, "statusDetailCode");
465: if (tmp_node != NULL && (tmp = xmlnode_get_data_unescaped(tmp_node)) != NULL) {
466: status_detail_code = atoi(tmp);
467: g_free(tmp);
468: }
469:
470: purple_debug_error("oscar", "clientLogin response statusCode "
471: "was %d (%d): %s\n", status_code, status_detail_code, response);
472:
473: if (status_code == 330 && status_detail_code == 3011) {
474: PurpleAccount *account = purple_connection_get_account(gc);
475: if (!purple_account_get_remember_password(account))
476: purple_account_set_password(account, NULL);
477: purple_connection_error_reason(gc,
478: PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
479: _("Incorrect password"));
480: } else if (status_code == 330 && status_detail_code == 3015) {
481: purple_connection_error_reason(gc,
482: PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
483: _("Server requested that you fill out a CAPTCHA in order to "
484: "sign in, but this client does not currently support CAPTCHAs."));
485: } else if (status_code == 401 && status_detail_code == 3019) {
486: purple_connection_error_reason(gc,
487: PURPLE_CONNECTION_ERROR_OTHER_ERROR,
488: _("AOL does not allow your screen name to authenticate here"));
489: } else {
490: char *msg;
491: msg = generate_error_message(response_node,
492: get_client_login_url(od));
493: purple_connection_error_reason(gc,
494: PURPLE_CONNECTION_ERROR_OTHER_ERROR, msg);
495: g_free(msg);
496: }
497:
498: xmlnode_free(response_node);
499: return FALSE;
500: }
501: g_free(tmp);
502:
503: /* Make sure we have everything else */
504: if (data_node == NULL || secret_node == NULL ||
505: token_node == NULL || tokena_node == NULL)
506: {
507: char *msg;
508: purple_debug_error("oscar", "clientLogin response was missing "
509: "something: %s\n", response);
510: msg = generate_error_message(response_node,
511: get_client_login_url(od));
512: purple_connection_error_reason(gc,
513: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
514: g_free(msg);
515: xmlnode_free(response_node);
516: return FALSE;
517: }
518:
519: /* Extract data from the XML */
520: *token = xmlnode_get_data_unescaped(tokena_node);
521: *secret = xmlnode_get_data_unescaped(secret_node);
522: tmp = xmlnode_get_data_unescaped(hosttime_node);
523: if (*token == NULL || **token == '\0' || *secret == NULL || **secret == '\0' || tmp == NULL || *tmp == '\0')
524: {
525: char *msg;
526: purple_debug_error("oscar", "clientLogin response was missing "
527: "something: %s\n", response);
528: msg = generate_error_message(response_node,
529: get_client_login_url(od));
530: purple_connection_error_reason(gc,
531: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg);
532: g_free(msg);
533: g_free(*token);
534: g_free(*secret);
535: g_free(tmp);
536: xmlnode_free(response_node);
537: return FALSE;
538: }
539:
540: *hosttime = strtol(tmp, NULL, 10);
541: g_free(tmp);
542:
543: xmlnode_free(response_node);
544:
545: return TRUE;
546: }
547:
548: static void client_login_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
549: {
550: OscarData *od;
551: PurpleConnection *gc;
552: char *token, *secret, *session_key;
553: time_t hosttime;
554: int password_len;
555: char *password;
556:
557: od = user_data;
558: gc = od->gc;
559:
560: od->url_data = NULL;
561:
562: if (error_message != NULL || len == 0) {
563: gchar *tmp;
564: tmp = g_strdup_printf(_("Error requesting %s: %s"),
565: get_client_login_url(od), error_message ?
566: error_message : _("The server returned an empty response"));
567: purple_connection_error_reason(gc,
568: PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
569: g_free(tmp);
570: return;
571: }
572:
573: if (!parse_client_login_response(gc, url_text, len, &token, &secret, &hosttime))
574: return;
575:
576: password_len = strlen(purple_connection_get_password(gc));
577: password = g_strdup_printf("%.*s",
578: od->icq ? MIN(password_len, MAXICQPASSLEN) : password_len,
579: purple_connection_get_password(gc));
580: session_key = hmac_sha256(password, secret);
581: g_free(password);
582: g_free(secret);
583:
584: send_start_oscar_session(od, token, session_key, hosttime);
585:
586: g_free(token);
587: g_free(session_key);
588: }
589:
590: /**
591: * This function sends a request to
592: * https://api.screenname.aol.com/auth/clientLogin with the user's
593: * username and password and receives the user's session key, which is
594: * used to request a connection to the BOSS server.
595: */
596: void send_client_login(OscarData *od, const char *username)
597: {
598: PurpleConnection *gc;
599: GString *request, *body;
600: const char *tmp;
601: char *password;
602: int password_len;
603:
604: gc = od->gc;
605:
606: /*
607: * We truncate ICQ passwords to 8 characters. There is probably a
608: * limit for AIM passwords, too, but we really only need to do
609: * this for ICQ because older ICQ clients let you enter a password
610: * as long as you wanted and then they truncated it silently.
611: *
612: * And we can truncate based on the number of bytes and not the
613: * number of characters because passwords for AIM and ICQ are
614: * supposed to be plain ASCII (I don't know if this has always been
615: * the case, though).
616: */
617: tmp = purple_connection_get_password(gc);
618: password_len = strlen(tmp);
619: password = g_strndup(tmp, od->icq ? MIN(password_len, MAXICQPASSLEN) : password_len);
620:
621: /* Construct the body of the HTTP POST request */
622: body = g_string_new("");
623: g_string_append_printf(body, "devId=%s", get_client_key(od));
624: g_string_append_printf(body, "&f=xml");
625: g_string_append_printf(body, "&pwd=%s", purple_url_encode(password));
626: g_string_append_printf(body, "&s=%s", purple_url_encode(username));
627: g_free(password);
628:
629: /* Construct an HTTP POST request */
630: request = g_string_new("POST /auth/clientLogin HTTP/1.0\r\n"
631: "Connection: close\r\n"
632: "Accept: */*\r\n");
633:
634: /* Tack on the body */
635: g_string_append_printf(request, "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n");
636: g_string_append_printf(request, "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n", body->len);
637: g_string_append_len(request, body->str, body->len);
638: g_string_free(body, TRUE);
639:
640: /* Send the POST request */
641: od->url_data = purple_util_fetch_url_request_len_with_account(
642: purple_connection_get_account(gc), get_client_login_url(od),
643: TRUE, NULL, FALSE, request->str, FALSE, -1,
644: client_login_cb, od);
645: g_string_free(request, TRUE);
646: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>