Replace functions which called once with their bodies
[pidgin-git.git] / libpurple / protocols / oscar / kerberos.c
blob81f91de97b90b3ad50ab8f2cd6744ea5d191cd31
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(G_GNUC_UNUSED SoupSession *session, SoupMessage *msg,
152 gpointer user_data)
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 g_clear_object(&od->http_conns);
170 if (!SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
171 gchar *tmp;
172 gchar *url;
174 url = get_kdc_url(od);
175 tmp = g_strdup_printf(_("Error requesting %s: %d %s"), url,
176 msg->status_code, msg->reason_phrase);
177 purple_connection_error(gc,
178 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
179 g_free(tmp);
180 g_free(url);
181 return;
184 purple_debug_info("oscar",
185 "Received kerberos login HTTP response %" G_GOFFSET_FORMAT
186 " : ",
187 msg->response_body->length);
189 byte_stream_init(&bs, (guint8 *)msg->response_body->data,
190 msg->response_body->length);
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",
254 "Token %" G_GSIZE_FORMAT
255 " has %d TLVs for service '%s'\n",
256 i, len, xsnac.tokens[i].service);
258 len = byte_stream_get16(&bs);
259 xsnac.tlvlist = aim_tlvlist_readnum(&bs, len);
261 for (i = 0; i < xsnac.num_tokens; i++) {
262 if (purple_strequal(xsnac.tokens[i].service, "im/boss")) {
263 aim_tlv_t *tlv;
264 GSList *tlvlist;
265 ByteStream tbs;
267 tlv = aim_tlv_gettlv(xsnac.tokens[i].tlvlist, 0x0003, 1);
268 if (tlv != NULL) {
269 byte_stream_init(&tbs, tlv->value, tlv->length);
270 byte_stream_get32(&tbs);
271 tlvlist = aim_tlvlist_read(&tbs);
272 if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
273 bosip = aim_tlv_getstr(tlvlist, 0x0005, 1);
274 if (aim_tlv_gettlv(tlvlist, 0x0005, 1))
275 tlsCertName = aim_tlv_getstr(tlvlist, 0x008D, 1);
276 tlv = aim_tlv_gettlv(tlvlist, 0x0006, 1);
277 if (tlv) {
278 cookie_len = tlv->length;
279 cookie = tlv->value;
282 break;
285 if (bosip && cookie) {
286 port = AIM_DEFAULT_KDC_PORT;
287 for (i = 0; i < strlen(bosip); i++) {
288 if (bosip[i] == ':') {
289 port = atoi(&(bosip[i+1]));
290 break;
293 host = g_strndup(bosip, i);
294 oscar_connect_to_bos(gc, od, host, port, cookie, cookie_len, tlsCertName);
295 g_free(host);
296 } else {
297 purple_connection_error(gc,
298 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
299 _("Unknown error during authentication"));
301 aim_xsnac_free(&xsnac);
302 g_free(tlsCertName);
303 g_free(bosip);
307 * This function sends a binary blob request to the Kerberos KDC server
308 * https://kdc.uas.aol.com with the user's username and password and
309 * receives the IM cookie, which is used to request a connection to the
310 * BOSS server.
311 * The binary data below is what AIM 8.0.8.1 sends in order to authenticate
312 * to the KDC server. It is an 'X-SNAC' packet, which is relatively similar
313 * to SNAC packets but somehow different.
314 * The header starts with the 0x50C family follow by 0x0002 subtype, then
315 * some fixed length data and TLVs. The string "COOL" appears in there for
316 * some reason followed by the 'US' and 'en' strings.
317 * Then the 'imApp key=<client key>' comes after that, and then the username
318 * and the string "im/boss" which seems to represent the service we are
319 * requesting the authentication for. Changing that will lead to a
320 * 'unknown service' error. The client key is then added again (without the
321 * 'imApp key' string prepended to it) then a XOR-ed version of the password.
322 * The meaning of the header/footer/in-between bytes is not known but never
323 * seems to change so there is no need to reverse engineer their meaning at
324 * this point.
326 void send_kerberos_login(OscarData *od, const char *username)
328 PurpleConnection *gc;
329 SoupMessage *msg;
330 gchar *url;
331 const gchar *password;
332 gchar password_xored[MAXAIMPASSLEN];
333 const gchar *client_key;
334 gchar *imapp_key;
335 GString *body;
336 guint16 len_be;
337 guint16 reqid;
338 const gchar header[] = {
339 0x05, 0x0C, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
340 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
341 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
342 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x05,
343 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05,
344 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x18, 0x99,
345 0x00, 0x05, 0x00, 0x04, 0x43, 0x4F, 0x4F, 0x4C,
346 0x00, 0x0A, 0x00, 0x02, 0x00, 0x01, 0x00, 0x0B,
347 0x00, 0x04, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00,
348 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
349 0x55, 0x53, 0x00, 0x02, 0x65, 0x6E, 0x00, 0x04,
350 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0D,
351 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,
352 0x00, 0x05};
353 const gchar pre_username[] = {
354 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x01, 0x8B,
355 0x01, 0x00, 0x00, 0x00, 0x00};
356 const gchar post_username[] = {
357 0x00, 0x07, 0x69, 0x6D, 0x2F, 0x62, 0x6F, 0x73,
358 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
359 0x04, 0x00, 0x02};
360 const gchar pre_password[] = {
361 0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01,
362 0x00, 0x00};
363 const gchar post_password[] = {0x00, 0x00, 0x00, 0x1D};
364 const gchar footer[] = {
365 0x00, 0x21, 0x00, 0x32, 0x00, 0x01, 0x10, 0x03,
366 0x00, 0x2C, 0x00, 0x07, 0x00, 0x14, 0x00, 0x04,
367 0x00, 0x00, 0x01, 0x8B, 0x00, 0x16, 0x00, 0x02,
368 0x00, 0x26, 0x00, 0x17, 0x00, 0x02, 0x00, 0x07,
369 0x00, 0x18, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19,
370 0x00, 0x02, 0x00, 0x0D, 0x00, 0x1A, 0x00, 0x02,
371 0x00, 0x04, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x28,
372 0x00, 0x00};
374 gc = od->gc;
376 password = purple_connection_get_password(gc);
377 aim_encode_password(password, password_xored);
379 client_key = get_client_key(od);
380 imapp_key = g_strdup_printf("imApp key=%s", client_key);
382 /* Construct the body of the HTTP POST request */
383 body = g_string_new(NULL);
384 g_string_append_len(body, header, sizeof(header));
385 reqid = (guint16) g_random_int();
386 g_string_overwrite_len(body, 0xC, (void *)&reqid, sizeof(guint16));
388 len_be = GUINT16_TO_BE(strlen(imapp_key));
389 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
390 g_string_append(body, imapp_key);
392 len_be = GUINT16_TO_BE(strlen(username));
393 g_string_append_len(body, pre_username, sizeof(pre_username));
394 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
395 g_string_append(body, username);
396 g_string_append_len(body, post_username, sizeof(post_username));
398 len_be = GUINT16_TO_BE(strlen(password) + 0x10);
399 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
400 g_string_append_len(body, pre_password, sizeof(pre_password));
401 len_be = GUINT16_TO_BE(strlen(password) + 4);
402 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
403 len_be = GUINT16_TO_BE(strlen(password));
404 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
405 g_string_append_len(body, password_xored, strlen(password));
406 g_string_append_len(body, post_password, sizeof(post_password));
408 len_be = GUINT16_TO_BE(strlen(client_key));
409 g_string_append_len(body, (void *)&len_be, sizeof(guint16));
410 g_string_append(body, client_key);
411 g_string_append_len(body, footer, sizeof(footer));
413 g_free(imapp_key);
415 url = get_kdc_url(od);
416 msg = soup_message_new("POST", url);
417 soup_message_set_request(msg, "application/x-snac", SOUP_MEMORY_TAKE,
418 body->str, body->len);
419 soup_message_headers_replace(msg->request_headers, "Accept",
420 "application/x-snac");
421 soup_session_queue_message(od->http_conns, msg, kerberos_login_cb, od);
423 g_string_free(body, FALSE);
424 g_free(url);