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(G_GNUC_UNUSED SoupSession
*session
, SoupMessage
*msg
,
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
;
168 g_clear_object(&od
->http_conns
);
170 if (!SOUP_STATUS_IS_SUCCESSFUL(msg
->status_code
)) {
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
);
184 purple_debug_info("oscar",
185 "Received kerberos login HTTP response %" G_GOFFSET_FORMAT
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"));
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"));
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
++) {
223 tlv
= aim_tlvlist_readnum(&bs
, 1);
225 xsnac
.tokens
[i
].main_tlv
= tlv
->data
;
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")) {
267 tlv
= aim_tlv_gettlv(xsnac
.tokens
[i
].tlvlist
, 0x0003, 1);
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);
278 cookie_len
= tlv
->length
;
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]));
293 host
= g_strndup(bosip
, i
);
294 oscar_connect_to_bos(gc
, od
, host
, port
, cookie
, cookie_len
, tlsCertName
);
297 purple_connection_error(gc
,
298 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
299 _("Unknown error during authentication"));
301 aim_xsnac_free(&xsnac
);
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
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
326 void send_kerberos_login(OscarData
*od
, const char *username
)
328 PurpleConnection
*gc
;
331 const gchar
*password
;
332 gchar password_xored
[MAXAIMPASSLEN
];
333 const gchar
*client_key
;
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,
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,
360 const gchar pre_password
[] = {
361 0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01,
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,
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
));
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
);