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 * Family 0x0017 - Authentication.
24 * Deals with the authorizer for SNAC-based login, and also old-style
30 #include "oscarcommon.h"
34 /* #define USE_XOR_FOR_ICQ */
36 #ifdef USE_XOR_FOR_ICQ
38 * Encode a password using old XOR method
40 * This takes a const pointer to a (null terminated) string
41 * containing the unencoded password. It also gets passed
42 * an already allocated buffer to store the encoded password.
43 * This buffer should be the exact length of the password without
44 * the null. The encoded password buffer /is not %NULL terminated/.
46 * The encoding_table seems to be a fixed set of values. We'll
47 * hope it doesn't change over time!
49 * This is only used for the XOR method, not the better MD5 method.
51 * @param password Incoming password.
52 * @param encoded Buffer to put encoded password.
55 aim_encode_password(const char *password
, guint8
*encoded
)
57 guint8 encoding_table
[] = {
58 0xf3, 0x26, 0x81, 0xc4,
59 0x39, 0x86, 0xdb, 0x92,
60 0x71, 0xa3, 0xb9, 0xe6,
61 0x53, 0x7a, 0x95, 0x7c
65 for (i
= 0; i
< strlen(password
); i
++)
66 encoded
[i
] = (password
[i
] ^ encoding_table
[i
]);
74 aim_encode_password_md5(const char *password
, size_t password_len
, const char *key
, guint8
*digest
)
77 gsize digest_len
= 16;
79 hash
= g_checksum_new(G_CHECKSUM_MD5
);
80 g_checksum_update(hash
, (const guchar
*)key
, -1);
81 g_checksum_update(hash
, (const guchar
*)password
, password_len
);
82 g_checksum_update(hash
, (const guchar
*)AIM_MD5_STRING
, -1);
83 g_checksum_get_digest(hash
, digest
, &digest_len
);
84 g_checksum_free(hash
);
90 aim_encode_password_md5(const char *password
, size_t password_len
, const char *key
, guint8
*digest
)
93 guchar passdigest
[16];
94 gsize digest_len
= 16;
96 hash
= g_checksum_new(G_CHECKSUM_MD5
);
97 g_checksum_update(hash
, (const guchar
*)password
, password_len
);
98 g_checksum_get_digest(hash
, passdigest
, &digest_len
);
99 g_checksum_reset(hash
);
101 g_checksum_update(hash
, (const guchar
*)key
, -1);
102 g_checksum_update(hash
, passdigest
, digest_len
);
103 g_checksum_update(hash
, (const guchar
*)AIM_MD5_STRING
, -1);
104 g_checksum_get_digest(hash
, digest
, &digest_len
);
105 g_checksum_free(hash
);
111 #ifdef USE_XOR_FOR_ICQ
113 * Part two of the ICQ hack. Note the ignoring of the key.
116 goddamnicq2(OscarData
*od
, FlapConnection
*conn
, const char *sn
, const char *password
, ClientInfo
*ci
)
119 GSList
*tlvlist
= NULL
;
121 guint8
*password_encoded
;
124 passwdlen
= strlen(password
);
125 password_encoded
= (guint8
*)g_malloc(passwdlen
+1);
126 if (passwdlen
> MAXICQPASSLEN
)
127 passwdlen
= MAXICQPASSLEN
;
129 frame
= flap_frame_new(od
, 0x01, 1152);
131 aim_encode_password(password
, password_encoded
);
133 distrib
= oscar_get_ui_info_int(
134 od
->icq
? "prpl-icq-distid" : "prpl-aim-distid",
137 byte_stream_put32(&frame
->data
, 0x00000001); /* FLAP Version */
138 aim_tlvlist_add_str(&tlvlist
, 0x0001, sn
);
139 aim_tlvlist_add_raw(&tlvlist
, 0x0002, passwdlen
, password_encoded
);
141 if (ci
->clientstring
!= NULL
)
142 aim_tlvlist_add_str(&tlvlist
, 0x0003, ci
->clientstring
);
144 gchar
*clientstring
= oscar_get_clientstring();
145 aim_tlvlist_add_str(&tlvlist
, 0x0003, clientstring
);
146 g_free(clientstring
);
148 aim_tlvlist_add_16(&tlvlist
, 0x0016, (guint16
)ci
->clientid
);
149 aim_tlvlist_add_16(&tlvlist
, 0x0017, (guint16
)ci
->major
);
150 aim_tlvlist_add_16(&tlvlist
, 0x0018, (guint16
)ci
->minor
);
151 aim_tlvlist_add_16(&tlvlist
, 0x0019, (guint16
)ci
->point
);
152 aim_tlvlist_add_16(&tlvlist
, 0x001a, (guint16
)ci
->build
);
153 aim_tlvlist_add_32(&tlvlist
, 0x0014, distrib
); /* distribution chan */
154 aim_tlvlist_add_str(&tlvlist
, 0x000f, ci
->lang
);
155 aim_tlvlist_add_str(&tlvlist
, 0x000e, ci
->country
);
157 aim_tlvlist_write(&frame
->data
, &tlvlist
);
159 g_free(password_encoded
);
160 aim_tlvlist_free(tlvlist
);
162 flap_connection_send(conn
, frame
);
171 * This is the initial login request packet.
173 * NOTE!! If you want/need to make use of the aim_sendmemblock() function,
174 * then the client information you send here must exactly match the
175 * executable that you're pulling the data from.
178 * clientstring = "AOL Instant Messenger (TM) version 1.1.19 for Java built 03/24/98, freeMem 215871 totalMem 1048567, i686, Linus, #2 SMP Sun Feb 11 03:41:17 UTC 2001 2.4.1-ac9, IBM Corporation, 1.1.8, 45.3, Tue Mar 27 12:09:17 PST 2001"
184 * unknown= (not sent)
186 * AIM for Linux 1.1.112:
187 * clientstring = "AOL Instant Messenger (SM)"
193 * unknown= 0x0000008b
196 * @param truncate_pass Truncate the password to 8 characters. This
197 * usually happens for AOL accounts. We are told that we
198 * should truncate it if the 0x0017/0x0007 SNAC contains
199 * a TLV of type 0x0026 with data 0x0000.
200 * @param allow_multiple_logins Allow multiple logins? If TRUE, the AIM
201 * server will prompt the user when multiple logins occur. If
202 * FALSE, existing connections (on other clients) will be
203 * disconnected automatically as we connect.
206 aim_send_login(OscarData
*od
, FlapConnection
*conn
, const char *sn
, const char *password
, gboolean truncate_pass
, ClientInfo
*ci
, const char *key
, gboolean allow_multiple_logins
)
209 GSList
*tlvlist
= NULL
;
215 if (!ci
|| !sn
|| !password
)
218 #ifdef USE_XOR_FOR_ICQ
219 /* If we're signing on an ICQ account then use the older, XOR login method */
220 if (aim_snvalid_icq(sn
))
221 return goddamnicq2(od
, conn
, sn
, password
, ci
);
224 frame
= flap_frame_new(od
, 0x02, 1152);
226 snacid
= aim_cachesnac(od
, SNAC_FAMILY_AUTH
, 0x0002, 0x0000, NULL
, 0);
227 aim_putsnac(&frame
->data
, SNAC_FAMILY_AUTH
, 0x0002, snacid
);
229 aim_tlvlist_add_str(&tlvlist
, 0x0001, sn
);
231 /* Truncate ICQ and AOL passwords, if necessary */
232 password_len
= strlen(password
);
233 if (oscar_util_valid_name_icq(sn
) && (password_len
> MAXICQPASSLEN
))
234 password_len
= MAXICQPASSLEN
;
235 else if (truncate_pass
&& password_len
> 8)
238 aim_encode_password_md5(password
, password_len
, key
, digest
);
240 distrib
= oscar_get_ui_info_int(
241 od
->icq
? "prpl-icq-distid" : "prpl-aim-distid",
244 aim_tlvlist_add_raw(&tlvlist
, 0x0025, 16, digest
);
247 aim_tlvlist_add_noval(&tlvlist
, 0x004c);
250 if (ci
->clientstring
!= NULL
)
251 aim_tlvlist_add_str(&tlvlist
, 0x0003, ci
->clientstring
);
253 gchar
*clientstring
= oscar_get_clientstring();
254 aim_tlvlist_add_str(&tlvlist
, 0x0003, clientstring
);
255 g_free(clientstring
);
257 aim_tlvlist_add_16(&tlvlist
, 0x0016, (guint16
)ci
->clientid
);
258 aim_tlvlist_add_16(&tlvlist
, 0x0017, (guint16
)ci
->major
);
259 aim_tlvlist_add_16(&tlvlist
, 0x0018, (guint16
)ci
->minor
);
260 aim_tlvlist_add_16(&tlvlist
, 0x0019, (guint16
)ci
->point
);
261 aim_tlvlist_add_16(&tlvlist
, 0x001a, (guint16
)ci
->build
);
262 aim_tlvlist_add_32(&tlvlist
, 0x0014, distrib
);
263 aim_tlvlist_add_str(&tlvlist
, 0x000f, ci
->lang
);
264 aim_tlvlist_add_str(&tlvlist
, 0x000e, ci
->country
);
267 * If set, old-fashioned buddy lists will not work. You will need
270 aim_tlvlist_add_8(&tlvlist
, 0x004a, (allow_multiple_logins
? 0x01 : 0x03));
272 aim_tlvlist_write(&frame
->data
, &tlvlist
);
274 aim_tlvlist_free(tlvlist
);
276 flap_connection_send(conn
, frame
);
282 * This is sent back as a general response to the login command.
283 * It can be either an error or a success, depending on the
284 * presence of certain TLVs.
286 * The client should check the value passed as errorcode. If
287 * its nonzero, there was an error.
290 parse(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
293 aim_rxcallback_t userfunc
;
294 struct aim_authresp_info
*info
;
297 info
= g_new0(struct aim_authresp_info
, 1);
300 * Read block of TLVs. All further data is derived
301 * from what is parsed here.
303 tlvlist
= aim_tlvlist_read(bs
);
306 * No matter what, we should have a username.
308 if (aim_tlv_gettlv(tlvlist
, 0x0001, 1)) {
309 info
->bn
= aim_tlv_getstr(tlvlist
, 0x0001, 1);
310 purple_connection_set_display_name(od
->gc
, info
->bn
);
314 * Check for an error code. If so, we should also
317 if (aim_tlv_gettlv(tlvlist
, 0x0008, 1))
318 info
->errorcode
= aim_tlv_get16(tlvlist
, 0x0008, 1);
319 if (aim_tlv_gettlv(tlvlist
, 0x0004, 1))
320 info
->errorurl
= aim_tlv_getstr(tlvlist
, 0x0004, 1);
323 * BOS server address.
325 if (aim_tlv_gettlv(tlvlist
, 0x0005, 1))
326 info
->bosip
= aim_tlv_getstr(tlvlist
, 0x0005, 1);
329 * Authorization cookie.
331 if (aim_tlv_gettlv(tlvlist
, 0x0006, 1)) {
334 tmptlv
= aim_tlv_gettlv(tlvlist
, 0x0006, 1);
337 info
->cookielen
= tmptlv
->length
;
338 info
->cookie
= tmptlv
->value
;
343 * The email address attached to this account
344 * Not available for ICQ or @mac.com logins.
345 * If you receive this TLV, then you are allowed to use
346 * family 0x0018 to check the status of your email.
347 * XXX - Not really true!
349 if (aim_tlv_gettlv(tlvlist
, 0x0011, 1))
350 info
->email
= aim_tlv_getstr(tlvlist
, 0x0011, 1);
353 * The registration status. (Not real sure what it means.)
354 * Not available for ICQ or @mac.com logins.
357 * 2 = Limited disclosure
358 * 3 = Full disclosure
360 * This has to do with whether your email address is available
361 * to other users or not. AFAIK, this feature is no longer used.
363 * Means you can use the admin family? (0x0007)
366 if (aim_tlv_gettlv(tlvlist
, 0x0013, 1))
367 info
->regstatus
= aim_tlv_get16(tlvlist
, 0x0013, 1);
369 if (aim_tlv_gettlv(tlvlist
, 0x0040, 1))
370 info
->latestbeta
.build
= aim_tlv_get32(tlvlist
, 0x0040, 1);
371 if (aim_tlv_gettlv(tlvlist
, 0x0041, 1))
372 info
->latestbeta
.url
= aim_tlv_getstr(tlvlist
, 0x0041, 1);
373 if (aim_tlv_gettlv(tlvlist
, 0x0042, 1))
374 info
->latestbeta
.info
= aim_tlv_getstr(tlvlist
, 0x0042, 1);
375 if (aim_tlv_gettlv(tlvlist
, 0x0043, 1))
376 info
->latestbeta
.name
= aim_tlv_getstr(tlvlist
, 0x0043, 1);
378 if (aim_tlv_gettlv(tlvlist
, 0x0044, 1))
379 info
->latestrelease
.build
= aim_tlv_get32(tlvlist
, 0x0044, 1);
380 if (aim_tlv_gettlv(tlvlist
, 0x0045, 1))
381 info
->latestrelease
.url
= aim_tlv_getstr(tlvlist
, 0x0045, 1);
382 if (aim_tlv_gettlv(tlvlist
, 0x0046, 1))
383 info
->latestrelease
.info
= aim_tlv_getstr(tlvlist
, 0x0046, 1);
384 if (aim_tlv_gettlv(tlvlist
, 0x0047, 1))
385 info
->latestrelease
.name
= aim_tlv_getstr(tlvlist
, 0x0047, 1);
388 * URL to change password.
390 if (aim_tlv_gettlv(tlvlist
, 0x0054, 1))
391 info
->chpassurl
= aim_tlv_getstr(tlvlist
, 0x0054, 1);
395 if ((userfunc
= aim_callhandler(od
, snac
? snac
->family
: SNAC_FAMILY_AUTH
, snac
? snac
->subtype
: 0x0003)))
396 ret
= userfunc(od
, conn
, frame
, info
);
398 aim_tlvlist_free(tlvlist
);
403 #ifdef USE_XOR_FOR_ICQ
405 * Subtype 0x0007 (kind of) - Send a fake type 0x0007 SNAC to the client
407 * This is a bit confusing.
409 * Normal SNAC login goes like this:
411 * - server sends flap version
412 * - client sends flap version
413 * - client sends username (17/6)
414 * - server sends hash key (17/7)
415 * - client sends auth request (17/2 -- aim_send_login)
418 * XOR login (for ICQ) goes like this:
420 * - server sends flap version
421 * - client sends auth request which contains flap version (aim_send_login)
424 * For the client API, we make them implement the most complicated version,
425 * and for the simpler version, we fake it and make it look like the more
426 * complicated process.
428 * This is done by giving the client a faked key, just so we can convince
429 * them to call aim_send_login right away, which will detect the session
430 * flag that says this is XOR login and ignore the key, sending an ICQ
431 * login request instead of the normal SNAC one.
433 * As soon as AOL makes ICQ log in the same way as AIM, this is /gone/.
436 goddamnicq(OscarData
*od
, FlapConnection
*conn
, const char *sn
)
439 aim_rxcallback_t userfunc
;
441 if ((userfunc
= aim_callhandler(od
, SNAC_FAMILY_AUTH
, 0x0007)))
442 userfunc(od
, conn
, &frame
, "");
451 * In AIM 3.5 protocol, the first stage of login is to request login from the
452 * Authorizer, passing it the username for verification. If the name is
453 * invalid, a 0017/0003 is spit back, with the standard error contents. If
454 * valid, a 0017/0007 comes back, which is the signal to send it the main
455 * login command (0017/0002).
459 aim_request_login(OscarData
*od
, FlapConnection
*conn
, const char *sn
)
463 GSList
*tlvlist
= NULL
;
465 if (!od
|| !conn
|| !sn
)
468 #ifdef USE_XOR_FOR_ICQ
469 if (aim_snvalid_icq(sn
))
470 return goddamnicq(od
, conn
, sn
);
473 frame
= flap_frame_new(od
, 0x02, 10+2+2+strlen(sn
)+8);
475 snacid
= aim_cachesnac(od
, SNAC_FAMILY_AUTH
, 0x0006, 0x0000, NULL
, 0);
476 aim_putsnac(&frame
->data
, SNAC_FAMILY_AUTH
, 0x0006, snacid
);
478 aim_tlvlist_add_str(&tlvlist
, 0x0001, sn
);
480 /* Tell the server we support SecurID logins. */
481 aim_tlvlist_add_noval(&tlvlist
, 0x004b);
483 /* Unknown. Sent in recent WinAIM clients.*/
484 aim_tlvlist_add_noval(&tlvlist
, 0x005a);
486 aim_tlvlist_write(&frame
->data
, &tlvlist
);
487 aim_tlvlist_free(tlvlist
);
489 flap_connection_send(conn
, frame
);
497 * Middle handler for 0017/0007 SNACs. Contains the auth key prefixed
498 * by only its length in a two byte word.
500 * Calls the client, which should then use the value to call aim_send_login.
504 keyparse(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
509 gboolean truncate_pass
;
510 PurpleConnection
*gc
;
511 PurpleAccount
*account
;
512 ClientInfo aiminfo
= CLIENTINFO_PURPLE_AIM
;
513 ClientInfo icqinfo
= CLIENTINFO_PURPLE_ICQ
;
516 account
= purple_connection_get_account(gc
);
518 keylen
= byte_stream_get16(bs
);
519 keystr
= byte_stream_getstr(bs
, keylen
);
520 if (!g_utf8_validate(keystr
, -1, NULL
)) {
521 purple_debug_warning("oscar", "Received SNAC %04hx/%04hx with "
522 "invalid UTF-8 keystr.\n", snac
->family
, snac
->subtype
);
523 purple_connection_error(gc
, PURPLE_CONNECTION_ERROR_OTHER_ERROR
,
524 _("Received unexpected response from server"));
529 tlvlist
= aim_tlvlist_read(bs
);
532 * If the truncate_pass TLV exists then we should truncate the
533 * user's password to 8 characters. This flag is sent to us
534 * when logging in with an AOL user's username.
536 truncate_pass
= aim_tlv_gettlv(tlvlist
, 0x0026, 1) != NULL
;
538 /* XXX - When GiantGrayPanda signed on AIM I got a thing asking me to register
539 * for the netscape network. This SNAC had a type 0x0058 TLV with length 10.
540 * Data is 0x0007 0004 3e19 ae1e 0006 0004 0000 0005 */
542 aim_send_login(od
, conn
, purple_account_get_username(account
),
543 purple_connection_get_password(gc
), truncate_pass
,
544 od
->icq
? &icqinfo
: &aiminfo
, keystr
,
545 purple_account_get_bool(account
, "allow_multiple_logins", OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS
));
547 purple_connection_update_progress(gc
,
548 _("Password sent"), 2, OSCAR_CONNECT_STEPS
);
551 aim_tlvlist_free(tlvlist
);
559 * Receive SecurID request.
562 got_securid_request(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
565 aim_rxcallback_t userfunc
;
567 if ((userfunc
= aim_callhandler(od
, snac
->family
, snac
->subtype
)))
568 ret
= userfunc(od
, conn
, frame
);
576 * Send SecurID response.
579 aim_auth_securid_send(OscarData
*od
, const char *securid
)
581 FlapConnection
*conn
;
585 if (!od
|| !(conn
= flap_connection_getbytype_all(od
, SNAC_FAMILY_AUTH
)) || !securid
)
588 len
= strlen(securid
);
590 frame
= flap_frame_new(od
, 0x02, 10+2+len
);
592 /* aim_snacid_t snacid = */ aim_cachesnac(od
, SNAC_FAMILY_AUTH
, SNAC_SUBTYPE_AUTH_SECURID_RESPONSE
, 0x0000, NULL
, 0);
593 aim_putsnac(&frame
->data
, SNAC_FAMILY_AUTH
, SNAC_SUBTYPE_AUTH_SECURID_RESPONSE
, 0);
595 byte_stream_put16(&frame
->data
, len
);
596 byte_stream_putstr(&frame
->data
, securid
);
598 flap_connection_send(conn
, frame
);
604 auth_shutdown(OscarData
*od
, aim_module_t
*mod
)
606 if (od
->authinfo
!= NULL
)
608 g_free(od
->authinfo
->bn
);
609 g_free(od
->authinfo
->bosip
);
610 g_free(od
->authinfo
->errorurl
);
611 g_free(od
->authinfo
->email
);
612 g_free(od
->authinfo
->chpassurl
);
613 g_free(od
->authinfo
->latestrelease
.name
);
614 g_free(od
->authinfo
->latestrelease
.url
);
615 g_free(od
->authinfo
->latestrelease
.info
);
616 g_free(od
->authinfo
->latestbeta
.name
);
617 g_free(od
->authinfo
->latestbeta
.url
);
618 g_free(od
->authinfo
->latestbeta
.info
);
619 g_free(od
->authinfo
);
624 snachandler(OscarData
*od
, FlapConnection
*conn
, aim_module_t
*mod
, FlapFrame
*frame
, aim_modsnac_t
*snac
, ByteStream
*bs
)
626 if (snac
->subtype
== 0x0003)
627 return parse(od
, conn
, mod
, frame
, snac
, bs
);
628 else if (snac
->subtype
== 0x0007)
629 return keyparse(od
, conn
, mod
, frame
, snac
, bs
);
630 else if (snac
->subtype
== 0x000a)
631 return got_securid_request(od
, conn
, mod
, frame
, snac
, bs
);
637 auth_modfirst(OscarData
*od
, aim_module_t
*mod
)
639 mod
->family
= SNAC_FAMILY_AUTH
;
640 mod
->version
= 0x0000;
642 strncpy(mod
->name
, "auth", sizeof(mod
->name
));
643 mod
->snachandler
= snachandler
;
644 mod
->shutdown
= auth_shutdown
;