rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / oscar / kerberos.c
blob0223d30a9f0f96d084ecba491906760b61ff6e61
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(PurpleHttpConnection *http_conn,
152 PurpleHttpResponse *response, gpointer _od)
154 OscarData *od = _od;
155 PurpleConnection *gc;
156 const gchar *got_data;
157 size_t got_len;
158 ByteStream bs;
159 aim_xsnac_t xsnac = {0};
160 guint16 len;
161 gchar *bosip = NULL;
162 gchar *tlsCertName = NULL;
163 guint8 *cookie = NULL;
164 guint32 cookie_len = 0;
165 char *host; int port;
166 gsize i;
168 gc = od->gc;
170 od->hc = NULL;
172 if (!purple_http_response_is_successful(response)) {
173 gchar *tmp;
174 gchar *url;
176 url = get_kdc_url(od);
177 tmp = g_strdup_printf(_("Error requesting %s: %s"),
178 url,
179 purple_http_response_get_error(response));
180 purple_connection_error(gc,
181 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
182 g_free(tmp);
183 g_free(url);
184 return;
187 got_data = purple_http_response_get_data(response, &got_len);
188 purple_debug_info("oscar", "Received kerberos login HTTP response %lu : ", got_len);
190 byte_stream_init(&bs, (guint8 *)got_data, got_len);
192 xsnac.family = byte_stream_get16(&bs);
193 xsnac.subtype = byte_stream_get16(&bs);
194 byte_stream_getrawbuf(&bs, (guint8 *) xsnac.flags, 8);
196 if (xsnac.family == 0x50C && xsnac.subtype == 0x0005) {
197 purple_connection_error(gc,
198 PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
199 _("Incorrect password"));
200 return;
202 if (xsnac.family != 0x50C || xsnac.subtype != 0x0003) {
203 purple_connection_error(gc,
204 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
205 _("Error parsing response from authentication server"));
206 return;
208 xsnac.request_id = byte_stream_get16(&bs);
209 xsnac.epoch = byte_stream_get32(&bs);
210 xsnac.unknown = byte_stream_get32(&bs);
211 len = byte_stream_get16(&bs);
212 xsnac.principal1 = byte_stream_getstr(&bs, len);
213 len = byte_stream_get16(&bs);
214 xsnac.principal2 = byte_stream_getstr(&bs, len);
215 xsnac.num_tokens = byte_stream_get16(&bs);
217 purple_debug_info("oscar", "KDC: %d tokens between '%s' and '%s'\n",
218 xsnac.num_tokens, xsnac.principal1, xsnac.principal2);
219 xsnac.tokens = g_new0(aim_xsnac_token_t, xsnac.num_tokens);
220 for (i = 0; i < xsnac.num_tokens; i++) {
221 GSList *tlv;
223 tlv = aim_tlvlist_readnum(&bs, 1);
224 if (tlv)
225 xsnac.tokens[i].main_tlv = tlv->data;
226 g_slist_free(tlv);
228 len = byte_stream_get16(&bs);
229 xsnac.tokens[i].principal1 = byte_stream_getstr(&bs, len);
230 len = byte_stream_get16(&bs);
231 xsnac.tokens[i].service = byte_stream_getstr(&bs, len);
232 len = byte_stream_get16(&bs);
233 xsnac.tokens[i].principal1_again = byte_stream_getstr(&bs, len);
234 len = byte_stream_get16(&bs);
235 xsnac.tokens[i].principal2 = byte_stream_getstr(&bs, len);
236 xsnac.tokens[i].unknown = byte_stream_get8(&bs);
237 len = byte_stream_get16(&bs);
238 xsnac.tokens[i].footer = byte_stream_getraw(&bs, len);
240 xsnac.tokens[i].dates.unknown1 = byte_stream_get32(&bs);
241 xsnac.tokens[i].dates.unknown2 = byte_stream_get32(&bs);
242 xsnac.tokens[i].dates.epoch_now = byte_stream_get32(&bs);
243 xsnac.tokens[i].dates.epoch_valid = byte_stream_get32(&bs);
244 xsnac.tokens[i].dates.epoch_renew = byte_stream_get32(&bs);
245 xsnac.tokens[i].dates.epoch_expire = byte_stream_get32(&bs);
246 xsnac.tokens[i].dates.unknown3 = byte_stream_get32(&bs);
247 xsnac.tokens[i].dates.unknown4 = byte_stream_get32(&bs);
248 xsnac.tokens[i].dates.unknown5 = byte_stream_get32(&bs);
250 len = byte_stream_get16(&bs);
251 xsnac.tokens[i].tlvlist = aim_tlvlist_readnum(&bs, len);
253 purple_debug_info("oscar", "Token %lu has %d TLVs for service '%s'\n",
254 i, len, xsnac.tokens[i].service);
256 len = byte_stream_get16(&bs);
257 xsnac.tlvlist = aim_tlvlist_readnum(&bs, len);
259 for (i = 0; i < xsnac.num_tokens; i++) {
260 if (purple_strequal(xsnac.tokens[i].service, "im/boss")) {
261 aim_tlv_t *tlv;
262 GSList *tlvlist;
263 ByteStream tbs;
265 tlv = aim_tlv_gettlv(xsnac.tokens[i].tlvlist, 0x0003, 1);
266 if (tlv != NULL) {
267 byte_stream_init(&tbs, tlv->value, tlv->length);
268 byte_stream_get32(&tbs);
269 tlvlist = aim_tlvlist_read(&tbs);
270 if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
271 bosip = aim_tlv_getstr(tlvlist, 0x0005, 1);
272 if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
273 tlsCertName = aim_tlv_getstr(tlvlist, 0x008D, 1);
274 tlv = aim_tlv_gettlv(tlvlist, 0x0006, 1);
275 if (tlv) {
276 cookie_len = tlv->length;
277 cookie = tlv->value;
280 break;
283 if (bosip && cookie) {
284 port = AIM_DEFAULT_KDC_PORT;
285 for (i = 0; i < strlen(bosip); i++) {
286 if (bosip[i] == ':') {
287 port = atoi(&(bosip[i+1]));
288 break;
291 host = g_strndup(bosip, i);
292 oscar_connect_to_bos(gc, od, host, port, cookie, cookie_len, tlsCertName);
293 g_free(host);
294 } else {
295 purple_connection_error(gc,
296 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
297 _("Unknown error during authentication"));
299 aim_xsnac_free(&xsnac);
300 g_free(tlsCertName);
301 g_free(bosip);
305 * This function sends a binary blob request to the Kerberos KDC server
306 * https://kdc.uas.aol.com with the user's username and password and
307 * receives the IM cookie, which is used to request a connection to the
308 * BOSS server.
309 * The binary data below is what AIM 8.0.8.1 sends in order to authenticate
310 * to the KDC server. It is an 'X-SNAC' packet, which is relatively similar
311 * to SNAC packets but somehow different.
312 * The header starts with the 0x50C family follow by 0x0002 subtype, then
313 * some fixed length data and TLVs. The string "COOL" appears in there for
314 * some reason followed by the 'US' and 'en' strings.
315 * Then the 'imApp key=<client key>' comes after that, and then the username
316 * and the string "im/boss" which seems to represent the service we are
317 * requesting the authentication for. Changing that will lead to a
318 * 'unknown service' error. The client key is then added again (without the
319 * 'imApp key' string prepended to it) then a XOR-ed version of the password.
320 * The meaning of the header/footer/in-between bytes is not known but never
321 * seems to change so there is no need to reverse engineer their meaning at
322 * this point.
324 void send_kerberos_login(OscarData *od, const char *username)
326 PurpleConnection *gc;
327 PurpleHttpRequest *req;
328 gchar *url;
329 const gchar *password;
330 gchar password_xored[MAXAIMPASSLEN];
331 const gchar *client_key;
332 gchar *imapp_key;
333 GString *body;
334 guint16 len_be;
335 guint16 reqid;
336 const gchar header[] = {
337 0x05, 0x0C, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
338 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
339 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
340 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x05,
341 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05,
342 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x18, 0x99,
343 0x00, 0x05, 0x00, 0x04, 0x43, 0x4F, 0x4F, 0x4C,
344 0x00, 0x0A, 0x00, 0x02, 0x00, 0x01, 0x00, 0x0B,
345 0x00, 0x04, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00,
346 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
347 0x55, 0x53, 0x00, 0x02, 0x65, 0x6E, 0x00, 0x04,
348 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0D,
349 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,
350 0x00, 0x05};
351 const gchar pre_username[] = {
352 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x01, 0x8B,
353 0x01, 0x00, 0x00, 0x00, 0x00};
354 const gchar post_username[] = {
355 0x00, 0x07, 0x69, 0x6D, 0x2F, 0x62, 0x6F, 0x73,
356 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
357 0x04, 0x00, 0x02};
358 const gchar pre_password[] = {
359 0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01,
360 0x00, 0x00};
361 const gchar post_password[] = {0x00, 0x00, 0x00, 0x1D};
362 const gchar footer[] = {
363 0x00, 0x21, 0x00, 0x32, 0x00, 0x01, 0x10, 0x03,
364 0x00, 0x2C, 0x00, 0x07, 0x00, 0x14, 0x00, 0x04,
365 0x00, 0x00, 0x01, 0x8B, 0x00, 0x16, 0x00, 0x02,
366 0x00, 0x26, 0x00, 0x17, 0x00, 0x02, 0x00, 0x07,
367 0x00, 0x18, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19,
368 0x00, 0x02, 0x00, 0x0D, 0x00, 0x1A, 0x00, 0x02,
369 0x00, 0x04, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x28,
370 0x00, 0x00};
372 gc = od->gc;
374 password = purple_connection_get_password(gc);
375 aim_encode_password(password, password_xored);
377 client_key = get_client_key(od);
378 imapp_key = g_strdup_printf("imApp key=%s", client_key);
380 /* Construct the body of the HTTP POST request */
381 body = g_string_new(NULL);
382 g_string_append_len(body, header, sizeof(header));
383 reqid = (guint16) g_random_int();
384 g_string_overwrite_len(body, 0xC, (void *)&reqid, sizeof(guint16));
386 len_be = GUINT16_TO_BE(strlen(imapp_key));
387 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
388 g_string_append(body, imapp_key);
390 len_be = GUINT16_TO_BE(strlen(username));
391 g_string_append_len(body, pre_username, sizeof(pre_username));
392 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
393 g_string_append(body, username);
394 g_string_append_len(body, post_username, sizeof(post_username));
396 len_be = GUINT16_TO_BE(strlen(password) + 0x10);
397 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
398 g_string_append_len(body, pre_password, sizeof(pre_password));
399 len_be = GUINT16_TO_BE(strlen(password) + 4);
400 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
401 len_be = GUINT16_TO_BE(strlen(password));
402 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
403 g_string_append_len(body, password_xored, strlen(password));
404 g_string_append_len(body, post_password, sizeof(post_password));
406 len_be = GUINT16_TO_BE(strlen(client_key));
407 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
408 g_string_append(body, client_key);
409 g_string_append_len(body, footer, sizeof(footer));
411 g_free(imapp_key);
413 url = get_kdc_url(od);
414 req = purple_http_request_new(url);
415 purple_http_request_set_method(req, "POST");
416 purple_http_request_header_set(req, "Content-Type",
417 "application/x-snac");
418 purple_http_request_header_set(req, "Accept",
419 "application/x-snac");
420 purple_http_request_set_contents(req, body->str, body->len);
421 od->hc = purple_http_request(gc, req, kerberos_login_cb, od);
422 purple_http_request_unref(req);
424 g_string_free(body, TRUE);
425 g_free(url);