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
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.
38 #include "oscarcommon.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
51 gchar
*principal1_again
;
79 aim_xsnac_token_t
*tokens
;
83 static gchar
*get_kdc_url(OscarData
*od
)
85 PurpleAccount
*account
= purple_connection_get_account(od
->gc
);
88 gchar
*port_str
= NULL
;
91 server
= purple_account_get_string(account
, "server", AIM_DEFAULT_KDC_SERVER
);
92 port
= purple_account_get_int(account
, "port", AIM_DEFAULT_KDC_PORT
);
94 port_str
= g_strdup_printf(":%d", port
);
95 url
= g_strdup_printf("https://%s%s/", server
, port_str
? port_str
: "");
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
);
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,
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
]);
129 aim_xsnac_free(aim_xsnac_t
*xsnac
)
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
);
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
;
157 aim_xsnac_t xsnac
= {0};
160 gchar
*tlsCertName
= NULL
;
161 guint8
*cookie
= NULL
;
162 guint32 cookie_len
= 0;
163 char *host
; int port
;
170 if (error_message
!= NULL
|| got_len
== 0) {
174 url
= get_kdc_url(od
);
175 tmp
= g_strdup_printf(_("Error requesting %s: %s"),
177 error_message
: _("The server returned an empty response"));
178 purple_connection_error_reason(gc
,
179 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
, tmp
);
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"));
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"));
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
++) {
220 tlv
= aim_tlvlist_readnum(&bs
, 1);
222 xsnac
.tokens
[i
].main_tlv
= tlv
->data
;
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")) {
262 tlv
= aim_tlv_gettlv(xsnac
.tokens
[i
].tlvlist
, 0x0003, 1);
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);
273 cookie_len
= tlv
->length
;
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]));
288 host
= g_strndup(bosip
, i
);
289 oscar_connect_to_bos(gc
, od
, host
, port
, cookie
, cookie_len
, tlsCertName
);
292 purple_connection_error_reason(gc
,
293 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
294 _("Unknown error during authentication"));
296 aim_xsnac_free(&xsnac
);
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
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
321 void send_kerberos_login(OscarData
*od
, const char *username
)
323 PurpleConnection
*gc
;
326 const gchar
*password
;
327 gchar password_xored
[MAXAIMPASSLEN
];
328 const gchar
*client_key
;
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,
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,
355 const gchar pre_password
[] = {
356 0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01,
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,
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
));
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
);