Remove useless comparison
[pidgin-git.git] / libpurple / protocols / oscar / kerberos.c
blobfb909bfdda3f0c7c963f3fbeac7950a04f6bc835
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.
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.
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.
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
21 /**
22 * This file implements AIM's kerberos procedure for authenticating
23 * users. This replaces the older MD5-based and XOR-based
24 * authentication methods that use SNAC family 0x0017.
26 * This doesn't use SNACs or FLAPs at all. It makes https
27 * POSTs to AOL KDC server to validate the user based on the password they
28 * provided to us. Upon successful authentication we receive two tokens
29 * in the response. One is assumed to be the kerberos ticket for authentication
30 * on the various AOL websites, while the other contains BOSS information, such
31 * as the hostname and port number to use, the TLS certificate name as well as
32 * the cookie to use to authenticate to the BOS server.
33 * And then everything else is the same as with BUCP.
37 #include "oscar.h"
38 #include "oscarcommon.h"
39 #include "core.h"
41 #define MAXAIMPASSLEN 16
44 * Incomplete X-SNAC format taken from reverse engineering doen by digsby:
45 * https://github.com/ifwe/digsby/blob/master/digsby/src/oscar/login2.py
47 typedef struct {
48 aim_tlv_t *main_tlv;
49 gchar *principal1;
50 gchar *service;
51 gchar *principal1_again;
52 gchar *principal2;
53 gchar unknown;
54 guint8 *footer;
55 struct {
56 guint32 unknown1;
57 guint32 unknown2;
58 guint32 epoch_now;
59 guint32 epoch_valid;
60 guint32 epoch_renew;
61 guint32 epoch_expire;
62 guint32 unknown3;
63 guint32 unknown4;
64 guint32 unknown5;
65 } dates;
66 GSList *tlvlist;
67 } aim_xsnac_token_t;
69 typedef struct {
70 guint16 family;
71 guint16 subtype;
72 guint8 flags[8];
73 guint16 request_id;
74 guint32 epoch;
75 guint32 unknown;
76 gchar *principal1;
77 gchar *principal2;
78 guint16 num_tokens;
79 aim_xsnac_token_t *tokens;
80 GSList *tlvlist;
81 } aim_xsnac_t;
83 static gchar *get_kdc_url(OscarData *od)
85 PurpleAccount *account = purple_connection_get_account(od->gc);
86 const gchar *server;
87 gchar *url;
88 gchar *port_str = NULL;
89 gint port;
91 server = purple_account_get_string(account, "server", AIM_DEFAULT_KDC_SERVER);
92 port = purple_account_get_int(account, "port", AIM_DEFAULT_KDC_PORT);
93 if (port != 443)
94 port_str = g_strdup_printf(":%d", port);
95 url = g_strdup_printf("https://%s%s/", server, port_str ? port_str : "");
96 g_free(port_str);
98 return url;
101 static const char *get_client_key(OscarData *od)
103 return oscar_get_ui_info_string(
104 od->icq ? "prpl-icq-clientkey" : "prpl-aim-clientkey",
105 od->icq ? ICQ_DEFAULT_CLIENT_KEY : AIM_DEFAULT_CLIENT_KEY);
108 static void
109 aim_encode_password(const char *password, gchar *encoded)
111 guint8 encoding_table[] = {
112 0x76, 0x91, 0xc5, 0xe7,
113 0xd0, 0xd9, 0x95, 0xdd,
114 0x9e, 0x2F, 0xea, 0xd8,
115 0x6B, 0x21, 0xc2, 0xbc,
118 guint i;
121 * We truncate AIM passwords to 16 characters since that's what
122 * the official client does as well.
124 for (i = 0; i < strlen(password) && i < MAXAIMPASSLEN; i++)
125 encoded[i] = (password[i] ^ encoding_table[i]);
128 static void
129 aim_xsnac_free(aim_xsnac_t *xsnac)
131 gint i;
133 g_free(xsnac->principal1);
134 g_free(xsnac->principal2);
135 aim_tlvlist_free(xsnac->tlvlist);
137 for (i = 0; i < xsnac->num_tokens; i++) {
138 g_free(xsnac->tokens[i].main_tlv->value);
139 g_free(xsnac->tokens[i].main_tlv);
140 g_free(xsnac->tokens[i].principal1);
141 g_free(xsnac->tokens[i].service);
142 g_free(xsnac->tokens[i].principal1_again);
143 g_free(xsnac->tokens[i].principal2);
144 g_free(xsnac->tokens[i].footer);
145 aim_tlvlist_free(xsnac->tokens[i].tlvlist);
147 g_free(xsnac->tokens);
150 static void
151 kerberos_login_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
152 const gchar *got_data, gsize got_len, const gchar *error_message)
154 OscarData *od = user_data;
155 PurpleConnection *gc;
156 ByteStream bs;
157 aim_xsnac_t xsnac = {0};
158 guint16 len;
159 gchar *bosip = NULL;
160 gchar *tlsCertName = NULL;
161 guint8 *cookie = NULL;
162 guint32 cookie_len = 0;
163 char *host; int port;
164 gsize i;
166 gc = od->gc;
168 od->url_data = NULL;
170 if (error_message != NULL || got_len == 0) {
171 gchar *tmp;
172 gchar *url;
174 url = get_kdc_url(od);
175 tmp = g_strdup_printf(_("Error requesting %s: %s"),
176 url, error_message ?
177 error_message : _("The server returned an empty response"));
178 purple_connection_error_reason(gc,
179 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
180 g_free(tmp);
181 g_free(url);
182 return;
185 purple_debug_info("oscar", "Received kerberos login HTTP response %lu : ", got_len);
187 byte_stream_init(&bs, (guint8 *)got_data, got_len);
189 xsnac.family = byte_stream_get16(&bs);
190 xsnac.subtype = byte_stream_get16(&bs);
191 byte_stream_getrawbuf(&bs, (guint8 *) xsnac.flags, 8);
193 if (xsnac.family == 0x50C && xsnac.subtype == 0x0005) {
194 purple_connection_error_reason(gc,
195 PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
196 _("Incorrect password"));
197 return;
199 if (xsnac.family != 0x50C || xsnac.subtype != 0x0003) {
200 purple_connection_error_reason(gc,
201 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
202 _("Error parsing response from authentication server"));
203 return;
205 xsnac.request_id = byte_stream_get16(&bs);
206 xsnac.epoch = byte_stream_get32(&bs);
207 xsnac.unknown = byte_stream_get32(&bs);
208 len = byte_stream_get16(&bs);
209 xsnac.principal1 = byte_stream_getstr(&bs, len);
210 len = byte_stream_get16(&bs);
211 xsnac.principal2 = byte_stream_getstr(&bs, len);
212 xsnac.num_tokens = byte_stream_get16(&bs);
214 purple_debug_info("oscar", "KDC: %d tokens between '%s' and '%s'\n",
215 xsnac.num_tokens, xsnac.principal1, xsnac.principal2);
216 xsnac.tokens = g_new0(aim_xsnac_token_t, xsnac.num_tokens);
217 for (i = 0; i < xsnac.num_tokens; i++) {
218 GSList *tlv;
220 tlv = aim_tlvlist_readnum(&bs, 1);
221 if (tlv)
222 xsnac.tokens[i].main_tlv = tlv->data;
223 g_slist_free(tlv);
225 len = byte_stream_get16(&bs);
226 xsnac.tokens[i].principal1 = byte_stream_getstr(&bs, len);
227 len = byte_stream_get16(&bs);
228 xsnac.tokens[i].service = byte_stream_getstr(&bs, len);
229 len = byte_stream_get16(&bs);
230 xsnac.tokens[i].principal1_again = byte_stream_getstr(&bs, len);
231 len = byte_stream_get16(&bs);
232 xsnac.tokens[i].principal2 = byte_stream_getstr(&bs, len);
233 xsnac.tokens[i].unknown = byte_stream_get8(&bs);
234 len = byte_stream_get16(&bs);
235 xsnac.tokens[i].footer = byte_stream_getraw(&bs, len);
237 xsnac.tokens[i].dates.unknown1 = byte_stream_get32(&bs);
238 xsnac.tokens[i].dates.unknown2 = byte_stream_get32(&bs);
239 xsnac.tokens[i].dates.epoch_now = byte_stream_get32(&bs);
240 xsnac.tokens[i].dates.epoch_valid = byte_stream_get32(&bs);
241 xsnac.tokens[i].dates.epoch_renew = byte_stream_get32(&bs);
242 xsnac.tokens[i].dates.epoch_expire = byte_stream_get32(&bs);
243 xsnac.tokens[i].dates.unknown3 = byte_stream_get32(&bs);
244 xsnac.tokens[i].dates.unknown4 = byte_stream_get32(&bs);
245 xsnac.tokens[i].dates.unknown5 = byte_stream_get32(&bs);
247 len = byte_stream_get16(&bs);
248 xsnac.tokens[i].tlvlist = aim_tlvlist_readnum(&bs, len);
250 purple_debug_info("oscar", "Token %lu has %d TLVs for service '%s'\n",
251 i, len, xsnac.tokens[i].service);
253 len = byte_stream_get16(&bs);
254 xsnac.tlvlist = aim_tlvlist_readnum(&bs, len);
256 for (i = 0; i < xsnac.num_tokens; i++) {
257 if (purple_strequal(xsnac.tokens[i].service, "im/boss")) {
258 aim_tlv_t *tlv;
259 GSList *tlvlist;
260 ByteStream tbs;
262 tlv = aim_tlv_gettlv(xsnac.tokens[i].tlvlist, 0x0003, 1);
263 if (tlv != NULL) {
264 byte_stream_init(&tbs, tlv->value, tlv->length);
265 byte_stream_get32(&tbs);
266 tlvlist = aim_tlvlist_read(&tbs);
267 if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
268 bosip = aim_tlv_getstr(tlvlist, 0x0005, 1);
269 if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
270 tlsCertName = aim_tlv_getstr(tlvlist, 0x008D, 1);
271 tlv = aim_tlv_gettlv(tlvlist, 0x0006, 1);
272 if (tlv) {
273 cookie_len = tlv->length;
274 cookie = tlv->value;
277 break;
280 if (bosip && cookie) {
281 port = AIM_DEFAULT_KDC_PORT;
282 for (i = 0; i < strlen(bosip); i++) {
283 if (bosip[i] == ':') {
284 port = atoi(&(bosip[i+1]));
285 break;
288 host = g_strndup(bosip, i);
289 oscar_connect_to_bos(gc, od, host, port, cookie, cookie_len, tlsCertName);
290 g_free(host);
291 } else {
292 purple_connection_error_reason(gc,
293 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
294 _("Unknown error during authentication"));
296 aim_xsnac_free(&xsnac);
297 g_free(tlsCertName);
298 g_free(bosip);
302 * This function sends a binary blob request to the Kerberos KDC server
303 * https://kdc.uas.aol.com with the user's username and password and
304 * receives the IM cookie, which is used to request a connection to the
305 * BOSS server.
306 * The binary data below is what AIM 8.0.8.1 sends in order to authenticate
307 * to the KDC server. It is an 'X-SNAC' packet, which is relatively similar
308 * to SNAC packets but somehow different.
309 * The header starts with the 0x50C family follow by 0x0002 subtype, then
310 * some fixed length data and TLVs. The string "COOL" appears in there for
311 * some reason followed by the 'US' and 'en' strings.
312 * Then the 'imApp key=<client key>' comes after that, and then the username
313 * and the string "im/boss" which seems to represent the service we are
314 * requesting the authentication for. Changing that will lead to a
315 * 'unknown service' error. The client key is then added again (without the
316 * 'imApp key' string prepended to it) then a XOR-ed version of the password.
317 * The meaning of the header/footer/in-between bytes is not known but never
318 * seems to change so there is no need to reverse engineer their meaning at
319 * this point.
321 void send_kerberos_login(OscarData *od, const char *username)
323 PurpleConnection *gc;
324 GString *request;
325 gchar *url;
326 const gchar *password;
327 gchar password_xored[MAXAIMPASSLEN];
328 const gchar *client_key;
329 gchar *imapp_key;
330 GString *body;
331 guint16 len_be;
332 guint16 reqid;
333 const gchar header[] = {
334 0x05, 0x0C, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
335 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
336 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
337 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x05,
338 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05,
339 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x18, 0x99,
340 0x00, 0x05, 0x00, 0x04, 0x43, 0x4F, 0x4F, 0x4C,
341 0x00, 0x0A, 0x00, 0x02, 0x00, 0x01, 0x00, 0x0B,
342 0x00, 0x04, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00,
343 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
344 0x55, 0x53, 0x00, 0x02, 0x65, 0x6E, 0x00, 0x04,
345 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0D,
346 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,
347 0x00, 0x05};
348 const gchar pre_username[] = {
349 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x01, 0x8B,
350 0x01, 0x00, 0x00, 0x00, 0x00};
351 const gchar post_username[] = {
352 0x00, 0x07, 0x69, 0x6D, 0x2F, 0x62, 0x6F, 0x73,
353 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
354 0x04, 0x00, 0x02};
355 const gchar pre_password[] = {
356 0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01,
357 0x00, 0x00};
358 const gchar post_password[] = {0x00, 0x00, 0x00, 0x1D};
359 const gchar footer[] = {
360 0x00, 0x21, 0x00, 0x32, 0x00, 0x01, 0x10, 0x03,
361 0x00, 0x2C, 0x00, 0x07, 0x00, 0x14, 0x00, 0x04,
362 0x00, 0x00, 0x01, 0x8B, 0x00, 0x16, 0x00, 0x02,
363 0x00, 0x26, 0x00, 0x17, 0x00, 0x02, 0x00, 0x07,
364 0x00, 0x18, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19,
365 0x00, 0x02, 0x00, 0x0D, 0x00, 0x1A, 0x00, 0x02,
366 0x00, 0x04, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x28,
367 0x00, 0x00};
369 gc = od->gc;
371 password = purple_connection_get_password(gc);
372 aim_encode_password(password, password_xored);
374 client_key = get_client_key(od);
375 imapp_key = g_strdup_printf("imApp key=%s", client_key);
377 /* Construct the body of the HTTP POST request */
378 body = g_string_new(NULL);
379 g_string_append_len(body, header, sizeof(header));
380 reqid = (guint16) g_random_int();
381 g_string_overwrite_len(body, 0xC, (void *)&reqid, sizeof(guint16));
383 len_be = GUINT16_TO_BE(strlen(imapp_key));
384 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
385 g_string_append(body, imapp_key);
387 len_be = GUINT16_TO_BE(strlen(username));
388 g_string_append_len(body, pre_username, sizeof(pre_username));
389 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
390 g_string_append(body, username);
391 g_string_append_len(body, post_username, sizeof(post_username));
393 len_be = GUINT16_TO_BE(strlen(password) + 0x10);
394 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
395 g_string_append_len(body, pre_password, sizeof(pre_password));
396 len_be = GUINT16_TO_BE(strlen(password) + 4);
397 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
398 len_be = GUINT16_TO_BE(strlen(password));
399 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
400 g_string_append_len(body, password_xored, strlen(password));
401 g_string_append_len(body, post_password, sizeof(post_password));
403 len_be = GUINT16_TO_BE(strlen(client_key));
404 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
405 g_string_append(body, client_key);
406 g_string_append_len(body, footer, sizeof(footer));
408 g_free(imapp_key);
410 url = get_kdc_url(od);
412 /* Construct an HTTP POST request */
413 request = g_string_new("POST / HTTP/1.1\n"
414 "Connection: close\n"
415 "Accept: application/x-snac\n");
417 /* Tack on the body */
418 g_string_append_printf(request, "Content-Type: application/x-snac\n");
419 g_string_append_printf(request, "Content-Length: %" G_GSIZE_FORMAT "\n\n", body->len);
420 g_string_append_len(request, body->str, body->len);
422 /* Send the POST request */
423 od->url_data = purple_util_fetch_url_request_data_len_with_account(
424 purple_connection_get_account(gc), url,
425 TRUE, NULL, TRUE, request->str, request->len, FALSE, -1,
426 kerberos_login_cb, od);
427 g_string_free(request, TRUE);
429 g_string_free(body, TRUE);
430 g_free(url);