diff options
Diffstat (limited to 'protocols/oscar')
38 files changed, 16352 insertions, 0 deletions
diff --git a/protocols/oscar/AUTHORS b/protocols/oscar/AUTHORS new file mode 100644 index 00000000..5ca13988 --- /dev/null +++ b/protocols/oscar/AUTHORS @@ -0,0 +1,31 @@ + +Anyone else want to be in here? + +--- + +N: Adam Fritzler +H: mid +E: mid@auk.cx +W: http://www.auk.cx/~mid,http://www.auk.cx/faim +D: Wrote most of the wap of crap that you see before you. + +N: Josh Myer +E: josh@joshisanerd.com +D: OFT/ODC (not quite finished yet..), random little things, Munger-At-Large, compile-time warnings. + +N: Daniel Reed +H: n, linuxkitty +E: n@ml.org +W: http://users.n.ml.org/n/ +D: Fixed aim_snac.c + +N: Eric Warmenhoven +E: warmenhoven@linux.com +D: Some OFT info, author of the faim interface for gaim + +N: Brock Wilcox +H: awwaiid +E: awwaiid@auk.cx +D: Figured out original password roasting + + diff --git a/protocols/oscar/COPYING b/protocols/oscar/COPYING new file mode 100644 index 00000000..223ede7d --- /dev/null +++ b/protocols/oscar/COPYING @@ -0,0 +1,504 @@ +		  GNU LESSER GENERAL PUBLIC LICENSE +		       Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. +     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL.  It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + +			    Preamble + +  The licenses for most software are designed to take away your +freedom to share and change it.  By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + +  This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it.  You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + +  When we speak of free software, we are referring to freedom of use, +not price.  Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + +  To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights.  These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + +  For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you.  You must make sure that they, too, receive or can get the source +code.  If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it.  And you must show them these terms so they know their rights. + +  We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + +  To protect each distributor, we want to make it very clear that +there is no warranty for the free library.  Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + +  Finally, software patents pose a constant threat to the existence of +any free program.  We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder.  Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + +  Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License.  This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License.  We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + +  When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library.  The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom.  The Lesser General +Public License permits more lax criteria for linking other code with +the library. + +  We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License.  It also provides other free software developers Less +of an advantage over competing non-free programs.  These disadvantages +are the reason we use the ordinary General Public License for many +libraries.  However, the Lesser license provides advantages in certain +special circumstances. + +  For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard.  To achieve this, non-free programs must be +allowed to use the library.  A more frequent case is that a free +library does the same job as widely used non-free libraries.  In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + +  In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software.  For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + +  Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + +  The precise terms and conditions for copying, distribution and +modification follow.  Pay close attention to the difference between a +"work based on the library" and a "work that uses the library".  The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + +		  GNU LESSER GENERAL PUBLIC LICENSE +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +  0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + +  A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + +  The "Library", below, refers to any such software library or work +which has been distributed under these terms.  A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language.  (Hereinafter, translation is +included without limitation in the term "modification".) + +  "Source code" for a work means the preferred form of the work for +making modifications to it.  For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + +  Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope.  The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it).  Whether that is true depends on what the Library does +and what the program that uses the Library does. +   +  1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + +  You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + +  2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +    a) The modified work must itself be a software library. + +    b) You must cause the files modified to carry prominent notices +    stating that you changed the files and the date of any change. + +    c) You must cause the whole of the work to be licensed at no +    charge to all third parties under the terms of this License. + +    d) If a facility in the modified Library refers to a function or a +    table of data to be supplied by an application program that uses +    the facility, other than as an argument passed when the facility +    is invoked, then you must make a good faith effort to ensure that, +    in the event an application does not supply such function or +    table, the facility still operates, and performs whatever part of +    its purpose remains meaningful. + +    (For example, a function in a library to compute square roots has +    a purpose that is entirely well-defined independent of the +    application.  Therefore, Subsection 2d requires that any +    application-supplied function or table used by this function must +    be optional: if the application does not supply it, the square +    root function must still compute square roots.) + +These requirements apply to the modified work as a whole.  If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works.  But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +  3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library.  To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License.  (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.)  Do not make any other change in +these notices. + +  Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + +  This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + +  4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + +  If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + +  5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library".  Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + +  However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library".  The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + +  When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library.  The +threshold for this to be true is not precisely defined by law. + +  If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work.  (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + +  Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + +  6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + +  You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License.  You must supply a copy of this License.  If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License.  Also, you must do one +of these things: + +    a) Accompany the work with the complete corresponding +    machine-readable source code for the Library including whatever +    changes were used in the work (which must be distributed under +    Sections 1 and 2 above); and, if the work is an executable linked +    with the Library, with the complete machine-readable "work that +    uses the Library", as object code and/or source code, so that the +    user can modify the Library and then relink to produce a modified +    executable containing the modified Library.  (It is understood +    that the user who changes the contents of definitions files in the +    Library will not necessarily be able to recompile the application +    to use the modified definitions.) + +    b) Use a suitable shared library mechanism for linking with the +    Library.  A suitable mechanism is one that (1) uses at run time a +    copy of the library already present on the user's computer system, +    rather than copying library functions into the executable, and (2) +    will operate properly with a modified version of the library, if +    the user installs one, as long as the modified version is +    interface-compatible with the version that the work was made with. + +    c) Accompany the work with a written offer, valid for at +    least three years, to give the same user the materials +    specified in Subsection 6a, above, for a charge no more +    than the cost of performing this distribution. + +    d) If distribution of the work is made by offering access to copy +    from a designated place, offer equivalent access to copy the above +    specified materials from the same place. + +    e) Verify that the user has already received a copy of these +    materials or that you have already sent this user a copy. + +  For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it.  However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + +  It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system.  Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + +  7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + +    a) Accompany the combined library with a copy of the same work +    based on the Library, uncombined with any other library +    facilities.  This must be distributed under the terms of the +    Sections above. + +    b) Give prominent notice with the combined library of the fact +    that part of it is a work based on the Library, and explaining +    where to find the accompanying uncombined form of the same work. + +  8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License.  Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License.  However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +  9. You are not required to accept this License, since you have not +signed it.  However, nothing else grants you permission to modify or +distribute the Library or its derivative works.  These actions are +prohibited by law if you do not accept this License.  Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +  10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions.  You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + +  11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License.  If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all.  For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices.  Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +  12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded.  In such case, this License incorporates the limitation as if +written in the body of this License. + +  13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number.  If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation.  If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + +  14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission.  For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this.  Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + +			    NO WARRANTY + +  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +		     END OF TERMS AND CONDITIONS + +           How to Apply These Terms to Your New Libraries + +  If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change.  You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + +  To apply these terms, attach the following notices to the library.  It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + +    <one line to give the library's name and a brief idea of what it does.> +    Copyright (C) <year>  <name of author> + +    This library is free software; you can redistribute it and/or +    modify it under the terms of the GNU Lesser General Public +    License as published by the Free Software Foundation; either +    version 2 of the License, or (at your option) any later version. + +    This library is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +    Lesser General Public License for more details. + +    You should have received a copy of the GNU Lesser General Public +    License along with this library; if not, write to the Free Software +    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary.  Here is a sample; alter the names: + +  Yoyodyne, Inc., hereby disclaims all copyright interest in the +  library `Frob' (a library for tweaking knobs) written by James Random Hacker. + +  <signature of Ty Coon>, 1 April 1990 +  Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/protocols/oscar/Makefile b/protocols/oscar/Makefile new file mode 100644 index 00000000..a83830df --- /dev/null +++ b/protocols/oscar/Makefile @@ -0,0 +1,47 @@ +########################### +## Makefile for BitlBee  ## +##                       ## +## Copyright 2002 Lintux ## +########################### + +### DEFINITIONS + +-include ../../Makefile.settings +ifdef SRCDIR +SRCDIR := $(SRCDIR)protocols/oscar/ +CFLAGS += -I$(SRCDIR) +endif + +# [SH] Program variables +objects = admin.o auth.o bos.o buddylist.o chat.o chatnav.o conn.o icq.o im.o info.o misc.o msgcookie.o rxhandlers.o rxqueue.o search.o service.o snac.o ssi.o stats.o tlv.o txqueue.o oscar_util.o oscar.o + +LFLAGS += -r + +# [SH] Phony targets +all: oscar_mod.o +check: all +lcov: check +gcov: +	gcov *.c + +.PHONY: all clean distclean + +clean: +	rm -f *.o core + +distclean: clean +	rm -rf .depend + +### MAIN PROGRAM + +$(objects): ../../Makefile.settings Makefile + +$(objects): %.o: $(SRCDIR)%.c +	@echo '*' Compiling $< +	@$(CC) -c $(CFLAGS) $< -o $@ + +oscar_mod.o: $(objects) +	@echo '*' Linking oscar_mod.o +	@$(LD) $(LFLAGS) $(objects) -o oscar_mod.o + +-include .depend/*.d diff --git a/protocols/oscar/admin.c b/protocols/oscar/admin.c new file mode 100644 index 00000000..09082c5b --- /dev/null +++ b/protocols/oscar/admin.c @@ -0,0 +1,196 @@ +#include <aim.h> +#include "admin.h" + +/* called for both reply and change-reply */ +static int infochange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	/* +	 * struct { +	 *    guint16 perms; +	 *    guint16 tlvcount; +	 *    aim_tlv_t tlvs[tlvcount]; +	 *  } admin_info[n]; +	 */ +	while (aim_bstream_empty(bs)) { +		guint16 perms, tlvcount; + +		perms = aimbs_get16(bs); +		tlvcount = aimbs_get16(bs); + +		while (tlvcount && aim_bstream_empty(bs)) { +			aim_rxcallback_t userfunc; +			guint16 type, len; +			guint8 *val; +			int str = 0; + +			type = aimbs_get16(bs); +			len = aimbs_get16(bs); + +			if ((type == 0x0011) || (type == 0x0004)) +				str = 1; + +			if (str) +				val = (guint8 *)aimbs_getstr(bs, len); +			else +				val = aimbs_getraw(bs, len); + +			/* XXX fix so its only called once for the entire packet */ +			if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +				userfunc(sess, rx, (snac->subtype == 0x0005) ? 1 : 0, perms, type, len, val, str); + +			g_free(val); + +			tlvcount--; +		} +	} + +	return 1; +} + +static int accountconfirm(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	guint16 status; + +	status = aimbs_get16(bs); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx, status); + +	return 0; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if ((snac->subtype == 0x0003) || (snac->subtype == 0x0005)) +		return infochange(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0007) +		return accountconfirm(sess, mod, rx, snac, bs); + +	return 0; +} + +int admin_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = AIM_CB_FAM_ADM; +	mod->version = 0x0001; +	mod->toolid = AIM_TOOL_NEWWIN; +	mod->toolversion = 0x0629;  +	mod->flags = 0; +	strncpy(mod->name, "admin", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} + +int aim_admin_changepasswd(aim_session_t *sess, aim_conn_t *conn, const char *newpw, const char *curpw) +{ +	aim_frame_t *tx; +	aim_tlvlist_t *tl = NULL; +	aim_snacid_t snacid; + +	if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+strlen(curpw)+4+strlen(newpw)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0); +	aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid); + +	/* new password TLV t(0002) */ +	aim_addtlvtochain_raw(&tl, 0x0002, strlen(newpw), (guint8 *)newpw); + +	/* current password TLV t(0012) */ +	aim_addtlvtochain_raw(&tl, 0x0012, strlen(curpw), (guint8 *)curpw); + +	aim_writetlvchain(&tx->data, &tl); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, tx); + +	return 0; +} + +/* + * Request account confirmation.  + * + * This will cause an email to be sent to the address associated with + * the account.  By following the instructions in the mail, you can + * get the TRIAL flag removed from your account. + * + */ +int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, 0x0007, 0x0006); +} + +/* + * Request a bit of account info. + * + * The only known valid tag is 0x0011 (email address). + * + */  +int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, guint16 info) +{ +	aim_frame_t *tx; +	aim_snacid_t snacid; + +	if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 14))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0002, 0x0002, 0x0000, NULL, 0); +	aim_putsnac(&tx->data, 0x0007, 0x0002, 0x0000, snacid); + +	aimbs_put16(&tx->data, info); +	aimbs_put16(&tx->data, 0x0000); + +	aim_tx_enqueue(sess, tx); + +	return 0; +} + +int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail) +{ +	aim_frame_t *tx; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL; + +	if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newemail)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0); +	aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid); + +	aim_addtlvtochain_raw(&tl, 0x0011, strlen(newemail), (guint8 *)newemail); +	 +	aim_writetlvchain(&tx->data, &tl); +	aim_freetlvchain(&tl); +	 +	aim_tx_enqueue(sess, tx); + +	return 0; +} + +int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick) +{ +	aim_frame_t *tx; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL; + +	if (!(tx = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(newnick)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0007, 0x0004, 0x0000, NULL, 0); +	aim_putsnac(&tx->data, 0x0007, 0x0004, 0x0000, snacid); + +	aim_addtlvtochain_raw(&tl, 0x0001, strlen(newnick), (guint8 *)newnick); +	 +	aim_writetlvchain(&tx->data, &tl); +	aim_freetlvchain(&tl); +	 +	aim_tx_enqueue(sess, tx); + + +	return 0; +} diff --git a/protocols/oscar/admin.h b/protocols/oscar/admin.h new file mode 100644 index 00000000..f9d74cae --- /dev/null +++ b/protocols/oscar/admin.h @@ -0,0 +1,13 @@ +#ifndef __OSCAR_ADMIN_H__ +#define __OSCAR_ADMIN_H__ + +#define AIM_CB_FAM_ADM 0x0007 + +/* + * SNAC Family: Administrative Services. + */  +#define AIM_CB_ADM_ERROR 0x0001 +#define AIM_CB_ADM_INFOCHANGE_REPLY 0x0005 +#define AIM_CB_ADM_DEFAULT 0xffff + +#endif /* __OSCAR_ADMIN_H__ */ diff --git a/protocols/oscar/aim.h b/protocols/oscar/aim.h new file mode 100644 index 00000000..479f8fd0 --- /dev/null +++ b/protocols/oscar/aim.h @@ -0,0 +1,914 @@ +/*  + * Main libfaim header.  Must be included in client for prototypes/macros. + * + * "come on, i turned a chick lesbian; i think this is the hackish equivalent" + *                                                -- Josh Meyer + * + */ + +#ifndef __AIM_H__ +#define __AIM_H__ + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <sys/types.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <time.h> +#include <gmodule.h> + +#include "bitlbee.h" + +/* XXX adjust these based on autoconf-detected platform */ +typedef guint32 aim_snacid_t; +typedef guint16 flap_seqnum_t; + +/* Portability stuff (DMP) */ + +#if defined(mach) && defined(__APPLE__) +#define gethostbyname(x) gethostbyname2(x, AF_INET)  +#endif + +/*  + * Current Maximum Length for Screen Names (not including NULL)  + * + * Currently only names up to 16 characters can be registered + * however it is aparently legal for them to be larger. + */ +#define MAXSNLEN 32 + +/* + * Current Maximum Length for Instant Messages + * + * This was found basically by experiment, but not wholly + * accurate experiment.  It should not be regarded + * as completely correct.  But its a decent approximation. + * + * Note that although we can send this much, its impossible + * for WinAIM clients (up through the latest (4.0.1957)) to + * send any more than 1kb.  Amaze all your windows friends + * with utterly oversized instant messages! + * + * XXX: the real limit is the total SNAC size at 8192. Fix this. + *  + */ +#define MAXMSGLEN 7987 + +/* + * Maximum size of a Buddy Icon. + */ +#define MAXICONLEN 7168 +#define AIM_ICONIDENT "AVT1picture.id" + +/* + * Current Maximum Length for Chat Room Messages + * + * This is actually defined by the protocol to be + * dynamic, but I have yet to see due cause to  + * define it dynamically here.  Maybe later. + * + */ +#define MAXCHATMSGLEN 512 + +/* + * Standard size of an AIM authorization cookie + */ +#define AIM_COOKIELEN            0x100 + +#define AIM_MD5_STRING "AOL Instant Messenger (SM)" + +/* + * Default Authorizer server name and TCP port for the OSCAR farm.   + * + * You shouldn't need to change this unless you're writing + * your own server.  + * + * Note that only one server is needed to start the whole + * AIM process.  The later server addresses come from + * the authorizer service. + * + * This is only here for convenience.  Its still up to + * the client to connect to it. + * + */ +#define AIM_DEFAULT_LOGIN_SERVER_AIM "login.messaging.aol.com" +#define AIM_DEFAULT_LOGIN_SERVER_ICQ "login.icq.com" +#define AIM_LOGIN_PORT 5190 + +/* + * Size of the SNAC caching hash. + * + * Default: 16 + * + */ +#define AIM_SNAC_HASH_SIZE 16 + +/* + * Client info.  Filled in by the client and passed in to  + * aim_send_login().  The information ends up getting passed to OSCAR + * through the initial login command. + * + */ +struct client_info_s { +	const char *clientstring; +	guint16 clientid; +	int major; +	int minor; +	int point; +	int build; +	const char *country; /* two-letter abbrev */ +	const char *lang; /* two-letter abbrev */ +}; + +#define AIM_CLIENTINFO_KNOWNGOOD_3_5_1670 { \ +	"AOL Instant Messenger (SM), version 3.5.1670/WIN32", \ +	0x0004, \ +	0x0003, \ +	0x0005, \ +	0x0000, \ +	0x0686, \ +	"us", \ +	"en", \ +} + +#define AIM_CLIENTINFO_KNOWNGOOD_4_1_2010 { \ +	  "AOL Instant Messenger (SM), version 4.1.2010/WIN32", \ +	  0x0004, \ +	  0x0004, \ +	  0x0001, \ +	  0x0000, \ +	  0x07da, \ +	  "us", \ +	  "en", \ +} + +#define AIM_CLIENTINFO_KNOWNGOOD_5_1_3036 { \ +	"AOL Instant Messenger, version 5.1.3036/WIN32", \ +	0x0109, \ +	0x0005, \ +	0x0001, \ +	0x0000, \ +	0x0bdc, \ +	"us", \ +	"en", \ +} + +/* + * I would make 4.1.2010 the default, but they seem to have found + * an alternate way of breaking that one.  + * + * 3.5.1670 should work fine, however, you will be subjected to the + * memory test, which may require you to have a WinAIM binary laying  + * around. (see login.c::memrequest()) + */ +#define AIM_CLIENTINFO_KNOWNGOOD AIM_CLIENTINFO_KNOWNGOOD_5_1_3036 + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +/*  + * These could be arbitrary, but its easier to use the actual AIM values  + */ +#define AIM_CONN_TYPE_AUTH          0x0007 +#define AIM_CONN_TYPE_ADS           0x0005 +#define AIM_CONN_TYPE_BOS           0x0002 +#define AIM_CONN_TYPE_CHAT          0x000e +#define AIM_CONN_TYPE_CHATNAV       0x000d + +/* + * Status values returned from aim_conn_new().  ORed together. + */ +#define AIM_CONN_STATUS_READY       0x0001 +#define AIM_CONN_STATUS_INTERNALERR 0x0002 +#define AIM_CONN_STATUS_RESOLVERR   0x0040 +#define AIM_CONN_STATUS_CONNERR     0x0080 +#define AIM_CONN_STATUS_INPROGRESS  0x0100 + +#define AIM_FRAMETYPE_FLAP 0x0000 + +/* + * message type flags + */ +#define AIM_MTYPE_PLAIN     0x01 +#define AIM_MTYPE_CHAT      0x02 +#define AIM_MTYPE_FILEREQ   0x03 +#define AIM_MTYPE_URL       0x04 +#define AIM_MTYPE_AUTHREQ   0x06 +#define AIM_MTYPE_AUTHDENY  0x07 +#define AIM_MTYPE_AUTHOK    0x08 +#define AIM_MTYPE_SERVER    0x09 +#define AIM_MTYPE_ADDED     0x0C +#define AIM_MTYPE_WWP       0x0D +#define AIM_MTYPE_EEXPRESS  0x0E +#define AIM_MTYPE_CONTACTS  0x13 +#define AIM_MTYPE_PLUGIN    0x1A +#define AIM_MTYPE_AUTOAWAY	0xE8 +#define AIM_MTYPE_AUTOBUSY	0xE9 +#define AIM_MTYPE_AUTONA	0xEA +#define AIM_MTYPE_AUTODND	0xEB +#define AIM_MTYPE_AUTOFFC	0xEC + +typedef struct aim_conn_s { +	int fd; +	guint16 type; +	guint16 subtype; +	flap_seqnum_t seqnum; +	guint32 status; +	void *priv; /* misc data the client may want to store */ +	void *internal; /* internal conn-specific libfaim data */ +	time_t lastactivity; /* time of last transmit */ +	int forcedlatency;  +	void *handlerlist; +	void *sessv; /* pointer to parent session */ +	void *inside; /* only accessible from inside libfaim */ +	struct aim_conn_s *next; +} aim_conn_t; + +/* + * Byte Stream type. Sort of. + * + * Use of this type serves a couple purposes: + *   - Buffer/buflen pairs are passed all around everywhere. This turns + *     that into one value, as well as abstracting it slightly. + *   - Through the abstraction, it is possible to enable bounds checking + *     for robustness at the cost of performance.  But a clean failure on + *     weird packets is much better than a segfault. + *   - I like having variables named "bs". + * + * Don't touch the insides of this struct.  Or I'll have to kill you. + * + */ +typedef struct aim_bstream_s { +	guint8 *data; +	guint32 len; +	guint32 offset; +} aim_bstream_t; + +typedef struct aim_frame_s { +	guint8 hdrtype; /* defines which piece of the union to use */ +	union { +		struct {  +			guint8 type; +			flap_seqnum_t seqnum;      +		} flap; +	} hdr; +	aim_bstream_t data;	/* payload stream */ +	guint8 handled;		/* 0 = new, !0 = been handled */ +	guint8 nofree;		/* 0 = free data on purge, 1 = only unlink */ +	aim_conn_t *conn;  /* the connection it came in on... */ +	struct aim_frame_s *next; +} aim_frame_t; + +typedef struct aim_msgcookie_s { +	unsigned char cookie[8]; +	int type; +	void *data; +	time_t addtime; +	struct aim_msgcookie_s *next; +} aim_msgcookie_t; + +/* + * AIM Session: The main client-data interface.   + * + */ +typedef struct aim_session_s { + +	/* ---- Client Accessible ------------------------ */ + +	/* Our screen name. */ +	char sn[MAXSNLEN+1]; + +	/* +	 * Pointer to anything the client wants to  +	 * explicitly associate with this session. +	 * +	 * This is for use in the callbacks mainly. In any +	 * callback, you can access this with sess->aux_data. +	 * +	 */ +	void *aux_data; + +	/* ---- Internal Use Only ------------------------ */ + +	/* Server-stored information (ssi) */ +	struct { +		int received_data; +		guint16 revision; +		struct aim_ssi_item *items; +		time_t timestamp; +		int waiting_for_ack; +		aim_frame_t *holding_queue; +	} ssi; + +	/* Connection information */ +	aim_conn_t *connlist; + +	/* +	 * Transmit/receive queues. +	 * +	 * These are only used when you don't use your own lowlevel +	 * I/O.  I don't suggest that you use libfaim's internal I/O. +	 * Its really bad and the API/event model is quirky at best. +	 *   +	 */ +	aim_frame_t *queue_outgoing;    +	aim_frame_t *queue_incoming;  + +	/* +	 * Tx Enqueuing function. +	 * +	 * This is how you override the transmit direction of libfaim's +	 * internal I/O.  This function will be called whenever it needs +	 * to send something. +	 * +	 */ +	int (*tx_enqueue)(struct aim_session_s *, aim_frame_t *); + +	/* +	 * Outstanding snac handling  +	 * +	 * XXX: Should these be per-connection? -mid +	 */ +	void *snac_hash[AIM_SNAC_HASH_SIZE]; +	aim_snacid_t snacid_next; + +	struct aim_icq_info *icq_info; +	struct aim_oft_info *oft_info; +	struct aim_authresp_info *authinfo; +	struct aim_emailinfo *emailinfo; + +	struct { +		struct aim_userinfo_s *userinfo; +		struct userinfo_node *torequest; +		struct userinfo_node *requested; +		int waiting_for_response; +	} locate; + +	guint32 flags; /* AIM_SESS_FLAGS_ */ + +	aim_msgcookie_t *msgcookies; + +	void *modlistv; + +	guint8 aim_icq_state;  /* ICQ representation of away state */ +} aim_session_t; + +/* Values for sess->flags */ +#define AIM_SESS_FLAGS_SNACLOGIN         0x00000001 +#define AIM_SESS_FLAGS_XORLOGIN          0x00000002 +#define AIM_SESS_FLAGS_NONBLOCKCONNECT   0x00000004 +#define AIM_SESS_FLAGS_DONTTIMEOUTONICBM 0x00000008 + +/* Valid for calling aim_icq_setstatus() and for aim_userinfo_t->icqinfo.status */ +#define AIM_ICQ_STATE_NORMAL    0x00000000 +#define AIM_ICQ_STATE_AWAY      0x00000001 +#define AIM_ICQ_STATE_DND       0x00000002 +#define AIM_ICQ_STATE_OUT       0x00000004 +#define AIM_ICQ_STATE_BUSY      0x00000010 +#define AIM_ICQ_STATE_CHAT      0x00000020 +#define AIM_ICQ_STATE_INVISIBLE 0x00000100 +#define AIM_ICQ_STATE_WEBAWARE  0x00010000 +#define AIM_ICQ_STATE_HIDEIP            0x00020000 +#define AIM_ICQ_STATE_BIRTHDAY          0x00080000 +#define AIM_ICQ_STATE_DIRECTDISABLED    0x00100000 +#define AIM_ICQ_STATE_ICQHOMEPAGE       0x00200000 +#define AIM_ICQ_STATE_DIRECTREQUIREAUTH 0x10000000 +#define AIM_ICQ_STATE_DIRECTCONTACTLIST 0x20000000 + +/* + * AIM User Info, Standard Form. + */ +typedef struct { +	char sn[MAXSNLEN+1]; +	guint16 warnlevel; +	guint16 idletime; +	guint16 flags; +	guint32 membersince; +	guint32 onlinesince; +	guint32 sessionlen;  +	guint32 capabilities; +	struct { +		guint32 status; +		guint32 ipaddr; +		guint8 crap[0x25]; /* until we figure it out... */ +	} icqinfo; +	guint32 present; +} aim_userinfo_t; + +#define AIM_USERINFO_PRESENT_FLAGS        0x00000001 +#define AIM_USERINFO_PRESENT_MEMBERSINCE  0x00000002 +#define AIM_USERINFO_PRESENT_ONLINESINCE  0x00000004 +#define AIM_USERINFO_PRESENT_IDLE         0x00000008 +#define AIM_USERINFO_PRESENT_ICQEXTSTATUS 0x00000010 +#define AIM_USERINFO_PRESENT_ICQIPADDR    0x00000020 +#define AIM_USERINFO_PRESENT_ICQDATA      0x00000040 +#define AIM_USERINFO_PRESENT_CAPABILITIES 0x00000080 +#define AIM_USERINFO_PRESENT_SESSIONLEN   0x00000100 + +const char *aim_userinfo_sn(aim_userinfo_t *ui); +guint16 aim_userinfo_flags(aim_userinfo_t *ui); +guint16 aim_userinfo_idle(aim_userinfo_t *ui); +float aim_userinfo_warnlevel(aim_userinfo_t *ui); +time_t aim_userinfo_membersince(aim_userinfo_t *ui); +time_t aim_userinfo_onlinesince(aim_userinfo_t *ui); +guint32 aim_userinfo_sessionlen(aim_userinfo_t *ui); +int aim_userinfo_hascap(aim_userinfo_t *ui, guint32 cap); + +#define AIM_FLAG_UNCONFIRMED 	0x0001 /* "damned transients" */ +#define AIM_FLAG_ADMINISTRATOR	0x0002 +#define AIM_FLAG_AOL		0x0004 +#define AIM_FLAG_OSCAR_PAY	0x0008 +#define AIM_FLAG_FREE 		0x0010 +#define AIM_FLAG_AWAY		0x0020 +#define AIM_FLAG_ICQ		0x0040 +#define AIM_FLAG_WIRELESS	0x0080 +#define AIM_FLAG_UNKNOWN100	0x0100 +#define AIM_FLAG_UNKNOWN200	0x0200 +#define AIM_FLAG_ACTIVEBUDDY    0x0400 +#define AIM_FLAG_UNKNOWN800	0x0800 +#define AIM_FLAG_ABINTERNAL     0x1000 + +#define AIM_FLAG_ALLUSERS	0x001f + +/* + * TLV handling + */ + +/* Generic TLV structure. */ +typedef struct aim_tlv_s { +	guint16 type; +	guint16 length; +	guint8 *value; +} aim_tlv_t; + +/* List of above. */ +typedef struct aim_tlvlist_s { +	aim_tlv_t *tlv; +	struct aim_tlvlist_s *next; +} aim_tlvlist_t; + +/* TLV-handling functions */ + +#if 0 +/* Very, very raw TLV handling. */ +int aim_puttlv_8(guint8 *buf, const guint16 t, const guint8 v); +int aim_puttlv_16(guint8 *buf, const guint16 t, const guint16 v); +int aim_puttlv_32(guint8 *buf, const guint16 t, const guint32 v); +int aim_puttlv_raw(guint8 *buf, const guint16 t, const guint16 l, const guint8 *v); +#endif + +/* TLV list handling. */ +aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs); +void aim_freetlvchain(aim_tlvlist_t **list); +aim_tlv_t *aim_gettlv(aim_tlvlist_t *list, guint16 t, const int n); +char *aim_gettlv_str(aim_tlvlist_t *list, const guint16 t, const int n); +guint8 aim_gettlv8(aim_tlvlist_t *list, const guint16 type, const int num); +guint16 aim_gettlv16(aim_tlvlist_t *list, const guint16 t, const int n); +guint32 aim_gettlv32(aim_tlvlist_t *list, const guint16 t, const int n); +int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list); +int aim_addtlvtochain8(aim_tlvlist_t **list, const guint16 t, const guint8 v); +int aim_addtlvtochain16(aim_tlvlist_t **list, const guint16 t, const guint16 v); +int aim_addtlvtochain32(aim_tlvlist_t **list, const guint16 type, const guint32 v); +int aim_addtlvtochain_availmsg(aim_tlvlist_t **list, const guint16 type, const char *msg); +int aim_addtlvtochain_raw(aim_tlvlist_t **list, const guint16 t, const guint16 l, const guint8 *v); +int aim_addtlvtochain_caps(aim_tlvlist_t **list, const guint16 t, const guint32 caps); +int aim_addtlvtochain_noval(aim_tlvlist_t **list, const guint16 type); +int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance); +int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, guint16 type, aim_userinfo_t *ui); +int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, guint16 type, aim_tlvlist_t **tl); +int aim_counttlvchain(aim_tlvlist_t **list); +int aim_sizetlvchain(aim_tlvlist_t **list); + + +/* + * Get command from connections + * + * aim_get_commmand() is the libfaim lowlevel I/O in the receive direction. + * XXX Make this easily overridable. + * + */ +int aim_get_command(aim_session_t *, aim_conn_t *); + +/* + * Dispatch commands that are in the rx queue. + */ +void aim_rxdispatch(aim_session_t *); + +int aim_debugconn_sendconnect(aim_session_t *sess, aim_conn_t *conn); + +typedef int (*aim_rxcallback_t)(aim_session_t *, aim_frame_t *, ...); + +struct aim_clientrelease { +	char *name; +	guint32 build; +	char *url; +	char *info; +}; + +struct aim_authresp_info { +	char *sn; +	guint16 errorcode; +	char *errorurl; +	guint16 regstatus; +	char *email; +	char *bosip; +	guint8 *cookie; +	struct aim_clientrelease latestrelease; +	struct aim_clientrelease latestbeta; +}; + +/* Callback data for redirect. */ +struct aim_redirect_data { +	guint16 group; +	const char *ip; +	const guint8 *cookie; +	struct { /* group == AIM_CONN_TYPE_CHAT */ +		guint16 exchange; +		const char *room; +		guint16 instance; +	} chat; +}; + +int aim_clientready(aim_session_t *sess, aim_conn_t *conn); +int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn); +int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn); +int aim_send_login(aim_session_t *, aim_conn_t *, const char *, const char *, struct client_info_s *, const char *key); +int aim_encode_password_md5(const char *password, const char *key, unsigned char *digest); +void aim_purge_rxqueue(aim_session_t *); + +#define AIM_TX_QUEUED    0 /* default */ +#define AIM_TX_IMMEDIATE 1 +#define AIM_TX_USER      2 +int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *)); + +int aim_tx_flushqueue(aim_session_t *); +void aim_tx_purgequeue(aim_session_t *); + +int aim_conn_setlatency(aim_conn_t *conn, int newval); + +void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn); + +int aim_conn_addhandler(aim_session_t *, aim_conn_t *conn, u_short family, u_short type, aim_rxcallback_t newhandler, u_short flags); +int aim_clearhandlers(aim_conn_t *conn); + +aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, guint16 group); +aim_session_t *aim_conn_getsess(aim_conn_t *conn); +void aim_conn_close(aim_conn_t *deadconn); +aim_conn_t *aim_newconn(aim_session_t *, int type, const char *dest); +int aim_conngetmaxfd(aim_session_t *); +aim_conn_t *aim_select(aim_session_t *, struct timeval *, int *); +int aim_conn_isready(aim_conn_t *); +int aim_conn_setstatus(aim_conn_t *, int); +int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn); +int aim_conn_isconnecting(aim_conn_t *conn); + +typedef void (*faim_debugging_callback_t)(aim_session_t *sess, int level, const char *format, va_list va); +int aim_setdebuggingcb(aim_session_t *sess, faim_debugging_callback_t); +void aim_session_init(aim_session_t *, guint32 flags, int debuglevel); +void aim_session_kill(aim_session_t *); +void aim_setupproxy(aim_session_t *sess, const char *server, const char *username, const char *password); +aim_conn_t *aim_getconn_type(aim_session_t *, int type); +aim_conn_t *aim_getconn_type_all(aim_session_t *, int type); +aim_conn_t *aim_getconn_fd(aim_session_t *, int fd); + +/* aim_misc.c */ + + +struct aim_chat_roominfo { +	unsigned short exchange; +	char *name; +	unsigned short instance; +}; + +struct aim_chat_invitation { +	struct im_connection * ic; +	char * name; +	guint8 exchange; +}; + +#define AIM_VISIBILITYCHANGE_PERMITADD    0x05 +#define AIM_VISIBILITYCHANGE_PERMITREMOVE 0x06 +#define AIM_VISIBILITYCHANGE_DENYADD      0x07 +#define AIM_VISIBILITYCHANGE_DENYREMOVE   0x08 + +#define AIM_PRIVFLAGS_ALLOWIDLE           0x01 +#define AIM_PRIVFLAGS_ALLOWMEMBERSINCE    0x02 + +#define AIM_WARN_ANON                     0x01 + +int aim_sendpauseack(aim_session_t *sess, aim_conn_t *conn); +int aim_send_warning(aim_session_t *sess, aim_conn_t *conn, const char *destsn, guint32 flags); +int aim_nop(aim_session_t *, aim_conn_t *); +int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn); +int aim_bos_setidle(aim_session_t *, aim_conn_t *, guint32); +int aim_bos_changevisibility(aim_session_t *, aim_conn_t *, int, const char *); +int aim_bos_setbuddylist(aim_session_t *, aim_conn_t *, const char *); +int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile, const char *awaymsg, guint32 caps); +int aim_bos_setgroupperm(aim_session_t *, aim_conn_t *, guint32 mask); +int aim_bos_setprivacyflags(aim_session_t *, aim_conn_t *, guint32); +int aim_reqpersonalinfo(aim_session_t *, aim_conn_t *); +int aim_reqservice(aim_session_t *, aim_conn_t *, guint16); +int aim_bos_reqrights(aim_session_t *, aim_conn_t *); +int aim_bos_reqbuddyrights(aim_session_t *, aim_conn_t *); +int aim_bos_reqlocaterights(aim_session_t *, aim_conn_t *); +int aim_setdirectoryinfo(aim_session_t *sess, aim_conn_t *conn, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, guint16 privacy); +int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy); +int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status); + +struct aim_fileheader_t *aim_getlisting(aim_session_t *sess, FILE *); + +#define AIM_CLIENTTYPE_UNKNOWN  0x0000 +#define AIM_CLIENTTYPE_MC       0x0001 +#define AIM_CLIENTTYPE_WINAIM   0x0002 +#define AIM_CLIENTTYPE_WINAIM41 0x0003 +#define AIM_CLIENTTYPE_AOL_TOC  0x0004 +unsigned short aim_fingerprintclient(unsigned char *msghdr, int len); + +#define AIM_RATE_CODE_CHANGE     0x0001 +#define AIM_RATE_CODE_WARNING    0x0002 +#define AIM_RATE_CODE_LIMIT      0x0003 +#define AIM_RATE_CODE_CLEARLIMIT 0x0004 +int aim_ads_requestads(aim_session_t *sess, aim_conn_t *conn); + +/* aim_im.c */ + +aim_conn_t *aim_sendfile_initiate(aim_session_t *, const char *destsn, const char *filename, guint16 numfiles, guint32 totsize); + +aim_conn_t *aim_getfile_initiate(aim_session_t *sess, aim_conn_t *conn, const char *destsn); +int aim_oft_getfile_request(aim_session_t *sess, aim_conn_t *conn, const char *name, int size); +int aim_oft_getfile_ack(aim_session_t *sess, aim_conn_t *conn); +int aim_oft_getfile_end(aim_session_t *sess, aim_conn_t *conn); + +#define AIM_SENDMEMBLOCK_FLAG_ISREQUEST  0 +#define AIM_SENDMEMBLOCK_FLAG_ISHASH     1 + +int aim_sendmemblock(aim_session_t *sess, aim_conn_t *conn, guint32 offset, guint32 len, const guint8 *buf, guint8 flag); + +#define AIM_GETINFO_GENERALINFO 0x00001 +#define AIM_GETINFO_AWAYMESSAGE 0x00003 +#define AIM_GETINFO_CAPABILITIES 0x0004 + +struct aim_invite_priv { +	char *sn; +	char *roomname; +	guint16 exchange; +	guint16 instance; +}; + +#define AIM_COOKIETYPE_UNKNOWN  0x00 +#define AIM_COOKIETYPE_ICBM     0x01 +#define AIM_COOKIETYPE_ADS      0x02 +#define AIM_COOKIETYPE_BOS      0x03 +#define AIM_COOKIETYPE_IM       0x04 +#define AIM_COOKIETYPE_CHAT     0x05 +#define AIM_COOKIETYPE_CHATNAV  0x06 +#define AIM_COOKIETYPE_INVITE   0x07 + +int aim_handlerendconnect(aim_session_t *sess, aim_conn_t *cur); + +#define AIM_TRANSFER_DENY_NOTSUPPORTED 0x0000 +#define AIM_TRANSFER_DENY_DECLINE 0x0001 +#define AIM_TRANSFER_DENY_NOTACCEPTING 0x0002 +int aim_denytransfer(aim_session_t *sess, const char *sender, const guint8 *cookie, unsigned short code); +aim_conn_t *aim_accepttransfer(aim_session_t *sess, aim_conn_t *conn, const char *sn, const guint8 *cookie, const guint8 *ip, guint16 listingfiles, guint16 listingtotsize, guint16 listingsize, guint32 listingchecksum, guint16 rendid); + +int aim_getinfo(aim_session_t *, aim_conn_t *, const char *, unsigned short); +int aim_sendbuddyoncoming(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *info); +int aim_sendbuddyoffgoing(aim_session_t *sess, aim_conn_t *conn, const char *sn); + +#define AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED	0x00000001 +#define AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED	0x00000002 + +/* This is what the server will give you if you don't set them yourself. */ +#define AIM_IMPARAM_DEFAULTS { \ +	0, \ +	AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \ +	512, /* !! Note how small this is. */ \ +	(99.9)*10, (99.9)*10, \ +	1000 /* !! And how large this is. */ \ +} + +/* This is what most AIM versions use. */ +#define AIM_IMPARAM_REASONABLE { \ +	0, \ +	AIM_IMPARAM_FLAG_CHANMSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSEDCALLS_ENABLED, \ +	8000, \ +	(99.9)*10, (99.9)*10, \ +	0 \ +} + + +struct aim_icbmparameters { +	guint16 maxchan; +	guint32 flags; /* AIM_IMPARAM_FLAG_ */ +	guint16 maxmsglen; /* message size that you will accept */ +	guint16 maxsenderwarn; /* this and below are *10 (999=99.9%) */ +	guint16 maxrecverwarn; +	guint32 minmsginterval; /* in milliseconds? */ +}; + +int aim_reqicbmparams(aim_session_t *sess); +int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params); + +/* auth.c */ +int aim_sendcookie(aim_session_t *, aim_conn_t *, const guint8 *); + +int aim_admin_changepasswd(aim_session_t *, aim_conn_t *, const char *newpw, const char *curpw); +int aim_admin_reqconfirm(aim_session_t *sess, aim_conn_t *conn); +int aim_admin_getinfo(aim_session_t *sess, aim_conn_t *conn, guint16 info); +int aim_admin_setemail(aim_session_t *sess, aim_conn_t *conn, const char *newemail); +int aim_admin_setnick(aim_session_t *sess, aim_conn_t *conn, const char *newnick); + +/* These apply to exchanges as well. */ +#define AIM_CHATROOM_FLAG_EVILABLE 0x0001 +#define AIM_CHATROOM_FLAG_NAV_ONLY 0x0002 +#define AIM_CHATROOM_FLAG_INSTANCING_ALLOWED 0x0004 +#define AIM_CHATROOM_FLAG_OCCUPANT_PEEK_ALLOWED 0x0008 + +struct aim_chat_exchangeinfo { +	guint16 number; +	guint16 flags; +	char *name; +	char *charset1; +	char *lang1; +	char *charset2; +	char *lang2; +}; + +#define AIM_CHATFLAGS_NOREFLECT 	0x0001 +#define AIM_CHATFLAGS_AWAY      	0x0002 +#define AIM_CHATFLAGS_UNICODE		0x0004 +#define AIM_CHATFLAGS_ISO_8859_1	0x0008 + +int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const char *msg, int msglen); +int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance); +int aim_chat_attachname(aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance); +char *aim_chat_getname(aim_conn_t *conn); +aim_conn_t *aim_chat_getconn(aim_session_t *, const char *name); + +int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn); + +int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance); + +int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, guint16 exchange); +int aim_chat_leaveroom(aim_session_t *sess, const char *name); + +/* aim_util.c */ +/* + * These are really ugly.  You'd think this was LISP.  I wish it was. + * + * XXX With the advent of bstream's, these should be removed to enforce + * their use. + * + */ +#define aimutil_put8(buf, data) ((*(buf) = (u_char)(data)&0xff),1) +#define aimutil_get8(buf) ((*(buf))&0xff) +#define aimutil_put16(buf, data) ( \ +		(*(buf) = (u_char)((data)>>8)&0xff), \ +		(*((buf)+1) = (u_char)(data)&0xff),  \ +		2) +#define aimutil_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff)) +#define aimutil_put32(buf, data) ( \ +		(*((buf)) = (u_char)((data)>>24)&0xff), \ +		(*((buf)+1) = (u_char)((data)>>16)&0xff), \ +		(*((buf)+2) = (u_char)((data)>>8)&0xff), \ +		(*((buf)+3) = (u_char)(data)&0xff), \ +		4) +#define aimutil_get32(buf) ((((*(buf))<<24)&0xff000000) + \ +		(((*((buf)+1))<<16)&0x00ff0000) + \ +		(((*((buf)+2))<< 8)&0x0000ff00) + \ +		(((*((buf)+3)    )&0x000000ff))) + +/* Little-endian versions (damn ICQ) */ +#define aimutil_putle8(buf, data) ( \ +		(*(buf) = (unsigned char)(data) & 0xff), \ +		1) +#define aimutil_getle8(buf) ( \ +		(*(buf)) & 0xff \ +		) +#define aimutil_putle16(buf, data) ( \ +		(*((buf)+0) = (unsigned char)((data) >> 0) & 0xff),  \ +		(*((buf)+1) = (unsigned char)((data) >> 8) & 0xff), \ +		2) +#define aimutil_getle16(buf) ( \ +		(((*((buf)+0)) << 0) & 0x00ff) + \ +		(((*((buf)+1)) << 8) & 0xff00) \ +		) +#define aimutil_putle32(buf, data) ( \ +		(*((buf)+0) = (unsigned char)((data) >>  0) & 0xff), \ +		(*((buf)+1) = (unsigned char)((data) >>  8) & 0xff), \ +		(*((buf)+2) = (unsigned char)((data) >> 16) & 0xff), \ +		(*((buf)+3) = (unsigned char)((data) >> 24) & 0xff), \ +		4) +#define aimutil_getle32(buf) ( \ +		(((*((buf)+0)) <<  0) & 0x000000ff) + \ +		(((*((buf)+1)) <<  8) & 0x0000ff00) + \ +		(((*((buf)+2)) << 16) & 0x00ff0000) + \ +		(((*((buf)+3)) << 24) & 0xff000000)) + + +int aimutil_putstr(u_char *, const char *, int); +int aimutil_tokslen(char *toSearch, int index, char dl); +int aimutil_itemcnt(char *toSearch, char dl); +char *aimutil_itemidx(char *toSearch, int index, char dl); +int aim_sncmp(const char *a, const char *b); + +#include <aim_internal.h> + +/* + * SNAC Families. + */ +#define AIM_CB_FAM_ACK 0x0000 +#define AIM_CB_FAM_GEN 0x0001 +#define AIM_CB_FAM_SPECIAL 0xffff /* Internal libfaim use */ + +/* + * SNAC Family: Ack. + *  + * Not really a family, but treating it as one really + * helps it fit into the libfaim callback structure better. + * + */ +#define AIM_CB_ACK_ACK 0x0001 + +/* + * SNAC Family: General. + */  +#define AIM_CB_GEN_ERROR 0x0001 +#define AIM_CB_GEN_CLIENTREADY 0x0002 +#define AIM_CB_GEN_SERVERREADY 0x0003 +#define AIM_CB_GEN_SERVICEREQ 0x0004 +#define AIM_CB_GEN_REDIRECT 0x0005 +#define AIM_CB_GEN_RATEINFOREQ 0x0006 +#define AIM_CB_GEN_RATEINFO 0x0007 +#define AIM_CB_GEN_RATEINFOACK 0x0008 +#define AIM_CB_GEN_RATECHANGE 0x000a +#define AIM_CB_GEN_SERVERPAUSE 0x000b +#define AIM_CB_GEN_SERVERRESUME 0x000d +#define AIM_CB_GEN_REQSELFINFO 0x000e +#define AIM_CB_GEN_SELFINFO 0x000f +#define AIM_CB_GEN_EVIL 0x0010 +#define AIM_CB_GEN_SETIDLE 0x0011 +#define AIM_CB_GEN_MIGRATIONREQ 0x0012 +#define AIM_CB_GEN_MOTD 0x0013 +#define AIM_CB_GEN_SETPRIVFLAGS 0x0014 +#define AIM_CB_GEN_WELLKNOWNURL 0x0015 +#define AIM_CB_GEN_NOP 0x0016 +#define AIM_CB_GEN_DEFAULT 0xffff + +/* + * SNAC Family: Advertisement Services + */  +#define AIM_CB_ADS_ERROR 0x0001 +#define AIM_CB_ADS_DEFAULT 0xffff + +/* + * OFT Services + * + * See non-SNAC note below. + */ +#define AIM_CB_OFT_DIRECTIMCONNECTREQ 0x0001/* connect request -- actually an OSCAR CAP*/ +#define AIM_CB_OFT_DIRECTIMINCOMING 0x0002 +#define AIM_CB_OFT_DIRECTIMDISCONNECT 0x0003 +#define AIM_CB_OFT_DIRECTIMTYPING 0x0004 +#define AIM_CB_OFT_DIRECTIMINITIATE 0x0005 + +#define AIM_CB_OFT_GETFILECONNECTREQ 0x0006 /* connect request -- actually an OSCAR CAP*/ +#define AIM_CB_OFT_GETFILELISTINGREQ 0x0007 /* OFT listing.txt request */ +#define AIM_CB_OFT_GETFILEFILEREQ 0x0008    /* received file request */ +#define AIM_CB_OFT_GETFILEFILESEND 0x0009   /* received file request confirm -- send data */ +#define AIM_CB_OFT_GETFILECOMPLETE 0x000a   /* received file send complete*/ +#define AIM_CB_OFT_GETFILEINITIATE 0x000b   /* request for file get acknowledge */ +#define AIM_CB_OFT_GETFILEDISCONNECT 0x000c   /* OFT connection disconnected.*/ +#define AIM_CB_OFT_GETFILELISTING 0x000d   /* OFT listing.txt received.*/ +#define AIM_CB_OFT_GETFILERECEIVE 0x000e   /* OFT file incoming.*/ +#define AIM_CB_OFT_GETFILELISTINGRXCONFIRM 0x000f +#define AIM_CB_OFT_GETFILESTATE4 0x0010 + +#define AIM_CB_OFT_SENDFILEDISCONNECT 0x0020   /* OFT connection disconnected.*/ + + + +/* + * SNAC Family: Internal Messages + * + * This isn't truely a SNAC family either, but using + * these, we can integrated non-SNAC services into + * the SNAC-centered libfaim callback structure. + * + */  +#define AIM_CB_SPECIAL_AUTHSUCCESS 0x0001 +#define AIM_CB_SPECIAL_AUTHOTHER 0x0002 +#define AIM_CB_SPECIAL_CONNERR 0x0003 +#define AIM_CB_SPECIAL_CONNCOMPLETE 0x0004 +#define AIM_CB_SPECIAL_FLAPVER 0x0005 +#define AIM_CB_SPECIAL_CONNINITDONE 0x0006 +#define AIM_CB_SPECIAL_IMAGETRANSFER 0x007 +#define AIM_CB_SPECIAL_UNKNOWN 0xffff +#define AIM_CB_SPECIAL_DEFAULT AIM_CB_SPECIAL_UNKNOWN + +#endif /* __AIM_H__ */ diff --git a/protocols/oscar/aim_internal.h b/protocols/oscar/aim_internal.h new file mode 100644 index 00000000..80a9c758 --- /dev/null +++ b/protocols/oscar/aim_internal.h @@ -0,0 +1,214 @@ +/* + * aim_internal.h -- prototypes/structs for the guts of libfaim + * + */ + +#ifndef __AIM_INTERNAL_H__ +#define __AIM_INTERNAL_H__ 1 + +typedef struct { +	guint16 family; +	guint16 subtype; +	guint16 flags; +	guint32 id; +} aim_modsnac_t; + +#define AIM_MODULENAME_MAXLEN 16 +#define AIM_MODFLAG_MULTIFAMILY 0x0001 +typedef struct aim_module_s { +	guint16 family; +	guint16 version; +	guint16 toolid; +	guint16 toolversion; +	guint16 flags; +	char name[AIM_MODULENAME_MAXLEN+1]; +	int (*snachandler)(aim_session_t *sess, struct aim_module_s *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs); +	void (*shutdown)(aim_session_t *sess, struct aim_module_s *mod); +	void *priv; +	struct aim_module_s *next; +} aim_module_t; + +int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *)); +void aim__shutdownmodules(aim_session_t *sess); +aim_module_t *aim__findmodulebygroup(aim_session_t *sess, guint16 group); + +int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod); +int admin_modfirst(aim_session_t *sess, aim_module_t *mod); +int bos_modfirst(aim_session_t *sess, aim_module_t *mod); +int search_modfirst(aim_session_t *sess, aim_module_t *mod); +int stats_modfirst(aim_session_t *sess, aim_module_t *mod); +int auth_modfirst(aim_session_t *sess, aim_module_t *mod); +int msg_modfirst(aim_session_t *sess, aim_module_t *mod); +int misc_modfirst(aim_session_t *sess, aim_module_t *mod); +int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod); +int chat_modfirst(aim_session_t *sess, aim_module_t *mod); +int locate_modfirst(aim_session_t *sess, aim_module_t *mod); +int general_modfirst(aim_session_t *sess, aim_module_t *mod); +int ssi_modfirst(aim_session_t *sess, aim_module_t *mod); +int icq_modfirst(aim_session_t *sess, aim_module_t *mod); + +int aim_genericreq_n(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype); +int aim_genericreq_n_snacid(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype); +int aim_genericreq_l(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype, guint32 *); +int aim_genericreq_s(aim_session_t *, aim_conn_t *conn, guint16 family, guint16 subtype, guint16 *); + +#define AIMBS_CURPOSPAIR(x) ((x)->data + (x)->offset), ((x)->len - (x)->offset) + +void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn); +int aim_recv(int fd, void *buf, size_t count); +int aim_bstream_init(aim_bstream_t *bs, guint8 *data, int len); +int aim_bstream_empty(aim_bstream_t *bs); +int aim_bstream_curpos(aim_bstream_t *bs); +int aim_bstream_setpos(aim_bstream_t *bs, int off); +void aim_bstream_rewind(aim_bstream_t *bs); +int aim_bstream_advance(aim_bstream_t *bs, int n); +guint8 aimbs_get8(aim_bstream_t *bs); +guint16 aimbs_get16(aim_bstream_t *bs); +guint32 aimbs_get32(aim_bstream_t *bs); +guint8 aimbs_getle8(aim_bstream_t *bs); +guint16 aimbs_getle16(aim_bstream_t *bs); +guint32 aimbs_getle32(aim_bstream_t *bs); +int aimbs_put8(aim_bstream_t *bs, guint8 v); +int aimbs_put16(aim_bstream_t *bs, guint16 v); +int aimbs_put32(aim_bstream_t *bs, guint32 v); +int aimbs_putle8(aim_bstream_t *bs, guint8 v); +int aimbs_putle16(aim_bstream_t *bs, guint16 v); +int aimbs_putle32(aim_bstream_t *bs, guint32 v); +int aimbs_getrawbuf(aim_bstream_t *bs, guint8 *buf, int len); +guint8 *aimbs_getraw(aim_bstream_t *bs, int len); +char *aimbs_getstr(aim_bstream_t *bs, int len); +int aimbs_putraw(aim_bstream_t *bs, const guint8 *v, int len); +int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len); + +int aim_get_command_rendezvous(aim_session_t *sess, aim_conn_t *conn); + +int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *cur); +flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *); +aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, guint8 framing, guint8 chan, int datalen); +void aim_frame_destroy(aim_frame_t *); +int aim_tx_enqueue(aim_session_t *, aim_frame_t *); +int aim_tx_printqueue(aim_session_t *); +void aim_tx_cleanqueue(aim_session_t *, aim_conn_t *); + +aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, u_short family, u_short type); + +/* + * Generic SNAC structure.  Rarely if ever used. + */ +typedef struct aim_snac_s { +	aim_snacid_t id; +	guint16 family; +	guint16 type; +	guint16 flags; +	void *data; +	time_t issuetime; +	struct aim_snac_s *next; +} aim_snac_t; + +void aim_initsnachash(aim_session_t *sess); +aim_snacid_t aim_cachesnac(aim_session_t *sess, const guint16 family, const guint16 type, const guint16 flags, const void *data, const int datalen); +aim_snac_t *aim_remsnac(aim_session_t *, aim_snacid_t id); +void aim_cleansnacs(aim_session_t *, int maxage); +int aim_putsnac(aim_bstream_t *, guint16 family, guint16 type, guint16 flags, aim_snacid_t id); + +aim_conn_t *aim_cloneconn(aim_session_t *sess, aim_conn_t *src); +void aim_clonehandlers(aim_session_t *sess, aim_conn_t *dest, aim_conn_t *src); + +int aim_oft_buildheader(unsigned char *,struct aim_fileheader_t *); + +int aim_parse_unknown(aim_session_t *, aim_frame_t *, ...); + +/* Stored in ->priv of the service request SNAC for chats. */ +struct chatsnacinfo { +	guint16 exchange; +	char name[128]; +	guint16 instance; +}; + +/* these are used by aim_*_clientready */ +#define AIM_TOOL_JAVA   0x0001 +#define AIM_TOOL_MAC    0x0002 +#define AIM_TOOL_WIN16  0x0003 +#define AIM_TOOL_WIN32  0x0004 +#define AIM_TOOL_MAC68K 0x0005 +#define AIM_TOOL_MACPPC 0x0006 +#define AIM_TOOL_NEWWIN 0x0010 +struct aim_tool_version { +	guint16 group; +	guint16 version; +	guint16 tool; +	guint16 toolversion; +}; + +/*  + * In SNACland, the terms 'family' and 'group' are synonymous -- the former + * is my term, the latter is AOL's. + */ +struct snacgroup { +	guint16 group; +	struct snacgroup *next; +}; + +struct snacpair { +	guint16 group; +	guint16 subtype; +	struct snacpair *next; +}; + +struct rateclass { +	guint16 classid; +	guint32 windowsize; +	guint32 clear; +	guint32 alert; +	guint32 limit; +	guint32 disconnect; +	guint32 current; +	guint32 max; +	guint8 unknown[5]; /* only present in versions >= 3 */ +	struct snacpair *members; +	struct rateclass *next; +}; + +/* + * This is inside every connection.  But it is a void * to anything + * outside of libfaim.  It should remain that way.  It's called data + * abstraction.  Maybe you've heard of it.  (Probably not if you're a  + * libfaim user.) + *  + */ +typedef struct aim_conn_inside_s { +	struct snacgroup *groups; +	struct rateclass *rates; +} aim_conn_inside_t; + +void aim_conn_addgroup(aim_conn_t *conn, guint16 group); + +guint32 aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len); +int aim_putcap(aim_bstream_t *bs, guint32 caps); + +int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie); +aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, guint8 *cookie, int type); +aim_msgcookie_t *aim_mkcookie(guint8 *, int, void *); +aim_msgcookie_t *aim_checkcookie(aim_session_t *sess, const unsigned char *, const int); +int aim_freecookie(aim_session_t *sess, aim_msgcookie_t *cookie); +int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie); + +int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *); +int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info); + +int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo); + +int aim_request_directim(aim_session_t *sess, const char *destsn, guint8 *ip, guint16 port, guint8 *ckret); +int aim_request_sendfile(aim_session_t *sess, const char *sn, const char *filename, guint16 numfiles, guint32 totsize, guint8 *ip, guint16 port, guint8 *ckret); +void aim_conn_close_rend(aim_session_t *sess, aim_conn_t *conn); +void aim_conn_kill_rend(aim_session_t *sess, aim_conn_t *conn); + +void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn); + +/* These are all handled internally now. */ +int aim_setversions(aim_session_t *sess, aim_conn_t *conn); +int aim_reqrates(aim_session_t *, aim_conn_t *); +int aim_rates_addparam(aim_session_t *, aim_conn_t *); +int aim_rates_delparam(aim_session_t *, aim_conn_t *); + +#endif /* __AIM_INTERNAL_H__ */ diff --git a/protocols/oscar/auth.c b/protocols/oscar/auth.c new file mode 100644 index 00000000..0f7c8d0f --- /dev/null +++ b/protocols/oscar/auth.c @@ -0,0 +1,538 @@ +/* + * Deals with the authorizer (group 0x0017=23, and old-style non-SNAC login). + * + */ + +#include <aim.h>  + +#include "md5.h" + +static int aim_encode_password(const char *password, unsigned char *encoded); + +/*  + * This just pushes the passed cookie onto the passed connection, without + * the SNAC header or any of that. + * + * Very commonly used, as every connection except auth will require this to + * be the first thing you send. + * + */ +int aim_sendcookie(aim_session_t *sess, aim_conn_t *conn, const guint8 *chipsahoy) +{ +	aim_frame_t *fr; +	aim_tlvlist_t *tl = NULL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x0001, 4+2+2+AIM_COOKIELEN))) +		return -ENOMEM; + +	aimbs_put32(&fr->data, 0x00000001); +	aim_addtlvtochain_raw(&tl, 0x0006, AIM_COOKIELEN, chipsahoy);	 +	aim_writetlvchain(&fr->data, &tl); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Normally the FLAP version is sent as the first few bytes of the cookie, + * meaning you generally never call this. + * + * But there are times when something might want it seperate. Specifically, + * libfaim sends this internally when doing SNAC login. + * + */ +int aim_sendflapver(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_frame_t *fr; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 4))) +		return -ENOMEM; + +	aimbs_put32(&fr->data, 0x00000001); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * This is a bit confusing. + * + * Normal SNAC login goes like this: + *   - connect + *   - server sends flap version + *   - client sends flap version + *   - client sends screen name (17/6) + *   - server sends hash key (17/7) + *   - client sends auth request (17/2 -- aim_send_login) + *   - server yells + * + * XOR login (for ICQ) goes like this: + *   - connect + *   - server sends flap version + *   - client sends auth request which contains flap version (aim_send_login) + *   - server yells + * + * For the client API, we make them implement the most complicated version, + * and for the simpler version, we fake it and make it look like the more + * complicated process. + * + * This is done by giving the client a faked key, just so we can convince + * them to call aim_send_login right away, which will detect the session + * flag that says this is XOR login and ignore the key, sending an ICQ + * login request instead of the normal SNAC one. + * + * As soon as AOL makes ICQ log in the same way as AIM, this is /gone/. + * + * XXX This may cause problems if the client relies on callbacks only + * being called from the context of aim_rxdispatch()... + * + */ +static int goddamnicq(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ +	aim_frame_t fr; +	aim_rxcallback_t userfunc; +	 +	sess->flags &= ~AIM_SESS_FLAGS_SNACLOGIN; +	sess->flags |= AIM_SESS_FLAGS_XORLOGIN; + +	fr.conn = conn; +	 +	if ((userfunc = aim_callhandler(sess, conn, 0x0017, 0x0007))) +		userfunc(sess, &fr, ""); + +	return 0; +} + +/* + * In AIM 3.5 protocol, the first stage of login is to request login from the  + * Authorizer, passing it the screen name for verification.  If the name is  + * invalid, a 0017/0003 is spit back, with the standard error contents.  If  + * valid, a 0017/0007 comes back, which is the signal to send it the main  + * login command (0017/0002).  + * + */ +int aim_request_login(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL; +	struct im_connection *ic = sess->aux_data; +	 +	if (!sess || !conn || !sn) +		return -EINVAL; + +	if (isdigit(sn[0]) && set_getbool(&ic->acc->set, "old_icq_auth")) +		return goddamnicq(sess, conn, sn); + +	sess->flags |= AIM_SESS_FLAGS_SNACLOGIN; + +	aim_sendflapver(sess, conn); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+2+strlen(sn)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0017, 0x0006, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0017, 0x0006, 0x0000, snacid); + +	aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn); +	aim_writetlvchain(&fr->data, &tl); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Part two of the ICQ hack.  Note the ignoring of the key and clientinfo. + */ +static int goddamnicq2(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password) +{ +	static const char clientstr[] = {"ICQ Inc. - Product of ICQ (TM) 2001b.5.17.1.3642.85"}; +	static const char lang[] = {"en"}; +	static const char country[] = {"us"}; +	aim_frame_t *fr; +	aim_tlvlist_t *tl = NULL; +	guint8 *password_encoded; + +	if (!(password_encoded = (guint8 *) g_malloc(strlen(password)))) +		return -ENOMEM; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x01, 1152))) { +		g_free(password_encoded); +		return -ENOMEM; +	} + +	aim_encode_password(password, password_encoded); + +	aimbs_put32(&fr->data, 0x00000001); +	aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn); +	aim_addtlvtochain_raw(&tl, 0x0002, strlen(password), password_encoded); +	aim_addtlvtochain_raw(&tl, 0x0003, strlen(clientstr), (guint8 *)clientstr); +	aim_addtlvtochain16(&tl, 0x0016, 0x010a); /* cliend ID */ +	aim_addtlvtochain16(&tl, 0x0017, 0x0005); /* major version */ +	aim_addtlvtochain16(&tl, 0x0018, 0x0011); /* minor version */ +	aim_addtlvtochain16(&tl, 0x0019, 0x0001); /* point version */ +	aim_addtlvtochain16(&tl, 0x001a, 0x0e3a); /* build */ +	aim_addtlvtochain32(&tl, 0x0014, 0x00000055); /* distribution chan */ +	aim_addtlvtochain_raw(&tl, 0x000f, strlen(lang), (guint8 *)lang); +	aim_addtlvtochain_raw(&tl, 0x000e, strlen(country), (guint8 *)country); + +	aim_writetlvchain(&fr->data, &tl); + +	g_free(password_encoded); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * send_login(int socket, char *sn, char *password) + *   + * This is the initial login request packet. + * + * NOTE!! If you want/need to make use of the aim_sendmemblock() function, + * then the client information you send here must exactly match the + * executable that you're pulling the data from. + * + * WinAIM 4.8.2540 + *   clientstring = "AOL Instant Messenger (SM), version 4.8.2540/WIN32" + *   clientid = 0x0109 + *   major = 0x0004 + *   minor = 0x0008 + *   point = 0x0000 + *   build = 0x09ec + *   t(0x0014) = 0x000000af + *   t(0x004a) = 0x01 + * + * WinAIM 4.3.2188: + *   clientstring = "AOL Instant Messenger (SM), version 4.3.2188/WIN32" + *   clientid = 0x0109 + *   major = 0x0400 + *   minor = 0x0003 + *   point = 0x0000 + *   build = 0x088c + *   unknown = 0x00000086 + *   lang = "en" + *   country = "us" + *   unknown4a = 0x01 + * + * Latest WinAIM that libfaim can emulate without server-side buddylists: + *   clientstring = "AOL Instant Messenger (SM), version 4.1.2010/WIN32" + *   clientid = 0x0004 + *   major  = 0x0004 + *   minor  = 0x0001 + *   point = 0x0000 + *   build  = 0x07da + *   unknown= 0x0000004b + * + * WinAIM 3.5.1670: + *   clientstring = "AOL Instant Messenger (SM), version 3.5.1670/WIN32" + *   clientid = 0x0004 + *   major =  0x0003 + *   minor =  0x0005 + *   point = 0x0000 + *   build =  0x0686 + *   unknown =0x0000002a + * + * Java AIM 1.1.19: + *   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" + *   clientid = 0x0001 + *   major  = 0x0001 + *   minor  = 0x0001 + *   point = (not sent) + *   build  = 0x0013 + *   unknown= (not sent) + *    + * AIM for Linux 1.1.112: + *   clientstring = "AOL Instant Messenger (SM)" + *   clientid = 0x1d09 + *   major  = 0x0001 + *   minor  = 0x0001 + *   point = 0x0001 + *   build  = 0x0070 + *   unknown= 0x0000008b + *   serverstore = 0x01 + * + */ +int aim_send_login(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *password, struct client_info_s *ci, const char *key) +{ +	aim_frame_t *fr; +	aim_tlvlist_t *tl = NULL; +	guint8 digest[16]; +	aim_snacid_t snacid; + +	if (!ci || !sn || !password) +		return -EINVAL; + +	/* +	 * What the XORLOGIN flag _really_ means is that its an ICQ login, +	 * which is really stupid and painful, so its not done here. +	 * +	 */ +	if (sess->flags & AIM_SESS_FLAGS_XORLOGIN) +		return goddamnicq2(sess, conn, sn, password); + + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0017, 0x0002, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0017, 0x0002, 0x0000, snacid); + +	aim_addtlvtochain_raw(&tl, 0x0001, strlen(sn), (guint8 *)sn); + +	aim_encode_password_md5(password, key, digest); +	aim_addtlvtochain_raw(&tl, 0x0025, 16, digest); + +	/* +	 * Newer versions of winaim have an empty type x004c TLV here. +	 */ + +	if (ci->clientstring) +		aim_addtlvtochain_raw(&tl, 0x0003, strlen(ci->clientstring), (guint8 *)ci->clientstring); +	aim_addtlvtochain16(&tl, 0x0016, (guint16)ci->clientid); +	aim_addtlvtochain16(&tl, 0x0017, (guint16)ci->major); +	aim_addtlvtochain16(&tl, 0x0018, (guint16)ci->minor); +	aim_addtlvtochain16(&tl, 0x0019, (guint16)ci->point); +	aim_addtlvtochain16(&tl, 0x001a, (guint16)ci->build); +	aim_addtlvtochain_raw(&tl, 0x000e, strlen(ci->country), (guint8 *)ci->country); +	aim_addtlvtochain_raw(&tl, 0x000f, strlen(ci->lang), (guint8 *)ci->lang); + +	/* +	 * If set, old-fashioned buddy lists will not work. You will need +	 * to use SSI. +	 */ +	aim_addtlvtochain8(&tl, 0x004a, 0x01); + +	aim_writetlvchain(&fr->data, &tl); + +	aim_freetlvchain(&tl); +	 +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_encode_password_md5(const char *password, const char *key, guint8 *digest) +{ +	md5_state_t state; + +	md5_init(&state);	 +	md5_append(&state, (const md5_byte_t *)key, strlen(key)); +	md5_append(&state, (const md5_byte_t *)password, strlen(password)); +	md5_append(&state, (const md5_byte_t *)AIM_MD5_STRING, strlen(AIM_MD5_STRING)); +	md5_finish(&state, (md5_byte_t *)digest); + +	return 0; +} + +/** + * aim_encode_password - Encode a password using old XOR method + * @password: incoming password + * @encoded: buffer to put encoded password + * + * This takes a const pointer to a (null terminated) string + * containing the unencoded password.  It also gets passed + * an already allocated buffer to store the encoded password. + * This buffer should be the exact length of the password without + * the null.  The encoded password buffer /is not %NULL terminated/. + * + * The encoding_table seems to be a fixed set of values.  We'll + * hope it doesn't change over time!   + * + * This is only used for the XOR method, not the better MD5 method. + * + */ +static int aim_encode_password(const char *password, guint8 *encoded) +{ +	guint8 encoding_table[] = { +	/* v2.1 table, also works for ICQ */ +		0xf3, 0x26, 0x81, 0xc4, +		0x39, 0x86, 0xdb, 0x92, +		0x71, 0xa3, 0xb9, 0xe6, +		0x53, 0x7a, 0x95, 0x7c +	}; +	int i; + +	for (i = 0; i < strlen(password); i++) +		encoded[i] = (password[i] ^ encoding_table[i]); + +	return 0; +} + +/* + * This is sent back as a general response to the login command. + * It can be either an error or a success, depending on the + * precense of certain TLVs.   + * + * The client should check the value passed as errorcode. If + * its nonzero, there was an error. + * + */ +static int parse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_tlvlist_t *tlvlist; +	aim_rxcallback_t userfunc; +	struct aim_authresp_info info; +	int ret = 0; + +	memset(&info, 0, sizeof(info)); + +	/* +	 * Read block of TLVs.  All further data is derived +	 * from what is parsed here. +	 */ +	tlvlist = aim_readtlvchain(bs); + +	/* +	 * No matter what, we should have a screen name. +	 */ +	memset(sess->sn, 0, sizeof(sess->sn)); +	if (aim_gettlv(tlvlist, 0x0001, 1)) { +		info.sn = aim_gettlv_str(tlvlist, 0x0001, 1); +		strncpy(sess->sn, info.sn, sizeof(sess->sn)); +	} + +	/* +	 * Check for an error code.  If so, we should also +	 * have an error url. +	 */ +	if (aim_gettlv(tlvlist, 0x0008, 1))  +		info.errorcode = aim_gettlv16(tlvlist, 0x0008, 1); +	if (aim_gettlv(tlvlist, 0x0004, 1)) +		info.errorurl = aim_gettlv_str(tlvlist, 0x0004, 1); + +	/* +	 * BOS server address. +	 */ +	if (aim_gettlv(tlvlist, 0x0005, 1)) +		info.bosip = aim_gettlv_str(tlvlist, 0x0005, 1); + +	/* +	 * Authorization cookie. +	 */ +	if (aim_gettlv(tlvlist, 0x0006, 1)) { +		aim_tlv_t *tmptlv; + +		tmptlv = aim_gettlv(tlvlist, 0x0006, 1); + +		info.cookie = tmptlv->value; +	} + +	/* +	 * The email address attached to this account +	 *   Not available for ICQ logins. +	 */ +	if (aim_gettlv(tlvlist, 0x0011, 1)) +		info.email = aim_gettlv_str(tlvlist, 0x0011, 1); + +	/* +	 * The registration status.  (Not real sure what it means.) +	 *   Not available for ICQ logins. +	 * +	 *   1 = No disclosure +	 *   2 = Limited disclosure +	 *   3 = Full disclosure +	 * +	 * This has to do with whether your email address is available +	 * to other users or not.  AFAIK, this feature is no longer used. +	 * +	 */ +	if (aim_gettlv(tlvlist, 0x0013, 1)) +		info.regstatus = aim_gettlv16(tlvlist, 0x0013, 1); + +	if (aim_gettlv(tlvlist, 0x0040, 1)) +		info.latestbeta.build = aim_gettlv32(tlvlist, 0x0040, 1); +	if (aim_gettlv(tlvlist, 0x0041, 1)) +		info.latestbeta.url = aim_gettlv_str(tlvlist, 0x0041, 1); +	if (aim_gettlv(tlvlist, 0x0042, 1)) +		info.latestbeta.info = aim_gettlv_str(tlvlist, 0x0042, 1); +	if (aim_gettlv(tlvlist, 0x0043, 1)) +		info.latestbeta.name = aim_gettlv_str(tlvlist, 0x0043, 1); +	if (aim_gettlv(tlvlist, 0x0048, 1)) +		; /* no idea what this is */ + +	if (aim_gettlv(tlvlist, 0x0044, 1)) +		info.latestrelease.build = aim_gettlv32(tlvlist, 0x0044, 1); +	if (aim_gettlv(tlvlist, 0x0045, 1)) +		info.latestrelease.url = aim_gettlv_str(tlvlist, 0x0045, 1); +	if (aim_gettlv(tlvlist, 0x0046, 1)) +		info.latestrelease.info = aim_gettlv_str(tlvlist, 0x0046, 1); +	if (aim_gettlv(tlvlist, 0x0047, 1)) +		info.latestrelease.name = aim_gettlv_str(tlvlist, 0x0047, 1); +	if (aim_gettlv(tlvlist, 0x0049, 1)) +		; /* no idea what this is */ + + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac ? snac->family : 0x0017, snac ? snac->subtype : 0x0003))) +		ret = userfunc(sess, rx, &info); + +	g_free(info.sn); +	g_free(info.bosip); +	g_free(info.errorurl); +	g_free(info.email); +	g_free(info.latestrelease.name); +	g_free(info.latestrelease.url); +	g_free(info.latestrelease.info); +	g_free(info.latestbeta.name); +	g_free(info.latestbeta.url); +	g_free(info.latestbeta.info); + +	aim_freetlvchain(&tlvlist); + +	return ret; +} + +/* + * Middle handler for 0017/0007 SNACs.  Contains the auth key prefixed + * by only its length in a two byte word. + * + * Calls the client, which should then use the value to call aim_send_login. + * + */ +static int keyparse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int keylen, ret = 1; +	aim_rxcallback_t userfunc; +	char *keystr; + +	keylen = aimbs_get16(bs); +	keystr = aimbs_getstr(bs, keylen); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, keystr); + +	g_free(keystr);  + +	return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0003) +		return parse(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0007) +		return keyparse(sess, mod, rx, snac, bs); + +	return 0; +} + +int auth_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x0017; +	mod->version = 0x0000; +	mod->flags = 0; +	strncpy(mod->name, "auth", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} + diff --git a/protocols/oscar/bos.c b/protocols/oscar/bos.c new file mode 100644 index 00000000..e7f12f79 --- /dev/null +++ b/protocols/oscar/bos.c @@ -0,0 +1,161 @@ +#include <aim.h> +#include "bos.h" + +/* Request BOS rights (group 9, type 2) */ +int aim_bos_reqrights(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, 0x0009, 0x0002); +} + +/* BOS Rights (group 9, type 3) */ +static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	aim_tlvlist_t *tlvlist; +	guint16 maxpermits = 0, maxdenies = 0; +	int ret = 0; + +	/*  +	 * TLVs follow  +	 */ +	tlvlist = aim_readtlvchain(bs); + +	/* +	 * TLV type 0x0001: Maximum number of buddies on permit list. +	 */ +	if (aim_gettlv(tlvlist, 0x0001, 1)) +		maxpermits = aim_gettlv16(tlvlist, 0x0001, 1); + +	/* +	 * TLV type 0x0002: Maximum number of buddies on deny list. +	 */ +	if (aim_gettlv(tlvlist, 0x0002, 1))  +		maxdenies = aim_gettlv16(tlvlist, 0x0002, 1); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, maxpermits, maxdenies); + +	aim_freetlvchain(&tlvlist); + +	return ret;   +} + +/*  + * Set group permisson mask (group 9, type 4) + * + * Normally 0x1f (all classes). + * + * The group permission mask allows you to keep users of a certain + * class or classes from talking to you.  The mask should be + * a bitwise OR of all the user classes you want to see you. + * + */ +int aim_bos_setgroupperm(aim_session_t *sess, aim_conn_t *conn, guint32 mask) +{ +	return aim_genericreq_l(sess, conn, 0x0009, 0x0004, &mask); +} + +/* + * Modify permit/deny lists (group 9, types 5, 6, 7, and 8) + * + * Changes your visibility depending on changetype: + * + *  AIM_VISIBILITYCHANGE_PERMITADD: Lets provided list of names see you + *  AIM_VISIBILITYCHANGE_PERMIDREMOVE: Removes listed names from permit list + *  AIM_VISIBILITYCHANGE_DENYADD: Hides you from provided list of names + *  AIM_VISIBILITYCHANGE_DENYREMOVE: Lets list see you again + * + * list should be a list of  + * screen names in the form "Screen Name One&ScreenNameTwo&" etc. + * + * Equivelents to options in WinAIM: + *   - Allow all users to contact me: Send an AIM_VISIBILITYCHANGE_DENYADD + *      with only your name on it. + *   - Allow only users on my Buddy List: Send an  + *      AIM_VISIBILITYCHANGE_PERMITADD with the list the same as your + *      buddy list + *   - Allow only the uesrs below: Send an AIM_VISIBILITYCHANGE_PERMITADD  + *      with everyone listed that you want to see you. + *   - Block all users: Send an AIM_VISIBILITYCHANGE_PERMITADD with only  + *      yourself in the list + *   - Block the users below: Send an AIM_VISIBILITYCHANGE_DENYADD with + *      the list of users to be blocked + * + * XXX ye gods. + */ +int aim_bos_changevisibility(aim_session_t *sess, aim_conn_t *conn, int changetype, const char *denylist) +{ +	aim_frame_t *fr; +	int packlen = 0; +	guint16 subtype; +	char *localcpy = NULL, *tmpptr = NULL; +	int i; +	int listcount; +	aim_snacid_t snacid; + +	if (!denylist) +		return -EINVAL; + +	if (changetype == AIM_VISIBILITYCHANGE_PERMITADD) +		subtype = 0x05; +	else if (changetype == AIM_VISIBILITYCHANGE_PERMITREMOVE) +		subtype = 0x06; +	else if (changetype == AIM_VISIBILITYCHANGE_DENYADD) +		subtype = 0x07; +	else if (changetype == AIM_VISIBILITYCHANGE_DENYREMOVE) +		subtype = 0x08; +	else +		return -EINVAL; + +	localcpy = g_strdup(denylist); + +	listcount = aimutil_itemcnt(localcpy, '&'); +	packlen = aimutil_tokslen(localcpy, 99, '&') + listcount + 9; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, packlen))) { +		g_free(localcpy); +		return -ENOMEM; +	} + +	snacid = aim_cachesnac(sess, 0x0009, subtype, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0009, subtype, 0x00, snacid); + +	for (i = 0; (i < (listcount - 1)) && (i < 99); i++) { +		tmpptr = aimutil_itemidx(localcpy, i, '&'); + +		aimbs_put8(&fr->data, strlen(tmpptr)); +		aimbs_putraw(&fr->data, (guint8 *)tmpptr, strlen(tmpptr)); + +		g_free(tmpptr); +	} +	g_free(localcpy); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0003) +		return rights(sess, mod, rx, snac, bs); + +	return 0; +} + +int bos_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x0009; +	mod->version = 0x0001; +	mod->toolid = 0x0110; +	mod->toolversion = 0x0629; +	mod->flags = 0; +	strncpy(mod->name, "bos", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} + + diff --git a/protocols/oscar/bos.h b/protocols/oscar/bos.h new file mode 100644 index 00000000..e7c2cbcd --- /dev/null +++ b/protocols/oscar/bos.h @@ -0,0 +1,14 @@ +#ifndef __OSCAR_BOS_H__ +#define __OSCAR_BOS_H__ + +#define AIM_CB_FAM_BOS 0x0009 + +/* + * SNAC Family: Misc BOS Services. + */  +#define AIM_CB_BOS_ERROR 0x0001 +#define AIM_CB_BOS_RIGHTSQUERY 0x0002 +#define AIM_CB_BOS_RIGHTS 0x0003 +#define AIM_CB_BOS_DEFAULT 0xffff + +#endif /* __OSCAR_BOS_H__ */ diff --git a/protocols/oscar/buddylist.c b/protocols/oscar/buddylist.c new file mode 100644 index 00000000..a7b461f2 --- /dev/null +++ b/protocols/oscar/buddylist.c @@ -0,0 +1,150 @@ +#include <aim.h> +#include "buddylist.h" + +/* + * Oncoming Buddy notifications contain a subset of the + * user information structure.  Its close enough to run + * through aim_extractuserinfo() however. + * + * Although the offgoing notification contains no information, + * it is still in a format parsable by extractuserinfo. + * + */ +static int buddychange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_userinfo_t userinfo; +	aim_rxcallback_t userfunc; + +	aim_extractuserinfo(sess, bs, &userinfo); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx, &userinfo); + +	return 0; +} + +static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	aim_tlvlist_t *tlvlist; +	guint16 maxbuddies = 0, maxwatchers = 0; +	int ret = 0; + +	/*  +	 * TLVs follow  +	 */ +	tlvlist = aim_readtlvchain(bs); + +	/* +	 * TLV type 0x0001: Maximum number of buddies. +	 */ +	if (aim_gettlv(tlvlist, 0x0001, 1)) +		maxbuddies = aim_gettlv16(tlvlist, 0x0001, 1); + +	/* +	 * TLV type 0x0002: Maximum number of watchers. +	 * +	 * Watchers are other users who have you on their buddy +	 * list.  (This is called the "reverse list" by a certain +	 * other IM protocol.) +	 *  +	 */ +	if (aim_gettlv(tlvlist, 0x0002, 1)) +		maxwatchers = aim_gettlv16(tlvlist, 0x0002, 1); + +	/* +	 * TLV type 0x0003: Unknown. +	 * +	 * ICQ only? +	 */ + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, maxbuddies, maxwatchers); + +	aim_freetlvchain(&tlvlist); + +	return ret;   +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0003) +		return rights(sess, mod, rx, snac, bs); +	else if ((snac->subtype == 0x000b) || (snac->subtype == 0x000c)) +		return buddychange(sess, mod, rx, snac, bs); + +	return 0; +} + +int buddylist_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x0003; +	mod->version = 0x0001; +	mod->toolid = 0x0110; +	mod->toolversion = 0x0629; +	mod->flags = 0; +	strncpy(mod->name, "buddylist", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} + +/* + * aim_add_buddy() + * + * Adds a single buddy to your buddy list after login. + * + * XXX this should just be an extension of setbuddylist() + * + */ +int aim_add_buddy(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sn || !strlen(sn)) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0003, 0x0004, 0x0000, sn, strlen(sn)+1); +	aim_putsnac(&fr->data, 0x0003, 0x0004, 0x0000, snacid); + +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * XXX generalise to support removing multiple buddies (basically, its + * the same as setbuddylist() but with a different snac subtype). + * + */ +int aim_remove_buddy(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sn || !strlen(sn)) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0003, 0x0005, 0x0000, sn, strlen(sn)+1); +	aim_putsnac(&fr->data, 0x0003, 0x0005, 0x0000, snacid); + +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + diff --git a/protocols/oscar/buddylist.h b/protocols/oscar/buddylist.h new file mode 100644 index 00000000..9a325279 --- /dev/null +++ b/protocols/oscar/buddylist.h @@ -0,0 +1,23 @@ +#ifndef __OSCAR_BUDDYLIST_H__ +#define __OSCAR_BUDDYLIST_H__ + +#define AIM_CB_FAM_BUD 0x0003 + +/* + * SNAC Family: Buddy List Management Services. + */  +#define AIM_CB_BUD_ERROR 0x0001 +#define AIM_CB_BUD_REQRIGHTS 0x0002 +#define AIM_CB_BUD_RIGHTSINFO 0x0003 +#define AIM_CB_BUD_ADDBUDDY 0x0004 +#define AIM_CB_BUD_REMBUDDY 0x0005 +#define AIM_CB_BUD_REJECT 0x000a +#define AIM_CB_BUD_ONCOMING 0x000b +#define AIM_CB_BUD_OFFGOING 0x000c +#define AIM_CB_BUD_DEFAULT 0xffff + +/* aim_buddylist.c */ +int aim_add_buddy(aim_session_t *, aim_conn_t *, const char *); +int aim_remove_buddy(aim_session_t *, aim_conn_t *, const char *); + +#endif /* __OSCAR_BUDDYLIST_H__ */ diff --git a/protocols/oscar/chat.c b/protocols/oscar/chat.c new file mode 100644 index 00000000..fbf45693 --- /dev/null +++ b/protocols/oscar/chat.c @@ -0,0 +1,702 @@ +/* + * aim_chat.c + * + * Routines for the Chat service. + * + */ + +#include <aim.h>  +#include <glib.h> +#include "info.h" + +/* Stored in the ->priv of chat connections */ +struct chatconnpriv { +	guint16 exchange; +	char *name; +	guint16 instance; +}; + +void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn) +{ +	struct chatconnpriv *ccp = (struct chatconnpriv *)conn->priv; + +	if (ccp) +		g_free(ccp->name); +	g_free(ccp); + +	return; +} + +char *aim_chat_getname(aim_conn_t *conn) +{ +	struct chatconnpriv *ccp; + +	if (!conn) +		return NULL; + +	if (conn->type != AIM_CONN_TYPE_CHAT) +		return NULL; + +	ccp = (struct chatconnpriv *)conn->priv; + +	return ccp->name; +} + +/* XXX get this into conn.c -- evil!! */ +aim_conn_t *aim_chat_getconn(aim_session_t *sess, const char *name) +{ +	aim_conn_t *cur; + +	for (cur = sess->connlist; cur; cur = cur->next) { +		struct chatconnpriv *ccp = (struct chatconnpriv *)cur->priv; + +		if (cur->type != AIM_CONN_TYPE_CHAT) +			continue; +		if (!cur->priv) { +			imcb_error(sess->aux_data, "chat connection with no name!"); +			continue; +		} + +		if (strcmp(ccp->name, name) == 0) +			break; +	} + +	return cur; +} + +int aim_chat_attachname(aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance) +{ +	struct chatconnpriv *ccp; + +	if (!conn || !roomname) +		return -EINVAL; + +	if (conn->priv) +		g_free(conn->priv); + +	if (!(ccp = g_malloc(sizeof(struct chatconnpriv)))) +		return -ENOMEM; + +	ccp->exchange = exchange; +	ccp->name = g_strdup(roomname); +	ccp->instance = instance; + +	conn->priv = (void *)ccp; + +	return 0; +} + +/* + * Send a Chat Message. + * + * Possible flags: + *   AIM_CHATFLAGS_NOREFLECT   --  Unset the flag that requests messages + *                                 should be sent to their sender. + *   AIM_CHATFLAGS_AWAY        --  Mark the message as an autoresponse + *                                 (Note that WinAIM does not honor this, + *                                 and displays the message as normal.) + * + * XXX convert this to use tlvchains  + */ +int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const char *msg, int msglen) +{    +	int i; +	aim_frame_t *fr; +	aim_msgcookie_t *cookie; +	aim_snacid_t snacid; +	guint8 ckstr[8]; +	aim_tlvlist_t *otl = NULL, *itl = NULL; + +	if (!sess || !conn || !msg || (msglen <= 0)) +		return 0; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x000e, 0x0005, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x000e, 0x0005, 0x0000, snacid); + + +	/*  +	 * Generate a random message cookie. +	 * +	 * XXX mkcookie should generate the cookie and cache it in one +	 * operation to preserve uniqueness. +	 * +	 */ +	for (i = 0; i < sizeof(ckstr); i++) +		aimutil_put8(ckstr+i, (guint8) rand()); + +	cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_CHAT, NULL); +	cookie->data = NULL; /* XXX store something useful here */ + +	aim_cachecookie(sess, cookie); + +	for (i = 0; i < sizeof(ckstr); i++) +		aimbs_put8(&fr->data, ckstr[i]); + + +	/* +	 * Channel ID.  +	 */ +	aimbs_put16(&fr->data, 0x0003); + + +	/* +	 * Type 1: Flag meaning this message is destined to the room. +	 */ +	aim_addtlvtochain_noval(&otl, 0x0001); + +	/* +	 * Type 6: Reflect +	 */ +	if (!(flags & AIM_CHATFLAGS_NOREFLECT)) +		aim_addtlvtochain_noval(&otl, 0x0006); + +	/* +	 * Type 7: Autoresponse +	 */ +	if (flags & AIM_CHATFLAGS_AWAY) +		aim_addtlvtochain_noval(&otl, 0x0007); +	 +	/* [WvG] This wasn't there originally, but we really should send +	         the right charset flags, as we also do with normal +	         messages. Hope this will work. :-) */ +	/* +	if (flags & AIM_CHATFLAGS_UNICODE) +		aimbs_put16(&fr->data, 0x0002); +	else if (flags & AIM_CHATFLAGS_ISO_8859_1) +		aimbs_put16(&fr->data, 0x0003); +	else +		aimbs_put16(&fr->data, 0x0000); +	 +	aimbs_put16(&fr->data, 0x0000); +	*/ +	 +	/* +	 * SubTLV: Type 1: Message +	 */ +	aim_addtlvtochain_raw(&itl, 0x0001, strlen(msg), (guint8 *)msg); + +	/* +	 * Type 5: Message block.  Contains more TLVs. +	 * +	 * This could include other information... We just +	 * put in a message TLV however.   +	 *  +	 */ +	aim_addtlvtochain_frozentlvlist(&otl, 0x0005, &itl); + +	aim_writetlvchain(&fr->data, &otl); +	 +	aim_freetlvchain(&itl); +	aim_freetlvchain(&otl); +	 +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Join a room of name roomname.  This is the first step to joining an  + * already created room.  It's basically a Service Request for  + * family 0x000e, with a little added on to specify the exchange and room  + * name. + */ +int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL; +	struct chatsnacinfo csi; +	 +	if (!sess || !conn || !roomname || !strlen(roomname)) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) +		return -ENOMEM; + +	memset(&csi, 0, sizeof(csi)); +	csi.exchange = exchange; +	strncpy(csi.name, roomname, sizeof(csi.name)); +	csi.instance = instance; + +	snacid = aim_cachesnac(sess, 0x0001, 0x0004, 0x0000, &csi, sizeof(csi)); +	aim_putsnac(&fr->data, 0x0001, 0x0004, 0x0000, snacid); + +	/* +	 * Requesting service chat (0x000e) +	 */ +	aimbs_put16(&fr->data, 0x000e); + +	aim_addtlvtochain_chatroom(&tl, 0x0001, exchange, roomname, instance); +	aim_writetlvchain(&fr->data, &tl); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0;  +} + +int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo) +{ +	int namelen; + +	if (!bs || !outinfo) +		return 0; + +	outinfo->exchange = aimbs_get16(bs); +	namelen = aimbs_get8(bs); +	outinfo->name = aimbs_getstr(bs, namelen); +	outinfo->instance = aimbs_get16(bs); + +	return 0; +} + +int aim_chat_leaveroom(aim_session_t *sess, const char *name) +{ +	aim_conn_t *conn; + +	if (!(conn = aim_chat_getconn(sess, name))) +		return -ENOENT; + +	aim_conn_close(conn); + +	return 0; +} + +/* + * conn must be a BOS connection! + */ +int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance) +{ +	int i; +	aim_frame_t *fr; +	aim_msgcookie_t *cookie; +	struct aim_invite_priv *priv; +	guint8 ckstr[8]; +	aim_snacid_t snacid; +	aim_tlvlist_t *otl = NULL, *itl = NULL; +	guint8 *hdr; +	int hdrlen; +	aim_bstream_t hdrbs; +	 +	if (!sess || !conn || !sn || !msg || !roomname) +		return -EINVAL; + +	if (conn->type != AIM_CONN_TYPE_BOS) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1); +	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + + +	/* +	 * Cookie +	 */ +	for (i = 0; i < sizeof(ckstr); i++) +		aimutil_put8(ckstr, (guint8) rand()); + +	/* XXX should be uncached by an unwritten 'invite accept' handler */ +	if ((priv = g_malloc(sizeof(struct aim_invite_priv)))) { +		priv->sn = g_strdup(sn); +		priv->roomname = g_strdup(roomname); +		priv->exchange = exchange; +		priv->instance = instance; +	} + +	if ((cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_INVITE, priv))) +		aim_cachecookie(sess, cookie); +	else +		g_free(priv); + +	for (i = 0; i < sizeof(ckstr); i++) +		aimbs_put8(&fr->data, ckstr[i]); + + +	/* +	 * Channel (2) +	 */ +	aimbs_put16(&fr->data, 0x0002); + +	/* +	 * Dest sn +	 */ +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + +	/* +	 * TLV t(0005) +	 * +	 * Everything else is inside this TLV. +	 * +	 * Sigh.  AOL was rather inconsistent right here.  So we have +	 * to play some minor tricks.  Right inside the type 5 is some +	 * raw data, followed by a series of TLVs.   +	 * +	 */ +	hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2; +	hdr = g_malloc(hdrlen); +	aim_bstream_init(&hdrbs, hdr, hdrlen); +	 +	aimbs_put16(&hdrbs, 0x0000); /* Unknown! */ +	aimbs_putraw(&hdrbs, ckstr, sizeof(ckstr)); /* I think... */ +	aim_putcap(&hdrbs, AIM_CAPS_CHAT); + +	aim_addtlvtochain16(&itl, 0x000a, 0x0001); +	aim_addtlvtochain_noval(&itl, 0x000f); +	aim_addtlvtochain_raw(&itl, 0x000c, strlen(msg), (guint8 *)msg); +	aim_addtlvtochain_chatroom(&itl, 0x2711, exchange, roomname, instance); +	aim_writetlvchain(&hdrbs, &itl); +	 +	aim_addtlvtochain_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr); + +	aim_writetlvchain(&fr->data, &otl); + +	g_free(hdr); +	aim_freetlvchain(&itl); +	aim_freetlvchain(&otl); +	 +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * General room information.  Lots of stuff. + * + * Values I know are in here but I havent attached + * them to any of the 'Unknown's: + *	- Language (English) + * + * SNAC 000e/0002 + */ +static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_userinfo_t *userinfo = NULL; +	aim_rxcallback_t userfunc; +	int ret = 0; +	int usercount = 0; +	guint8 detaillevel = 0; +	char *roomname = NULL; +	struct aim_chat_roominfo roominfo; +	guint16 tlvcount = 0; +	aim_tlvlist_t *tlvlist; +	char *roomdesc = NULL; +	guint16 flags = 0; +	guint32 creationtime = 0; +	guint16 maxmsglen = 0, maxvisiblemsglen = 0; +	guint16 unknown_d2 = 0, unknown_d5 = 0; + +	aim_chat_readroominfo(bs, &roominfo); + +	detaillevel = aimbs_get8(bs); + +	if (detaillevel != 0x02) { +		imcb_error(sess->aux_data, "Only detaillevel 0x2 is support at the moment"); +		return 1; +	} + +	tlvcount = aimbs_get16(bs); + +	/* +	 * Everything else are TLVs. +	 */  +	tlvlist = aim_readtlvchain(bs); + +	/* +	 * TLV type 0x006a is the room name in Human Readable Form. +	 */ +	if (aim_gettlv(tlvlist, 0x006a, 1)) +		roomname = aim_gettlv_str(tlvlist, 0x006a, 1); + +	/* +	 * Type 0x006f: Number of occupants. +	 */ +	if (aim_gettlv(tlvlist, 0x006f, 1)) +		usercount = aim_gettlv16(tlvlist, 0x006f, 1); + +	/* +	 * Type 0x0073:  Occupant list. +	 */ +	if (aim_gettlv(tlvlist, 0x0073, 1)) {	 +		int curoccupant = 0; +		aim_tlv_t *tmptlv; +		aim_bstream_t occbs; + +		tmptlv = aim_gettlv(tlvlist, 0x0073, 1); + +		/* Allocate enough userinfo structs for all occupants */ +		userinfo = g_new0(aim_userinfo_t, usercount); + +		aim_bstream_init(&occbs, tmptlv->value, tmptlv->length); + +		while (curoccupant < usercount) +			aim_extractuserinfo(sess, &occbs, &userinfo[curoccupant++]); +	} + +	/*  +	 * Type 0x00c9: Flags. (AIM_CHATROOM_FLAG) +	 */ +	if (aim_gettlv(tlvlist, 0x00c9, 1)) +		flags = aim_gettlv16(tlvlist, 0x00c9, 1); + +	/*  +	 * Type 0x00ca: Creation time (4 bytes) +	 */ +	if (aim_gettlv(tlvlist, 0x00ca, 1)) +		creationtime = aim_gettlv32(tlvlist, 0x00ca, 1); + +	/*  +	 * Type 0x00d1: Maximum Message Length +	 */ +	if (aim_gettlv(tlvlist, 0x00d1, 1)) +		maxmsglen = aim_gettlv16(tlvlist, 0x00d1, 1); + +	/*  +	 * Type 0x00d2: Unknown. (2 bytes) +	 */ +	if (aim_gettlv(tlvlist, 0x00d2, 1)) +		unknown_d2 = aim_gettlv16(tlvlist, 0x00d2, 1); + +	/*  +	 * Type 0x00d3: Room Description +	 */ +	if (aim_gettlv(tlvlist, 0x00d3, 1)) +		roomdesc = aim_gettlv_str(tlvlist, 0x00d3, 1); + +	/* +	 * Type 0x000d4: Unknown (flag only) +	 */ +	if (aim_gettlv(tlvlist, 0x000d4, 1)) +		; + +	/*  +	 * Type 0x00d5: Unknown. (1 byte) +	 */ +	if (aim_gettlv(tlvlist, 0x00d5, 1)) +		unknown_d5 = aim_gettlv8(tlvlist, 0x00d5, 1); + + +	/* +	 * Type 0x00d6: Encoding 1 ("us-ascii") +	 */ +	if (aim_gettlv(tlvlist, 0x000d6, 1)) +		; +	 +	/* +	 * Type 0x00d7: Language 1 ("en") +	 */ +	if (aim_gettlv(tlvlist, 0x000d7, 1)) +		; + +	/* +	 * Type 0x00d8: Encoding 2 ("us-ascii") +	 */ +	if (aim_gettlv(tlvlist, 0x000d8, 1)) +		; +	 +	/* +	 * Type 0x00d9: Language 2 ("en") +	 */ +	if (aim_gettlv(tlvlist, 0x000d9, 1)) +		; + +	/* +	 * Type 0x00da: Maximum visible message length +	 */ +	if (aim_gettlv(tlvlist, 0x000da, 1)) +		maxvisiblemsglen = aim_gettlv16(tlvlist, 0x00da, 1); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) { +		ret = userfunc(sess, +				rx,  +				&roominfo, +				roomname, +				usercount, +				userinfo,	 +				roomdesc, +				flags, +				creationtime, +				maxmsglen, +				unknown_d2, +				unknown_d5, +				maxvisiblemsglen); +	} + +	g_free(roominfo.name); +	g_free(userinfo); +	g_free(roomname); +	g_free(roomdesc); +	aim_freetlvchain(&tlvlist); + +	return ret; +} + +static int userlistchange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_userinfo_t *userinfo = NULL; +	aim_rxcallback_t userfunc; +	int curcount = 0, ret = 0; + +	while (aim_bstream_empty(bs)) { +		curcount++; +		userinfo = g_realloc(userinfo, curcount * sizeof(aim_userinfo_t)); +		aim_extractuserinfo(sess, bs, &userinfo[curcount-1]); +	} + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, curcount, userinfo); + +	g_free(userinfo); + +	return ret; +} + +/* + * We could probably include this in the normal ICBM parsing  + * code as channel 0x0003, however, since only the start + * would be the same, we might as well do it here. + * + * General outline of this SNAC: + *   snac + *   cookie + *   channel id + *   tlvlist + *     unknown + *     source user info + *       name + *       evility + *       userinfo tlvs + *         online time + *         etc + *     message metatlv + *       message tlv + *         message string + *       possibly others + *   + */ +static int incomingmsg(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_userinfo_t userinfo; +	aim_rxcallback_t userfunc;	 +	int ret = 0; +	guint8 *cookie; +	guint16 channel; +	aim_tlvlist_t *otl; +	char *msg = NULL; +	aim_msgcookie_t *ck; + +	memset(&userinfo, 0, sizeof(aim_userinfo_t)); + +	/* +	 * ICBM Cookie.  Uncache it. +	 */ +	cookie = aimbs_getraw(bs, 8); + +	if ((ck = aim_uncachecookie(sess, cookie, AIM_COOKIETYPE_CHAT))) { +		g_free(ck->data); +		g_free(ck); +	} + +	/* +	 * Channel ID +	 * +	 * Channels 1 and 2 are implemented in the normal ICBM +	 * parser. +	 * +	 * We only do channel 3 here. +	 * +	 */ +	channel = aimbs_get16(bs); + +	if (channel != 0x0003) { +		imcb_error(sess->aux_data, "unknown channel!"); +		return 0; +	} + +	/* +	 * Start parsing TLVs right away.  +	 */ +	otl = aim_readtlvchain(bs); + +	/* +	 * Type 0x0003: Source User Information +	 */ +	if (aim_gettlv(otl, 0x0003, 1)) { +		aim_tlv_t *userinfotlv; +		aim_bstream_t tbs; + +		userinfotlv = aim_gettlv(otl, 0x0003, 1); + +		aim_bstream_init(&tbs, userinfotlv->value, userinfotlv->length); +		aim_extractuserinfo(sess, &tbs, &userinfo); +	} + +	/* +	 * Type 0x0001: If present, it means it was a message to the  +	 * room (as opposed to a whisper). +	 */ +	if (aim_gettlv(otl, 0x0001, 1)) +		; + +	/* +	 * Type 0x0005: Message Block.  Conains more TLVs. +	 */ +	if (aim_gettlv(otl, 0x0005, 1)) { +		aim_tlvlist_t *itl; +		aim_tlv_t *msgblock; +		aim_bstream_t tbs; + +		msgblock = aim_gettlv(otl, 0x0005, 1); +		aim_bstream_init(&tbs, msgblock->value, msgblock->length); +		itl = aim_readtlvchain(&tbs); + +		/*  +		 * Type 0x0001: Message. +		 */	 +		if (aim_gettlv(itl, 0x0001, 1)) +			msg = aim_gettlv_str(itl, 0x0001, 1); + +		aim_freetlvchain(&itl);  +	} + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, &userinfo, msg); + +	g_free(cookie); +	g_free(msg); +	aim_freetlvchain(&otl); + +	return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0002) +		return infoupdate(sess, mod, rx, snac, bs); +	else if ((snac->subtype == 0x0003) || (snac->subtype == 0x0004)) +		return userlistchange(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0006) +		return incomingmsg(sess, mod, rx, snac, bs); + +	return 0; +} + +int chat_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x000e; +	mod->version = 0x0001; +	mod->toolid = 0x0010; +	mod->toolversion = 0x0629; +	mod->flags = 0; +	strncpy(mod->name, "chat", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} diff --git a/protocols/oscar/chat.h b/protocols/oscar/chat.h new file mode 100644 index 00000000..6b360326 --- /dev/null +++ b/protocols/oscar/chat.h @@ -0,0 +1,17 @@ +#ifndef __OSCAR_CHAT_H__ +#define __OSCAR_CHAT_H__ + +#define AIM_CB_FAM_CHT 0x000e /* Chat */ + +/* + * SNAC Family: Chat Services + */  +#define AIM_CB_CHT_ERROR 0x0001 +#define AIM_CB_CHT_ROOMINFOUPDATE 0x0002 +#define AIM_CB_CHT_USERJOIN 0x0003 +#define AIM_CB_CHT_USERLEAVE 0x0004 +#define AIM_CB_CHT_OUTGOINGMSG 0x0005 +#define AIM_CB_CHT_INCOMINGMSG 0x0006 +#define AIM_CB_CHT_DEFAULT 0xffff + +#endif /* __OSCAR_CHAT_H__ */ diff --git a/protocols/oscar/chatnav.c b/protocols/oscar/chatnav.c new file mode 100644 index 00000000..7cfc52af --- /dev/null +++ b/protocols/oscar/chatnav.c @@ -0,0 +1,421 @@ +/* + * Handle ChatNav. + * + * [The ChatNav(igation) service does various things to keep chat + *  alive.  It provides room information, room searching and creating,  + *  as well as giving users the right ("permission") to use chat.] + * + */ + +#include <aim.h> +#include "chatnav.h" + +/* + * conn must be a chatnav connection! + */ +int aim_chatnav_reqrights(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n_snacid(sess, conn, 0x000d, 0x0002); +} + +int aim_chatnav_createroom(aim_session_t *sess, aim_conn_t *conn, const char *name, guint16 exchange) +{ +	static const char ck[] = {"create"}; +	static const char lang[] = {"en"}; +	static const char charset[] = {"us-ascii"}; +	aim_frame_t *fr; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x000d, 0x0008, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x000d, 0x0008, 0x0000, snacid); + +	/* exchange */ +	aimbs_put16(&fr->data, exchange); + +	/* +	 * This looks to be a big hack.  You'll note that this entire +	 * SNAC is just a room info structure, but the hard room name, +	 * here, is set to "create".   +	 * +	 * Either this goes on the "list of questions concerning +	 * why-the-hell-did-you-do-that", or this value is completly +	 * ignored.  Without experimental evidence, but a good knowledge of +	 * AOL style, I'm going to guess that it is the latter, and that +	 * the value of the room name in create requests is ignored. +	 */ +	aimbs_put8(&fr->data, strlen(ck)); +	aimbs_putraw(&fr->data, (guint8 *)ck, strlen(ck)); + +	/*  +	 * instance +	 *  +	 * Setting this to 0xffff apparently assigns the last instance. +	 * +	 */ +	aimbs_put16(&fr->data, 0xffff); + +	/* detail level */ +	aimbs_put8(&fr->data, 0x01); + +	aim_addtlvtochain_raw(&tl, 0x00d3, strlen(name), (guint8 *)name); +	aim_addtlvtochain_raw(&tl, 0x00d6, strlen(charset), (guint8 *)charset); +	aim_addtlvtochain_raw(&tl, 0x00d7, strlen(lang), (guint8 *)lang); + +	/* tlvcount */ +	aimbs_put16(&fr->data, aim_counttlvchain(&tl)); +	aim_writetlvchain(&fr->data, &tl); + +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +static int parseinfo_perms(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2) +{ +	aim_rxcallback_t userfunc; +	int ret = 0; +	struct aim_chat_exchangeinfo *exchanges = NULL; +	int curexchange; +	aim_tlv_t *exchangetlv; +	guint8 maxrooms = 0; +	aim_tlvlist_t *tlvlist, *innerlist; + +	tlvlist = aim_readtlvchain(bs); + +	/*  +	 * Type 0x0002: Maximum concurrent rooms. +	 */  +	if (aim_gettlv(tlvlist, 0x0002, 1)) +		maxrooms = aim_gettlv8(tlvlist, 0x0002, 1); + +	/*  +	 * Type 0x0003: Exchange information +	 * +	 * There can be any number of these, each one +	 * representing another exchange.   +	 *  +	 */ +	for (curexchange = 0; ((exchangetlv = aim_gettlv(tlvlist, 0x0003, curexchange+1))); ) { +		aim_bstream_t tbs; + +		aim_bstream_init(&tbs, exchangetlv->value, exchangetlv->length); + +		curexchange++; + +		exchanges = g_realloc(exchanges, curexchange * sizeof(struct aim_chat_exchangeinfo)); + +		/* exchange number */ +		exchanges[curexchange-1].number = aimbs_get16(&tbs); +		innerlist = aim_readtlvchain(&tbs); + +		/*  +		 * Type 0x000a: Unknown. +		 * +		 * Usually three bytes: 0x0114 (exchange 1) or 0x010f (others). +		 * +		 */ +		if (aim_gettlv(innerlist, 0x000a, 1)) +			; + +		/*  +		 * Type 0x000d: Unknown. +		 */ +		if (aim_gettlv(innerlist, 0x000d, 1)) +			; + +		/*  +		 * Type 0x0004: Unknown +		 */ +		if (aim_gettlv(innerlist, 0x0004, 1)) +			; + +		/*  +		 * Type 0x0002: Unknown +		 */ +		if (aim_gettlv(innerlist, 0x0002, 1)) { +			guint16 classperms; + +			classperms = aim_gettlv16(innerlist, 0x0002, 1); +			 +		} + +		/* +		 * Type 0x00c9: Flags +		 * +		 * 1 Evilable +		 * 2 Nav Only +		 * 4 Instancing Allowed +		 * 8 Occupant Peek Allowed +		 * +		 */  +		if (aim_gettlv(innerlist, 0x00c9, 1)) +			exchanges[curexchange-1].flags = aim_gettlv16(innerlist, 0x00c9, 1); +		       +		/* +		 * Type 0x00ca: Creation Date  +		 */ +		if (aim_gettlv(innerlist, 0x00ca, 1)) +			; +		       +		/* +		 * Type 0x00d0: Mandatory Channels? +		 */ +		if (aim_gettlv(innerlist, 0x00d0, 1)) +			; + +		/* +		 * Type 0x00d1: Maximum Message length +		 */ +		if (aim_gettlv(innerlist, 0x00d1, 1)) +			; + +		/* +		 * Type 0x00d2: Maximum Occupancy? +		 */ +		if (aim_gettlv(innerlist, 0x00d2, 1))	 +			; + +		/* +		 * Type 0x00d3: Exchange Description +		 */ +		if (aim_gettlv(innerlist, 0x00d3, 1))	 +			exchanges[curexchange-1].name = aim_gettlv_str(innerlist, 0x00d3, 1); +		else +			exchanges[curexchange-1].name = NULL; + +		/* +		 * Type 0x00d4: Exchange Description URL +		 */ +		if (aim_gettlv(innerlist, 0x00d4, 1))	 +			; + +		/* +		 * Type 0x00d5: Creation Permissions +		 * +		 * 0  Creation not allowed +		 * 1  Room creation allowed +		 * 2  Exchange creation allowed +		 *  +		 */ +		if (aim_gettlv(innerlist, 0x00d5, 1)) { +			guint8 createperms; + +			createperms = aim_gettlv8(innerlist, 0x00d5, 1); +		} + +		/* +		 * Type 0x00d6: Character Set (First Time) +		 */	       +		if (aim_gettlv(innerlist, 0x00d6, 1))	 +			exchanges[curexchange-1].charset1 = aim_gettlv_str(innerlist, 0x00d6, 1); +		else +			exchanges[curexchange-1].charset1 = NULL; +		       +		/* +		 * Type 0x00d7: Language (First Time) +		 */	       +		if (aim_gettlv(innerlist, 0x00d7, 1))	 +			exchanges[curexchange-1].lang1 = aim_gettlv_str(innerlist, 0x00d7, 1); +		else +			exchanges[curexchange-1].lang1 = NULL; + +		/* +		 * Type 0x00d8: Character Set (Second Time) +		 */	       +		if (aim_gettlv(innerlist, 0x00d8, 1))	 +			exchanges[curexchange-1].charset2 = aim_gettlv_str(innerlist, 0x00d8, 1); +		else +			exchanges[curexchange-1].charset2 = NULL; + +		/* +		 * Type 0x00d9: Language (Second Time) +		 */	       +		if (aim_gettlv(innerlist, 0x00d9, 1))	 +			exchanges[curexchange-1].lang2 = aim_gettlv_str(innerlist, 0x00d9, 1); +		else +			exchanges[curexchange-1].lang2 = NULL; +		       +		/* +		 * Type 0x00da: Unknown +		 */ +		if (aim_gettlv(innerlist, 0x00da, 1))	 +			; + +		aim_freetlvchain(&innerlist); +	} + +	/* +	 * Call client. +	 */ +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, snac2->type, maxrooms, curexchange, exchanges); + +	for (curexchange--; curexchange >= 0; curexchange--) { +		g_free(exchanges[curexchange].name); +		g_free(exchanges[curexchange].charset1); +		g_free(exchanges[curexchange].lang1); +		g_free(exchanges[curexchange].charset2); +		g_free(exchanges[curexchange].lang2); +	} +	g_free(exchanges); +	aim_freetlvchain(&tlvlist); + +	return ret; +} + +static int parseinfo_create(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2) +{ +	aim_rxcallback_t userfunc; +	aim_tlvlist_t *tlvlist, *innerlist; +	char *ck = NULL, *fqcn = NULL, *name = NULL; +	guint16 exchange = 0, instance = 0, unknown = 0, flags = 0, maxmsglen = 0, maxoccupancy = 0; +	guint32 createtime = 0; +	guint8 createperms = 0, detaillevel; +	int cklen; +	aim_tlv_t *bigblock; +	int ret = 0; +	aim_bstream_t bbbs; + +	tlvlist = aim_readtlvchain(bs); + +	if (!(bigblock = aim_gettlv(tlvlist, 0x0004, 1))) { +		imcb_error(sess->aux_data, "no bigblock in top tlv in create room response"); +	 +		aim_freetlvchain(&tlvlist); +		return 0; +	} + +	aim_bstream_init(&bbbs, bigblock->value, bigblock->length); + +	exchange = aimbs_get16(&bbbs); +	cklen = aimbs_get8(&bbbs); +	ck = aimbs_getstr(&bbbs, cklen); +	instance = aimbs_get16(&bbbs); +	detaillevel = aimbs_get8(&bbbs); + +	if (detaillevel != 0x02) { +		imcb_error(sess->aux_data, "unknown detaillevel in create room response"); +		aim_freetlvchain(&tlvlist); +		g_free(ck); +		return 0; +	} + +	unknown = aimbs_get16(&bbbs); + +	innerlist = aim_readtlvchain(&bbbs); + +	if (aim_gettlv(innerlist, 0x006a, 1)) +		fqcn = aim_gettlv_str(innerlist, 0x006a, 1); + +	if (aim_gettlv(innerlist, 0x00c9, 1)) +		flags = aim_gettlv16(innerlist, 0x00c9, 1); + +	if (aim_gettlv(innerlist, 0x00ca, 1)) +		createtime = aim_gettlv32(innerlist, 0x00ca, 1); + +	if (aim_gettlv(innerlist, 0x00d1, 1)) +		maxmsglen = aim_gettlv16(innerlist, 0x00d1, 1); + +	if (aim_gettlv(innerlist, 0x00d2, 1)) +		maxoccupancy = aim_gettlv16(innerlist, 0x00d2, 1); + +	if (aim_gettlv(innerlist, 0x00d3, 1)) +		name = aim_gettlv_str(innerlist, 0x00d3, 1); + +	if (aim_gettlv(innerlist, 0x00d5, 1)) +		createperms = aim_gettlv8(innerlist, 0x00d5, 1); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) { +		ret = userfunc(sess, rx, snac2->type, fqcn, instance, exchange, flags, createtime, maxmsglen, maxoccupancy, createperms, unknown, name, ck); +	} + +	g_free(ck); +	g_free(name); +	g_free(fqcn); +	aim_freetlvchain(&innerlist); +	aim_freetlvchain(&tlvlist); + +	return ret; +} + +/* + * Since multiple things can trigger this callback, we must lookup the  + * snacid to determine the original snac subtype that was called. + * + * XXX This isn't really how this works.  But this is:  Every d/9 response + * has a 16bit value at the beginning. That matches to: + *    Short Desc = 1 + *    Full Desc = 2 + *    Instance Info = 4 + *    Nav Short Desc = 8 + *    Nav Instance Info = 16 + * And then everything is really asynchronous.  There is no specific  + * attachment of a response to a create room request, for example.  Creating + * the room yields no different a response than requesting the room's info. + * + */ +static int parseinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_snac_t *snac2; +	int ret = 0; + +	if (!(snac2 = aim_remsnac(sess, snac->id))) { +		imcb_error(sess->aux_data, "received response to unknown request!"); +		return 0; +	} + +	if (snac2->family != 0x000d) { +		imcb_error(sess->aux_data, "received response that maps to corrupt request!"); +		return 0; +	} + +	/* +	 * We now know what the original SNAC subtype was. +	 */ +	if (snac2->type == 0x0002) /* request chat rights */ +		ret = parseinfo_perms(sess, mod, rx, snac, bs, snac2); +	else if (snac2->type == 0x0003) {} /* request exchange info */ +	else if (snac2->type == 0x0004) {} /* request room info */ +	else if (snac2->type == 0x0005) {} /* request more room info */ +	else if (snac2->type == 0x0006) {} /* request occupant list */ +	else if (snac2->type == 0x0007) {} /* search for a room */ +	else if (snac2->type == 0x0008) /* create room */ +		ret = parseinfo_create(sess, mod, rx, snac, bs, snac2); +	else +		imcb_error(sess->aux_data, "unknown request subtype"); + +	if (snac2) +		g_free(snac2->data); +	g_free(snac2); + +	return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0009) +		return parseinfo(sess, mod, rx, snac, bs); + +	return 0; +} + +int chatnav_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x000d; +	mod->version = 0x0003; +	mod->toolid = 0x0010; +	mod->toolversion = 0x0629; +	mod->flags = 0; +	strncpy(mod->name, "chatnav", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} diff --git a/protocols/oscar/chatnav.h b/protocols/oscar/chatnav.h new file mode 100644 index 00000000..285decad --- /dev/null +++ b/protocols/oscar/chatnav.h @@ -0,0 +1,14 @@ +#ifndef __OSCAR_CHATNAV_H__ +#define __OSCAR_CHATNAV_H__ + +#define AIM_CB_FAM_CTN 0x000d /* ChatNav */ + +/* + * SNAC Family: Chat Navigation Services + */  +#define AIM_CB_CTN_ERROR 0x0001 +#define AIM_CB_CTN_CREATE 0x0008 +#define AIM_CB_CTN_INFO 0x0009 +#define AIM_CB_CTN_DEFAULT 0xffff + +#endif /* __OSCAR_CHATNAV_H__ */ diff --git a/protocols/oscar/conn.c b/protocols/oscar/conn.c new file mode 100644 index 00000000..77ffd50f --- /dev/null +++ b/protocols/oscar/conn.c @@ -0,0 +1,684 @@ + +/* + * conn.c + * + * Does all this gloriously nifty connection handling stuff... + * + */ + +#include <aim.h>  +#include "sock.h" + +static int aim_logoff(aim_session_t *sess); + +/* + * In OSCAR, every connection has a set of SNAC groups associated + * with it.  These are the groups that you can send over this connection + * without being guarenteed a "Not supported" SNAC error.   + * + * The grand theory of things says that these associations transcend  + * what libfaim calls "connection types" (conn->type).  You can probably + * see the elegance here, but since I want to revel in it for a bit, you  + * get to hear it all spelled out. + * + * So let us say that you have your core BOS connection running.  One + * of your modules has just given you a SNAC of the group 0x0004 to send + * you.  Maybe an IM destined for some twit in Greenland.  So you start + * at the top of your connection list, looking for a connection that  + * claims to support group 0x0004.  You find one.  Why, that neat BOS + * connection of yours can do that.  So you send it on its way. + * + * Now, say, that fellow from Greenland has friends and they all want to + * meet up with you in a lame chat room.  This has landed you a SNAC + * in the family 0x000e and you have to admit you're a bit lost.  You've + * searched your connection list for someone who wants to make your life + * easy and deliver this SNAC for you, but there isn't one there. + * + * Here comes the good bit.  Without even letting anyone know, particularly + * the module that decided to send this SNAC, and definitly not that twit + * in Greenland, you send out a service request.  In this request, you have + * marked the need for a connection supporting group 0x000e.  A few seconds + * later, you receive a service redirect with an IP address and a cookie in + * it.  Great, you say.  Now I have something to do.  Off you go, making + * that connection.  One of the first things you get from this new server + * is a message saying that indeed it does support the group you were looking + * for.  So you continue and send rate confirmation and all that.   + *  + * Then you remember you had that SNAC to send, and now you have a means to + * do it, and you do, and everyone is happy.  Except the Greenlander, who is + * still stuck in the bitter cold. + * + * Oh, and this is useful for building the Migration SNACs, too.  In the + * future, this may help convince me to implement rate limit mitigation + * for real.  We'll see. + * + * Just to make me look better, I'll say that I've known about this great + * scheme for quite some time now.  But I still haven't convinced myself + * to make libfaim work that way.  It would take a fair amount of effort, + * and probably some client API changes as well.  (Whenever I don't want + * to do something, I just say it would change the client API.  Then I  + * instantly have a couple of supporters of not doing it.) + * + * Generally, addgroup is only called by the internal handling of the + * server ready SNAC.  So if you want to do something before that, you'll + * have to be more creative.  That is done rather early, though, so I don't + * think you have to worry about it.  Unless you're me.  I care deeply + * about such inane things. + * + */ +void aim_conn_addgroup(aim_conn_t *conn, guint16 group) +{ +	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; +	struct snacgroup *sg; + +	if (!(sg = g_malloc(sizeof(struct snacgroup)))) +		return; + +	sg->group = group; + +	sg->next = ins->groups; +	ins->groups = sg; + +	return; +} + +aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, guint16 group) +{ +	aim_conn_t *cur; + +	for (cur = sess->connlist; cur; cur = cur->next) { +		aim_conn_inside_t *ins = (aim_conn_inside_t *)cur->inside; +		struct snacgroup *sg; + +		for (sg = ins->groups; sg; sg = sg->next) { +			if (sg->group == group) +				return cur; +		} +	} + +	return NULL; +} + +static void connkill_snacgroups(struct snacgroup **head) +{ +	struct snacgroup *sg; + +	for (sg = *head; sg; ) { +		struct snacgroup *tmp; + +		tmp = sg->next; +		g_free(sg); +		sg = tmp; +	} + +	*head = NULL; + +	return; +} + +static void connkill_rates(struct rateclass **head) +{ +	struct rateclass *rc; + +	for (rc = *head; rc; ) { +		struct rateclass *tmp; +		struct snacpair *sp; + +		tmp = rc->next; + +		for (sp = rc->members; sp; ) { +			struct snacpair *tmpsp; + +			tmpsp = sp->next; +			g_free(sp); +			sp = tmpsp; +		} +		g_free(rc); + +		rc = tmp; +	} + +	*head = NULL; + +	return; +} + +static void connkill_real(aim_session_t *sess, aim_conn_t **deadconn) +{ + +	aim_rxqueue_cleanbyconn(sess, *deadconn); +	aim_tx_cleanqueue(sess, *deadconn); + +	if ((*deadconn)->fd != -1)  +		aim_conn_close(*deadconn); + +	/* +	 * XXX ->priv should never be touched by the library. I know +	 * it used to be, but I'm getting rid of all that.  Use +	 * ->internal instead. +	 */ +	if ((*deadconn)->priv) +		g_free((*deadconn)->priv); + +	/* +	 * This will free ->internal if it necessary... +	 */ +	if ((*deadconn)->type == AIM_CONN_TYPE_CHAT) +		aim_conn_kill_chat(sess, *deadconn); + +	if ((*deadconn)->inside) { +		aim_conn_inside_t *inside = (aim_conn_inside_t *)(*deadconn)->inside; + +		connkill_snacgroups(&inside->groups); +		connkill_rates(&inside->rates); + +		g_free(inside); +	} + +	g_free(*deadconn); +	*deadconn = NULL; + +	return; +} + +/** + * aim_connrst - Clears out connection list, killing remaining connections. + * @sess: Session to be cleared + * + * Clears out the connection list and kills any connections left. + * + */ +static void aim_connrst(aim_session_t *sess) +{ + +	if (sess->connlist) { +		aim_conn_t *cur = sess->connlist, *tmp; + +		while (cur) { +			tmp = cur->next; +			aim_conn_close(cur); +			connkill_real(sess, &cur); +			cur = tmp; +		} +	} + +	sess->connlist = NULL; + +	return; +} + +/** + * aim_conn_init - Reset a connection to default values. + * @deadconn: Connection to be reset + * + * Initializes and/or resets a connection structure. + * + */ +static void aim_conn_init(aim_conn_t *deadconn) +{ + +	if (!deadconn) +		return; + +	deadconn->fd = -1; +	deadconn->subtype = -1; +	deadconn->type = -1; +	deadconn->seqnum = 0; +	deadconn->lastactivity = 0; +	deadconn->forcedlatency = 0; +	deadconn->handlerlist = NULL; +	deadconn->priv = NULL; +	memset(deadconn->inside, 0, sizeof(aim_conn_inside_t)); + +	return; +} + +/** + * aim_conn_getnext - Gets a new connection structure. + * @sess: Session + * + * Allocate a new empty connection structure. + * + */ +static aim_conn_t *aim_conn_getnext(aim_session_t *sess) +{ +	aim_conn_t *newconn; + +	if (!(newconn = g_new0(aim_conn_t,1))) 	 +		return NULL; + +	if (!(newconn->inside = g_new0(aim_conn_inside_t,1))) { +		g_free(newconn); +		return NULL; +	} + +	aim_conn_init(newconn); + +	newconn->next = sess->connlist; +	sess->connlist = newconn; + +	return newconn; +} + +/** + * aim_conn_kill - Close and free a connection. + * @sess: Session for the connection + * @deadconn: Connection to be freed + * + * Close, clear, and free a connection structure. Should never be + * called from within libfaim. + * + */ +void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn) +{ +	aim_conn_t *cur, **prev; + +	if (!deadconn || !*deadconn)	 +		return; + +	for (prev = &sess->connlist; (cur = *prev); ) { +		if (cur == *deadconn) { +			*prev = cur->next; +			break; +		} +		prev = &cur->next; +	} + +	if (!cur) +		return; /* oops */ + +	connkill_real(sess, &cur); + +	return; +} + +/** + * aim_conn_close - Close a connection + * @deadconn: Connection to close + * + * Close (but not free) a connection. + * + * This leaves everything untouched except for clearing the  + * handler list and setting the fd to -1 (used to recognize + * dead connections).  It will also remove cookies if necessary. + * + */ +void aim_conn_close(aim_conn_t *deadconn) +{ + +	if (deadconn->fd >= 3) +		closesocket(deadconn->fd); +	deadconn->fd = -1; +	if (deadconn->handlerlist) +		aim_clearhandlers(deadconn); + +	return; +} + +/** + * aim_getconn_type - Find a connection of a specific type + * @sess: Session to search + * @type: Type of connection to look for + * + * Searches for a connection of the specified type in the  + * specified session.  Returns the first connection of that + * type found. + * + * XXX except for RENDEZVOUS, all uses of this should be removed and + * use aim_conn_findbygroup() instead. + */ +aim_conn_t *aim_getconn_type(aim_session_t *sess, int type) +{ +	aim_conn_t *cur; + +	for (cur = sess->connlist; cur; cur = cur->next) { +		if ((cur->type == type) &&  +				!(cur->status & AIM_CONN_STATUS_INPROGRESS)) +			break; +	} + +	return cur; +} + +aim_conn_t *aim_getconn_type_all(aim_session_t *sess, int type) +{ +	aim_conn_t *cur; + +	for (cur = sess->connlist; cur; cur = cur->next) { +		if (cur->type == type) +			break; +	} + +	return cur; +} + +/** + * aim_cloneconn - clone an aim_conn_t + * @sess: session containing parent + * @src: connection to clone + * + * A new connection is allocated, and the values are filled in + * appropriately. Note that this function sets the new connnection's + * ->priv pointer to be equal to that of its parent: only the pointer + * is copied, not the data it points to. + * + * This function returns a pointer to the new aim_conn_t, or %NULL on + * error + */ +aim_conn_t *aim_cloneconn(aim_session_t *sess, aim_conn_t *src) +{ +	aim_conn_t *conn; + +	if (!(conn = aim_conn_getnext(sess))) +		return NULL; + +	conn->fd = src->fd; +	conn->type = src->type; +	conn->subtype = src->subtype; +	conn->seqnum = src->seqnum; +	conn->priv = src->priv; +	conn->internal = src->internal; +	conn->lastactivity = src->lastactivity; +	conn->forcedlatency = src->forcedlatency; +	conn->sessv = src->sessv; +	aim_clonehandlers(sess, conn, src); + +	if (src->inside) { +		/* +		 * XXX should clone this section as well, but since currently +		 * this function only gets called for some of that rendezvous +		 * crap, and not on SNAC connections, its probably okay for +		 * now.  +		 * +		 */ +	} + +	return conn; +} + +/** + * aim_newconn - Open a new connection + * @sess: Session to create connection in + * @type: Type of connection to create + * @dest: Host to connect to (in "host:port" syntax) + * + * Opens a new connection to the specified dest host of specified + * type, using the proxy settings if available.  If @host is %NULL, + * the connection is allocated and returned, but no connection  + * is made. + * + * FIXME: Return errors in a more sane way. + * + */ +aim_conn_t *aim_newconn(aim_session_t *sess, int type, const char *dest) +{ +	aim_conn_t *connstruct; +	guint16 port = AIM_LOGIN_PORT; +	char *host; +	int i; + +	if (!(connstruct = aim_conn_getnext(sess))) +		return NULL; + +	connstruct->sessv = (void *)sess; +	connstruct->type = type; + +	if (!dest) { /* just allocate a struct */ +		connstruct->fd = -1; +		connstruct->status = 0; +		return connstruct; +	} + +	/*  +	 * As of 23 Jul 1999, AOL now sends the port number, preceded by a  +	 * colon, in the BOS redirect.  This fatally breaks all previous  +	 * libfaims.  Bad, bad AOL. +	 * +	 * We put this here to catch every case.  +	 * +	 */ + +	for(i = 0; i < (int)strlen(dest); i++) { +		if (dest[i] == ':') { +			port = atoi(&(dest[i+1])); +			break; +		} +	} + +	host = (char *)g_malloc(i+1); +	strncpy(host, dest, i); +	host[i] = '\0'; + +	connstruct->fd = proxy_connect(host, port, NULL, NULL); + +	g_free(host); + +	return connstruct; +} + +/** + * aim_conn_setlatency - Set a forced latency value for connection + * @conn: Conn to set latency for + * @newval: Number of seconds to force between transmits + * + * Causes @newval seconds to be spent between transmits on a connection. + * + * This is my lame attempt at overcoming not understanding the rate + * limiting.  + * + * XXX: This should really be replaced with something that scales and + * backs off like the real rate limiting does. + * + */ +int aim_conn_setlatency(aim_conn_t *conn, int newval) +{ + +	if (!conn) +		return -1; + +	conn->forcedlatency = newval; +	conn->lastactivity = 0; /* reset this just to make sure */ + +	return 0; +} + +/** + * aim_session_init - Initializes a session structure + * @sess: Session to initialize + * @flags: Flags to use. Any of %AIM_SESS_FLAGS %OR'd together. + * @debuglevel: Level of debugging output (zero is least) + * + * Sets up the initial values for a session. + * + */ +void aim_session_init(aim_session_t *sess, guint32 flags, int debuglevel) +{ + +	if (!sess) +		return; + +	memset(sess, 0, sizeof(aim_session_t)); +	aim_connrst(sess); +	sess->queue_outgoing = NULL; +	sess->queue_incoming = NULL; +	aim_initsnachash(sess); +	sess->msgcookies = NULL; +	sess->snacid_next = 0x00000001; + +	sess->flags = 0; + +	sess->modlistv = NULL; + +	sess->ssi.received_data = 0; +	sess->ssi.waiting_for_ack = 0; +	sess->ssi.holding_queue = NULL; +	sess->ssi.revision = 0; +	sess->ssi.items = NULL; +	sess->ssi.timestamp = (time_t)0; + +	sess->locate.userinfo = NULL; +	sess->locate.torequest = NULL; +	sess->locate.requested = NULL; +	sess->locate.waiting_for_response = FALSE; + +	sess->icq_info = NULL; +	sess->authinfo = NULL; +	sess->emailinfo = NULL; +	sess->oft_info = NULL; + + +	/* +	 * Default to SNAC login unless XORLOGIN is explicitly set. +	 */ +	if (!(flags & AIM_SESS_FLAGS_XORLOGIN)) +		sess->flags |= AIM_SESS_FLAGS_SNACLOGIN; +	sess->flags |= flags; + +	/* +	 * This must always be set.  Default to the queue-based +	 * version for back-compatibility.   +	 */ +	aim_tx_setenqueue(sess, AIM_TX_QUEUED, NULL); + + +	/* +	 * Register all the modules for this session... +	 */ +	aim__registermodule(sess, misc_modfirst); /* load the catch-all first */ +	aim__registermodule(sess, general_modfirst); +	aim__registermodule(sess, locate_modfirst); +	aim__registermodule(sess, buddylist_modfirst); +	aim__registermodule(sess, msg_modfirst); +	aim__registermodule(sess, admin_modfirst); +	aim__registermodule(sess, bos_modfirst); +	aim__registermodule(sess, search_modfirst); +	aim__registermodule(sess, stats_modfirst); +	aim__registermodule(sess, chatnav_modfirst); +	aim__registermodule(sess, chat_modfirst); +	/* missing 0x0f - 0x12 */ +	aim__registermodule(sess, ssi_modfirst); +	/* missing 0x14 */ +	aim__registermodule(sess, icq_modfirst); +	/* missing 0x16 */ +	aim__registermodule(sess, auth_modfirst); + +	return; +} + +/** + * aim_session_kill - Deallocate a session + * @sess: Session to kill + * + */ +void aim_session_kill(aim_session_t *sess) +{ +	aim_cleansnacs(sess, -1); + +	aim_logoff(sess); + +	aim__shutdownmodules(sess); + +	return; +} + +/* + * XXX this is nearly as ugly as proxyconnect(). + */ +int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn) +{ +	fd_set fds, wfds; +	struct timeval tv; +	int res, error = ETIMEDOUT; +	aim_rxcallback_t userfunc; + +	if (!conn || (conn->fd == -1)) +		return -1; + +	if (!(conn->status & AIM_CONN_STATUS_INPROGRESS)) +		return -1; + +	FD_ZERO(&fds); +	FD_SET(conn->fd, &fds); +	FD_ZERO(&wfds); +	FD_SET(conn->fd, &wfds); +	tv.tv_sec = 0; +	tv.tv_usec = 0; + +	if ((res = select(conn->fd+1, &fds, &wfds, NULL, &tv)) == -1) { +		error = errno; +		aim_conn_close(conn); +		errno = error; +		return -1; +	} else if (res == 0) { +		return 0; /* hasn't really completed yet... */ +	}  + +	if (FD_ISSET(conn->fd, &fds) || FD_ISSET(conn->fd, &wfds)) { +		unsigned int len = sizeof(error); + +		if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) +			error = errno; +	} + +	if (error) { +		aim_conn_close(conn); +		errno = error; +		return -1; +	} + +	sock_make_blocking(conn->fd); + +	conn->status &= ~AIM_CONN_STATUS_INPROGRESS; + +	if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE))) +		userfunc(sess, NULL, conn); + +	/* Flush out the queues if there was something waiting for this conn  */ +	aim_tx_flushqueue(sess); + +	return 0; +} + +aim_session_t *aim_conn_getsess(aim_conn_t *conn) +{ + +	if (!conn) +		return NULL; + +	return (aim_session_t *)conn->sessv; +} + +/* + * aim_logoff() + * + * Closes -ALL- open connections. + * + */ +static int aim_logoff(aim_session_t *sess) +{ + +	aim_connrst(sess);  /* in case we want to connect again */ + +	return 0; + +} + +/* + * aim_flap_nop() + * + * No-op.  WinAIM 4.x sends these _every minute_ to keep + * the connection alive.   + */ +int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_frame_t *fr; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x05, 0))) +		return -ENOMEM; + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + + diff --git a/protocols/oscar/icq.c b/protocols/oscar/icq.c new file mode 100644 index 00000000..f7c02e04 --- /dev/null +++ b/protocols/oscar/icq.c @@ -0,0 +1,441 @@ +/* + * Encapsulated ICQ. + * + */ + +#include <aim.h> +#include "icq.h" + +int aim_icq_reqofflinemsgs(aim_session_t *sess) +{ +	aim_conn_t *conn; +	aim_frame_t *fr; +	aim_snacid_t snacid; +	int bslen; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) +		return -EINVAL; + +	bslen = 2 + 4 + 2 + 2; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + +	/* For simplicity, don't bother using a tlvlist */ +	aimbs_put16(&fr->data, 0x0001); +	aimbs_put16(&fr->data, bslen); + +	aimbs_putle16(&fr->data, bslen - 2); +	aimbs_putle32(&fr->data, atoi(sess->sn)); +	aimbs_putle16(&fr->data, 0x003c); /* I command thee. */ +	aimbs_putle16(&fr->data, snacid); /* eh. */ + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_icq_ackofflinemsgs(aim_session_t *sess) +{ +	aim_conn_t *conn; +	aim_frame_t *fr; +	aim_snacid_t snacid; +	int bslen; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) +		return -EINVAL; + +	bslen = 2 + 4 + 2 + 2; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + +	/* For simplicity, don't bother using a tlvlist */ +	aimbs_put16(&fr->data, 0x0001); +	aimbs_put16(&fr->data, bslen); + +	aimbs_putle16(&fr->data, bslen - 2); +	aimbs_putle32(&fr->data, atoi(sess->sn)); +	aimbs_putle16(&fr->data, 0x003e); /* I command thee. */ +	aimbs_putle16(&fr->data, snacid); /* eh. */ + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_icq_sendxmlreq(aim_session_t *sess, const char *xml) +{ +	aim_conn_t *conn; +	aim_frame_t *fr; +	aim_snacid_t snacid; +	int bslen; + +	if (!xml || !strlen(xml)) +		return -EINVAL; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) +		return -EINVAL; + +	bslen = 2 + 10 + 2 + strlen(xml) + 1; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + +	/* For simplicity, don't bother using a tlvlist */ +	aimbs_put16(&fr->data, 0x0001); +	aimbs_put16(&fr->data, bslen); + +	aimbs_putle16(&fr->data, bslen - 2); +	aimbs_putle32(&fr->data, atoi(sess->sn)); +	aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */ +	aimbs_putle16(&fr->data, snacid); /* eh. */ +	aimbs_putle16(&fr->data, 0x0998); /* shrug. */ +	aimbs_putle16(&fr->data, strlen(xml) + 1); +	aimbs_putraw(&fr->data, (guint8 *)xml, strlen(xml) + 1); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_icq_getallinfo(aim_session_t *sess, const char *uin) +{ +        aim_conn_t *conn; +        aim_frame_t *fr; +        aim_snacid_t snacid; +        int bslen; +        struct aim_icq_info *info; + +        if (!uin || uin[0] < '0' || uin[0] > '9') +                return -EINVAL; + +        if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) +                return -EINVAL; + +        bslen = 2 + 4 + 2 + 2 + 2 + 4; + +        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) +                return -ENOMEM; + +        snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); +        aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + +        /* For simplicity, don't bother using a tlvlist */ +        aimbs_put16(&fr->data, 0x0001); +        aimbs_put16(&fr->data, bslen); + +        aimbs_putle16(&fr->data, bslen - 2); +        aimbs_putle32(&fr->data, atoi(sess->sn)); +        aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */ +        aimbs_putle16(&fr->data, snacid); /* eh. */ +        aimbs_putle16(&fr->data, 0x04b2); /* shrug. */ +        aimbs_putle32(&fr->data, atoi(uin)); + +        aim_tx_enqueue(sess, fr); + +        /* Keep track of this request and the ICQ number and request ID */ +        info = g_new0(struct aim_icq_info, 1); +        info->reqid = snacid; +        info->uin = atoi(uin); +        info->next = sess->icq_info; +        sess->icq_info = info; + +        return 0; +} + +int aim_icq_getsimpleinfo(aim_session_t *sess, const char *uin) +{ +	aim_conn_t *conn; +	aim_frame_t *fr; +	aim_snacid_t snacid; +	int bslen; + +	if (!uin || uin[0] < '0' || uin[0] > '9') +		return -EINVAL; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0015))) +		return -EINVAL; + +	bslen = 2 + 4 + 2 + 2 + 2 + 4; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 4 + bslen))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0015, 0x0002, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0015, 0x0002, 0x0000, snacid); + +	/* For simplicity, don't bother using a tlvlist */ +	aimbs_put16(&fr->data, 0x0001); +	aimbs_put16(&fr->data, bslen); + +	aimbs_putle16(&fr->data, bslen - 2); +	aimbs_putle32(&fr->data, atoi(sess->sn)); +	aimbs_putle16(&fr->data, 0x07d0); /* I command thee. */ +	aimbs_putle16(&fr->data, snacid); /* eh. */ +	aimbs_putle16(&fr->data, 0x051f); /* shrug. */ +	aimbs_putle32(&fr->data, atoi(uin)); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +static void aim_icq_freeinfo(struct aim_icq_info *info) { +        int i; + +        if (!info) +                return; +        g_free(info->nick); +        g_free(info->first); +        g_free(info->last); +        g_free(info->email); +        g_free(info->homecity); +        g_free(info->homestate); +        g_free(info->homephone); +        g_free(info->homefax); +        g_free(info->homeaddr); +        g_free(info->mobile); +        g_free(info->homezip); +        g_free(info->personalwebpage); +        if (info->email2) +                for (i = 0; i < info->numaddresses; i++) +                        g_free(info->email2[i]); +        g_free(info->email2); +        g_free(info->workcity); +        g_free(info->workstate); +        g_free(info->workphone); +        g_free(info->workfax); +        g_free(info->workaddr); +        g_free(info->workzip); +        g_free(info->workcompany); +        g_free(info->workdivision); +        g_free(info->workposition); +        g_free(info->workwebpage); +        g_free(info->info); +        g_free(info); +} + +/** + * Subtype 0x0003 - Response to 0x0015/0x002, contains an ICQesque packet. + */ +static int icqresponse(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	aim_tlvlist_t *tl; +	aim_tlv_t *datatlv; +	aim_bstream_t qbs; +	guint32 ouruin; +	guint16 cmdlen, cmd, reqid; + +	if (!(tl = aim_readtlvchain(bs)) || !(datatlv = aim_gettlv(tl, 0x0001, 1))) { +		aim_freetlvchain(&tl); +		imcb_error(sess->aux_data, "corrupt ICQ response\n"); +		return 0; +	} + +	aim_bstream_init(&qbs, datatlv->value, datatlv->length); + +	cmdlen = aimbs_getle16(&qbs); +	ouruin = aimbs_getle32(&qbs); +	cmd = aimbs_getle16(&qbs); +	reqid = aimbs_getle16(&qbs); + +	if (cmd == 0x0041) { /* offline message */ +		guint16 msglen; +		struct aim_icq_offlinemsg msg; +		aim_rxcallback_t userfunc; + +		memset(&msg, 0, sizeof(msg)); + +		msg.sender = aimbs_getle32(&qbs); +		msg.year = aimbs_getle16(&qbs); +		msg.month = aimbs_getle8(&qbs); +		msg.day = aimbs_getle8(&qbs); +		msg.hour = aimbs_getle8(&qbs); +		msg.minute = aimbs_getle8(&qbs); +		msg.type = aimbs_getle16(&qbs); +		msglen = aimbs_getle16(&qbs); +		msg.msg = aimbs_getstr(&qbs, msglen); + +		if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG))) +			ret = userfunc(sess, rx, &msg); + +		g_free(msg.msg); + +	} else if (cmd == 0x0042) { +		aim_rxcallback_t userfunc; + +		if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE))) +			ret = userfunc(sess, rx); +	} else if (cmd == 0x07da) { /* information */ +		guint16 subtype; +		struct aim_icq_info *info; +		aim_rxcallback_t userfunc; + +		subtype = aimbs_getle16(&qbs); +		aim_bstream_advance(&qbs, 1); /* 0x0a */ + +		/* find another data from the same request */ +		for (info = sess->icq_info; info && (info->reqid != reqid); info = info->next); + +		if (!info) { +			info = g_new0(struct aim_icq_info, 1); +			info->reqid = reqid; +			info->next = sess->icq_info; +			sess->icq_info = info; +		} + +		switch (subtype) { +			case 0x00a0: { /* hide ip status */ +							 /* nothing */ +						 } break; +			case 0x00aa: { /* password change status */ +							 /* nothing */ +						 } break; +			case 0x00c8: { /* general and "home" information */ +							 info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->email = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->homecity = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->homestate = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->homephone = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->homefax = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->homeaddr = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->mobile = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->homezip = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->homecountry = aimbs_getle16(&qbs); +							 /* 0x0a 00 02 00 */ +							 /* 1 byte timezone? */ +							 /* 1 byte hide email flag? */ +						 } break; +			case 0x00dc: { /* personal information */ +							 info->age = aimbs_getle8(&qbs); +							 info->unknown = aimbs_getle8(&qbs); +							 info->gender = aimbs_getle8(&qbs); +							 info->personalwebpage = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->birthyear = aimbs_getle16(&qbs); +							 info->birthmonth = aimbs_getle8(&qbs); +							 info->birthday = aimbs_getle8(&qbs); +							 info->language1 = aimbs_getle8(&qbs); +							 info->language2 = aimbs_getle8(&qbs); +							 info->language3 = aimbs_getle8(&qbs); +							 /* 0x00 00 01 00 00 01 00 00 00 00 00 */ +						 } break; +			case 0x00d2: { /* work information */ +							 info->workcity = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->workstate = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->workphone = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->workfax = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->workaddr = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->workzip = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->workcountry = aimbs_getle16(&qbs); +							 info->workcompany = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->workdivision = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->workposition = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 aim_bstream_advance(&qbs, 2); /* 0x01 00 */ +							 info->workwebpage = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +						 } break; +			case 0x00e6: { /* additional personal information */ +							 info->info = aimbs_getstr(&qbs, aimbs_getle16(&qbs)-1); +						 } break; +			case 0x00eb: { /* email address(es) */ +							 int i; +							 info->numaddresses = aimbs_getle16(&qbs); +							 info->email2 = g_new0(char *, info->numaddresses); +							 for (i = 0; i < info->numaddresses; i++) { +								 info->email2[i] = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +								 if (i+1 != info->numaddresses) +									 aim_bstream_advance(&qbs, 1); /* 0x00 */ +							 } +						 } break; +			case 0x00f0: { /* personal interests */ +						 } break; +			case 0x00fa: { /* past background and current organizations */ +						 } break; +			case 0x0104: { /* alias info */ +							 info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 aim_bstream_advance(&qbs, aimbs_getle16(&qbs)); +							 /* email address? */ +							 /* Then 0x00 02 00 */ +						 } break; +			case 0x010e: { /* unknown */ +							 /* 0x00 00 */ +						 } break; + +			case 0x019a: { /* simple info */ +							 aim_bstream_advance(&qbs, 2); +							 info->uin = aimbs_getle32(&qbs); +							 info->nick = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->first = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->last = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 info->email = aimbs_getstr(&qbs, aimbs_getle16(&qbs)); +							 /* Then 0x00 02 00 00 00 00 00 */ +						 } break; +		} /* End switch statement */ + + +		if (!(snac->flags & 0x0001)) { +			if (subtype != 0x0104) +				if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_INFO))) +					ret = userfunc(sess, rx, info); + +			/* Bitlbee - not supported, yet  +			if (info->uin && info->nick) +				if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_ALIAS))) +					ret = userfunc(sess, rx, info); +			*/ + +			if (sess->icq_info == info) { +				sess->icq_info = info->next; +			} else { +				struct aim_icq_info *cur; +				for (cur=sess->icq_info; (cur->next && (cur->next!=info)); cur=cur->next); +				if (cur->next) +					cur->next = cur->next->next; +			} +			aim_icq_freeinfo(info); +		} +	} + +	aim_freetlvchain(&tl); + +	return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0003) +		return icqresponse(sess, mod, rx, snac, bs); + +	return 0; +} + +int icq_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x0015; +	mod->version = 0x0001; +	mod->toolid = 0x0110; +	mod->toolversion = 0x047c; +	mod->flags = 0; +	strncpy(mod->name, "icq", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} + + diff --git a/protocols/oscar/icq.h b/protocols/oscar/icq.h new file mode 100644 index 00000000..c496f39c --- /dev/null +++ b/protocols/oscar/icq.h @@ -0,0 +1,98 @@ +#ifndef __OSCAR_ICQ_H__ +#define __OSCAR_ICQ_H__ + +#define AIM_CB_FAM_ICQ 0x0015 + +/* + * SNAC Family: ICQ + * + * Most of these are actually special. + */  +#define AIM_CB_ICQ_ERROR 0x0001 +#define AIM_CB_ICQ_OFFLINEMSG 0x00f0 +#define AIM_CB_ICQ_OFFLINEMSGCOMPLETE 0x00f1 +#define AIM_CB_ICQ_SIMPLEINFO 0x00f2 +#define AIM_CB_ICQ_INFO 0x00f2 /* just transitional */ +#define AIM_CB_ICQ_DEFAULT 0xffff + +struct aim_icq_offlinemsg { +	guint32 sender; +	guint16 year; +	guint8 month, day, hour, minute; +	guint16 type; +	char *msg; +}; + +struct aim_icq_simpleinfo { +	guint32 uin; +	char *nick; +	char *first; +	char *last; +	char *email; +}; + +struct aim_icq_info { +        gushort reqid; + +        /* simple */ +        guint32 uin; + +        /* general and "home" information (0x00c8) */ +        char *nick; +        char *first; +        char *last; +        char *email; +        char *homecity; +        char *homestate; +        char *homephone; +        char *homefax; +        char *homeaddr; +        char *mobile; +        char *homezip; +        gushort homecountry; +/*      guchar timezone; +        guchar hideemail; */ + +        /* personal (0x00dc) */ +        guchar age; +        guchar unknown; +        guchar gender; +        char *personalwebpage; +        gushort birthyear; +        guchar birthmonth; +        guchar birthday; +        guchar language1; +        guchar language2; +        guchar language3; + +        /* work (0x00d2) */ +        char *workcity; +        char *workstate; +        char *workphone; +        char *workfax; +        char *workaddr; +        char *workzip; +        gushort workcountry; +        char *workcompany; +        char *workdivision; +        char *workposition; +        char *workwebpage; + +        /* additional personal information (0x00e6) */ +        char *info; + +        /* email (0x00eb) */ +        gushort numaddresses; +        char **email2; + +        /* we keep track of these in a linked list because we're 1337 */ +        struct aim_icq_info *next; +}; + + +int aim_icq_reqofflinemsgs(aim_session_t *sess); +int aim_icq_ackofflinemsgs(aim_session_t *sess); +int aim_icq_getallinfo(aim_session_t *sess, const char *uin); +int aim_icq_getsimpleinfo(aim_session_t *sess, const char *uin); + +#endif /* __OSCAR_ICQ_H__ */ diff --git a/protocols/oscar/im.c b/protocols/oscar/im.c new file mode 100644 index 00000000..4169ea4d --- /dev/null +++ b/protocols/oscar/im.c @@ -0,0 +1,2141 @@ +/* + *  aim_im.c + * + *  The routines for sending/receiving Instant Messages. + * + *  Note the term ICBM (Inter-Client Basic Message) which blankets + *  all types of genericly routed through-server messages.  Within + *  the ICBM types (family 4), a channel is defined.  Each channel + *  represents a different type of message.  Channel 1 is used for + *  what would commonly be called an "instant message".  Channel 2 + *  is used for negotiating "rendezvous".  These transactions end in + *  something more complex happening, such as a chat invitation, or + *  a file transfer. + * + *  In addition to the channel, every ICBM contains a cookie.  For + *  standard IMs, these are only used for error messages.  However, + *  the more complex rendezvous messages make suitably more complex + *  use of this field. + * + */ + +#include <aim.h> +#include "im.h" +#include "info.h" + +/* + * Takes a msghdr (and a length) and returns a client type + * code.  Note that this is *only a guess* and has a low likelihood + * of actually being accurate. + * + * Its based on experimental data, with the help of Eric Warmenhoven + * who seems to have collected a wide variety of different AIM clients. + * + * + * Heres the current collection: + *  0501 0003 0101 0101 01       AOL Mobile Communicator, WinAIM 1.0.414 + *  0501 0003 0101 0201 01       WinAIM 2.0.847, 2.1.1187, 3.0.1464,  + *                                      4.3.2229, 4.4.2286 + *  0501 0004 0101 0102 0101     WinAIM 4.1.2010, libfaim (right here) + *  0501 0001 0101 01            AOL v6.0, CompuServe 2000 v6.0, any + *                                      TOC client + * + * Note that in this function, only the feature bytes are tested, since + * the rest will always be the same. + * + */ +guint16 aim_fingerprintclient(guint8 *msghdr, int len) +{ +	static const struct { +		guint16 clientid; +		int len; +		guint8 data[10]; +	} fingerprints[] = { +		/* AOL Mobile Communicator, WinAIM 1.0.414 */ +		{ AIM_CLIENTTYPE_MC,  +		  3, {0x01, 0x01, 0x01}}, + +		/* WinAIM 2.0.847, 2.1.1187, 3.0.1464, 4.3.2229, 4.4.2286 */ +		{ AIM_CLIENTTYPE_WINAIM,  +		  3, {0x01, 0x01, 0x02}}, + +		/* WinAIM 4.1.2010, libfaim */ +		{ AIM_CLIENTTYPE_WINAIM41, +		  4, {0x01, 0x01, 0x01, 0x02}}, + +		/* AOL v6.0, CompuServe 2000 v6.0, any TOC client */ +		{ AIM_CLIENTTYPE_AOL_TOC, +		  1, {0x01}}, + +		{ 0, 0} +	}; +	int i; + +	if (!msghdr || (len <= 0)) +		return AIM_CLIENTTYPE_UNKNOWN; + +	for (i = 0; fingerprints[i].len; i++) { +		if (fingerprints[i].len != len) +			continue; +		if (memcmp(fingerprints[i].data, msghdr, fingerprints[i].len) == 0) +			return fingerprints[i].clientid; +	} + +	return AIM_CLIENTTYPE_UNKNOWN; +} + +/* This should be endian-safe now... but who knows... */ +guint16 aim_iconsum(const guint8 *buf, int buflen) +{ +	guint32 sum; +	int i; + +	for (i = 0, sum = 0; i + 1 < buflen; i += 2) +		sum += (buf[i+1] << 8) + buf[i]; +	if (i < buflen) +		sum += buf[i]; + +	sum = ((sum & 0xffff0000) >> 16) + (sum & 0x0000ffff); + +	return (guint16)sum; +} + +/* + * Send an ICBM (instant message).   + * + * + * Possible flags: + *   AIM_IMFLAGS_AWAY  -- Marks the message as an autoresponse + *   AIM_IMFLAGS_ACK   -- Requests that the server send an ack + *                        when the message is received (of type 0x0004/0x000c) + *   AIM_IMFLAGS_OFFLINE--If destination is offline, store it until they are + *                        online (probably ICQ only). + *   AIM_IMFLAGS_UNICODE--Instead of ASCII7, the passed message is + *                        made up of UNICODE duples.  If you set + *                        this, you'd better be damn sure you know + *                        what you're doing. + *   AIM_IMFLAGS_ISO_8859_1 -- The message contains the ASCII8 subset + *                        known as ISO-8859-1.   + * + * Generally, you should use the lowest encoding possible to send + * your message.  If you only use basic punctuation and the generic + * Latin alphabet, use ASCII7 (no flags).  If you happen to use non-ASCII7 + * characters, but they are all clearly defined in ISO-8859-1, then  + * use that.  Keep in mind that not all characters in the PC ASCII8 + * character set are defined in the ISO standard. For those cases (most + * notably when the (r) symbol is used), you must use the full UNICODE + * encoding for your message.  In UNICODE mode, _all_ characters must + * occupy 16bits, including ones that are not special.  (Remember that + * the first 128 UNICODE symbols are equivelent to ASCII7, however they + * must be prefixed with a zero high order byte.) + * + * I strongly discourage the use of UNICODE mode, mainly because none + * of the clients I use can parse those messages (and besides that, + * wchars are difficult and non-portable to handle in most UNIX environments). + * If you really need to include special characters, use the HTML UNICODE  + * entities.  These are of the form ߪ where 2026 is the hex  + * representation of the UNICODE index (in this case, UNICODE  + * "Horizontal Ellipsis", or 133 in in ASCII8). + * + * Implementation note:  Since this is one of the most-used functions + * in all of libfaim, it is written with performance in mind.  As such, + * it is not as clear as it could be in respect to how this message is + * supposed to be layed out. Most obviously, tlvlists should be used  + * instead of writing out the bytes manually.  + * + * XXX more precise verification that we never send SNACs larger than 8192 + * XXX check SNAC size for multipart + * + */ +int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args) +{ +	static const guint8 deffeatures[] = { +		0x01, 0x01, 0x01, 0x02 +	}; +	aim_conn_t *conn; +	int i, msgtlvlen; +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) +		return -EINVAL; + +	if (!args) +		return -EINVAL; + +	if (args->flags & AIM_IMFLAGS_MULTIPART) { +		if (args->mpmsg->numparts <= 0) +			return -EINVAL; +	} else { +		if (!args->msg || (args->msglen <= 0)) +			return -EINVAL; + +		if (args->msglen >= MAXMSGLEN) +			return -E2BIG; +	} + +	/* Painfully calculate the size of the message TLV */ +	msgtlvlen = 1 + 1; /* 0501 */ + +	if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) +		msgtlvlen += 2 + args->featureslen; +	else +		msgtlvlen += 2 + sizeof(deffeatures); + +	if (args->flags & AIM_IMFLAGS_MULTIPART) { +		aim_mpmsg_section_t *sec; + +		for (sec = args->mpmsg->parts; sec; sec = sec->next) { +			msgtlvlen += 2 /* 0101 */ + 2 /* block len */; +			msgtlvlen += 4 /* charset */ + sec->datalen; +		} + +	} else { +		msgtlvlen += 2 /* 0101 */ + 2 /* block len */; +		msgtlvlen += 4 /* charset */ + args->msglen; +	} + + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, msgtlvlen+128))) +		return -ENOMEM; + +	/* XXX should be optional */	 +	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, args->destsn, strlen(args->destsn)+1); +	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + +	/*  +	 * Generate a random message cookie  +	 * +	 * We could cache these like we do SNAC IDs.  (In fact, it  +	 * might be a good idea.)  In the message error functions,  +	 * the 8byte message cookie is returned as well as the  +	 * SNAC ID. +	 * +	 */ +	for (i = 0; i < 8; i++) +		aimbs_put8(&fr->data, (guint8) rand()); + +	/* +	 * Channel ID +	 */ +	aimbs_put16(&fr->data, 0x0001); + +	/* +	 * Destination SN (prepended with byte length) +	 */ +	aimbs_put8(&fr->data, strlen(args->destsn)); +	aimbs_putraw(&fr->data, (guint8 *)args->destsn, strlen(args->destsn)); + +	/* +	 * Message TLV (type 2). +	 */ +	aimbs_put16(&fr->data, 0x0002); +	aimbs_put16(&fr->data, msgtlvlen); + +	/* +	 * Features  +	 * +	 */ +	aimbs_put8(&fr->data, 0x05); +	aimbs_put8(&fr->data, 0x01); + +	if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) { +		aimbs_put16(&fr->data, args->featureslen); +		aimbs_putraw(&fr->data, args->features, args->featureslen); +	} else { +		aimbs_put16(&fr->data, sizeof(deffeatures)); +		aimbs_putraw(&fr->data, deffeatures, sizeof(deffeatures)); +	} + +	if (args->flags & AIM_IMFLAGS_MULTIPART) { +		aim_mpmsg_section_t *sec; + +		for (sec = args->mpmsg->parts; sec; sec = sec->next) { +			aimbs_put16(&fr->data, 0x0101); +			aimbs_put16(&fr->data, sec->datalen + 4); +			aimbs_put16(&fr->data, sec->charset); +			aimbs_put16(&fr->data, sec->charsubset); +			aimbs_putraw(&fr->data, sec->data, sec->datalen); +		} + +	} else { + +		aimbs_put16(&fr->data, 0x0101); + +		/*  +		 * Message block length. +		 */ +		aimbs_put16(&fr->data, args->msglen + 0x04); + +		/* +		 * Character set. +		 */ +		if (args->flags & AIM_IMFLAGS_CUSTOMCHARSET) { + +			aimbs_put16(&fr->data, args->charset); +			aimbs_put16(&fr->data, args->charsubset); + +		} else { +			if (args->flags & AIM_IMFLAGS_UNICODE) +				aimbs_put16(&fr->data, 0x0002); +			else if (args->flags & AIM_IMFLAGS_ISO_8859_1) +				aimbs_put16(&fr->data, 0x0003); +			else +				aimbs_put16(&fr->data, 0x0000); + +			aimbs_put16(&fr->data, 0x0000); +		} + +		/* +		 * Message.  Not terminated. +		 */ +		aimbs_putraw(&fr->data, (guint8 *)args->msg, args->msglen); +	} + +	/* +	 * Set the Request Acknowledge flag.   +	 */ +	if (args->flags & AIM_IMFLAGS_ACK) { +		aimbs_put16(&fr->data, 0x0003); +		aimbs_put16(&fr->data, 0x0000); +	} + +	/* +	 * Set the Autoresponse flag. +	 */ +	if (args->flags & AIM_IMFLAGS_AWAY) { +		aimbs_put16(&fr->data, 0x0004); +		aimbs_put16(&fr->data, 0x0000); +	} + +	if (args->flags & AIM_IMFLAGS_OFFLINE) { +		aimbs_put16(&fr->data, 0x0006); +		aimbs_put16(&fr->data, 0x0000); +	} + +	/* +	 * Set the I HAVE A REALLY PURTY ICON flag. +	 */ +	if (args->flags & AIM_IMFLAGS_HASICON) { +		aimbs_put16(&fr->data, 0x0008); +		aimbs_put16(&fr->data, 0x000c); +		aimbs_put32(&fr->data, args->iconlen); +		aimbs_put16(&fr->data, 0x0001); +		aimbs_put16(&fr->data, args->iconsum); +		aimbs_put32(&fr->data, args->iconstamp); +	} + +	/* +	 * Set the Buddy Icon Requested flag. +	 */ +	if (args->flags & AIM_IMFLAGS_BUDDYREQ) { +		aimbs_put16(&fr->data, 0x0009); +		aimbs_put16(&fr->data, 0x0000); +	} + +	aim_tx_enqueue(sess, fr); + +	if (!(sess->flags & AIM_SESS_FLAGS_DONTTIMEOUTONICBM)) +		aim_cleansnacs(sess, 60); /* clean out SNACs over 60sec old */ + +	return 0; +} + +/* + * Simple wrapper for aim_send_im_ext()  + * + * You cannot use aim_send_im if you need the HASICON flag.  You must + * use aim_send_im_ext directly for that. + * + * aim_send_im also cannot be used if you require UNICODE messages, because + * that requires an explicit message length.  Use aim_send_im_ext(). + * + */ +int aim_send_im(aim_session_t *sess, const char *destsn, guint16 flags, const char *msg) +{ +	struct aim_sendimext_args args; + +	args.destsn = destsn; +	args.flags = flags; +	args.msg = msg; +	args.msglen = strlen(msg); + +	/* Make these don't get set by accident -- they need aim_send_im_ext */ +	args.flags &= ~(AIM_IMFLAGS_CUSTOMFEATURES | AIM_IMFLAGS_HASICON | AIM_IMFLAGS_MULTIPART); + +	return aim_send_im_ext(sess, &args); +} + +/* + * This is also performance sensitive. (If you can believe it...) + * + */ +int aim_send_icon(aim_session_t *sess, const char *sn, const guint8 *icon, int iconlen, time_t stamp, guint16 iconsum) +{ +	aim_conn_t *conn; +	int i; +	guint8 ck[8]; +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) +		return -EINVAL; + +	if (!sn || !icon || (iconlen <= 0) || (iconlen >= MAXICONLEN)) +		return -EINVAL; + +	for (i = 0; i < 8; i++) +		aimutil_put8(ck+i, (guint8) rand()); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn)+2+2+2+8+16+2+2+2+2+2+2+2+4+4+4+iconlen+strlen(AIM_ICONIDENT)+2+2))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + +	/* +	 * Cookie +	 */ +	aimbs_putraw(&fr->data, ck, 8); + +	/* +	 * Channel (2) +	 */ +	aimbs_put16(&fr->data, 0x0002); + +	/* +	 * Dest sn +	 */ +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + +	/* +	 * TLV t(0005) +	 * +	 * Encompasses everything below. +	 */ +	aimbs_put16(&fr->data, 0x0005); +	aimbs_put16(&fr->data, 2+8+16+6+4+4+iconlen+4+4+4+strlen(AIM_ICONIDENT)); + +	aimbs_put16(&fr->data, 0x0000); +	aimbs_putraw(&fr->data, ck, 8); +	aim_putcap(&fr->data, AIM_CAPS_BUDDYICON); + +	/* TLV t(000a) */ +	aimbs_put16(&fr->data, 0x000a); +	aimbs_put16(&fr->data, 0x0002); +	aimbs_put16(&fr->data, 0x0001); + +	/* TLV t(000f) */ +	aimbs_put16(&fr->data, 0x000f); +	aimbs_put16(&fr->data, 0x0000); + +	/* TLV t(2711) */ +	aimbs_put16(&fr->data, 0x2711); +	aimbs_put16(&fr->data, 4+4+4+iconlen+strlen(AIM_ICONIDENT)); +	aimbs_put16(&fr->data, 0x0000); +	aimbs_put16(&fr->data, iconsum); +	aimbs_put32(&fr->data, iconlen); +	aimbs_put32(&fr->data, stamp); +	aimbs_putraw(&fr->data, icon, iconlen); +	aimbs_putraw(&fr->data, (guint8 *)AIM_ICONIDENT, strlen(AIM_ICONIDENT)); + +	/* TLV t(0003) */ +	aimbs_put16(&fr->data, 0x0003); +	aimbs_put16(&fr->data, 0x0000); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * This only works for ICQ 2001b (thats 2001 not 2000).  Better, only + * send it to clients advertising the RTF capability.  In fact, if you send + * it to a client that doesn't support that capability, the server will gladly + * bounce it back to you. + * + * You'd think this would be in icq.c, but, well, I'm trying to stick with + * the one-group-per-file scheme as much as possible.  This could easily + * be an exception, since Rendezvous IMs are external of the Oscar core,  + * and therefore are undefined.  Really I just need to think of a good way to + * make an interface similar to what AOL actually uses.  But I'm not using COM. + * + */ +int aim_send_rtfmsg(aim_session_t *sess, struct aim_sendrtfmsg_args *args) +{ +	const char rtfcap[] = {"{97B12751-243C-4334-AD22-D6ABF73F1492}"}; /* AIM_CAPS_ICQRTF capability in string form */ +	aim_conn_t *conn; +	int i; +	guint8 ck[8]; +	aim_frame_t *fr; +	aim_snacid_t snacid; +	int servdatalen; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) +		return -EINVAL; + +	if (!args || !args->destsn || !args->rtfmsg) +		return -EINVAL; + +	servdatalen = 2+2+16+2+4+1+2  +  2+2+4+4+4  +  2+4+2+strlen(args->rtfmsg)+1  +  4+4+4+strlen(rtfcap)+1; + +	for (i = 0; i < 8; i++) +		aimutil_put8(ck+i, (guint8) rand()); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+128+servdatalen))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + +	/* +	 * Cookie +	 */ +	aimbs_putraw(&fr->data, ck, 8); + +	/* +	 * Channel (2) +	 */ +	aimbs_put16(&fr->data, 0x0002); + +	/* +	 * Dest sn +	 */ +	aimbs_put8(&fr->data, strlen(args->destsn)); +	aimbs_putraw(&fr->data, (guint8 *)args->destsn, strlen(args->destsn)); + +	/* +	 * TLV t(0005) +	 * +	 * Encompasses everything below. +	 */ +	aimbs_put16(&fr->data, 0x0005); +	aimbs_put16(&fr->data, 2+8+16  +  2+2+2  +  2+2  +  2+2+servdatalen); + +	aimbs_put16(&fr->data, 0x0000); +	aimbs_putraw(&fr->data, ck, 8); +	aim_putcap(&fr->data, AIM_CAPS_ICQSERVERRELAY); + +	/* +	 * t(000a) l(0002) v(0001) +	 */ +	aimbs_put16(&fr->data, 0x000a); +	aimbs_put16(&fr->data, 0x0002); +	aimbs_put16(&fr->data, 0x0001); + +	/* +	 * t(000f) l(0000) v() +	 */ +	aimbs_put16(&fr->data, 0x000f); +	aimbs_put16(&fr->data, 0x0000); + +	/* +	 * Service Data TLV +	 */ +	aimbs_put16(&fr->data, 0x2711); +	aimbs_put16(&fr->data, servdatalen); + +	aimbs_putle16(&fr->data, 11 + 16 /* 11 + (sizeof CLSID) */); +	aimbs_putle16(&fr->data, 9); +	aim_putcap(&fr->data, AIM_CAPS_EMPTY); +	aimbs_putle16(&fr->data, 0); +	aimbs_putle32(&fr->data, 0); +	aimbs_putle8(&fr->data, 0); +	aimbs_putle16(&fr->data, 0x03ea); /* trid1 */ + +	aimbs_putle16(&fr->data, 14); +	aimbs_putle16(&fr->data, 0x03eb); /* trid2 */ +	aimbs_putle32(&fr->data, 0); +	aimbs_putle32(&fr->data, 0); +	aimbs_putle32(&fr->data, 0); + +	aimbs_putle16(&fr->data, 0x0001); +	aimbs_putle32(&fr->data, 0); +	aimbs_putle16(&fr->data, strlen(args->rtfmsg)+1); +	aimbs_putraw(&fr->data, (guint8 *)args->rtfmsg, strlen(args->rtfmsg)+1); + +	aimbs_putle32(&fr->data, args->fgcolor); +	aimbs_putle32(&fr->data, args->bgcolor); +	aimbs_putle32(&fr->data, strlen(rtfcap)+1); +	aimbs_putraw(&fr->data, (guint8 *)rtfcap, strlen(rtfcap)+1); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_request_directim(aim_session_t *sess, const char *destsn, guint8 *ip, guint16 port, guint8 *ckret) +{ +	aim_conn_t *conn; +	guint8 ck[8]; +	aim_frame_t *fr; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL, *itl = NULL; +	int hdrlen, i; +	guint8 *hdr; +	aim_bstream_t hdrbs; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 256+strlen(destsn)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + +	/*  +	 * Generate a random message cookie  +	 * +	 * This cookie needs to be alphanumeric and NULL-terminated to be  +	 * TOC-compatible. +	 * +	 * XXX have I mentioned these should be generated in msgcookie.c? +	 * +	 */ +	for (i = 0; i < 7; i++) +	       	ck[i] = 0x30 + ((guint8) rand() % 10); +	ck[7] = '\0'; + +	if (ckret) +		memcpy(ckret, ck, 8); + +	/* Cookie */ +	aimbs_putraw(&fr->data, ck, 8); + +	/* Channel */ +	aimbs_put16(&fr->data, 0x0002); + +	/* Destination SN */ +	aimbs_put8(&fr->data, strlen(destsn)); +	aimbs_putraw(&fr->data, (guint8 *)destsn, strlen(destsn)); + +	aim_addtlvtochain_noval(&tl, 0x0003); + +	hdrlen = 2+8+16+6+8+6+4; +	hdr = g_malloc(hdrlen); +	aim_bstream_init(&hdrbs, hdr, hdrlen); + +	aimbs_put16(&hdrbs, 0x0000); +	aimbs_putraw(&hdrbs, ck, 8); +	aim_putcap(&hdrbs, AIM_CAPS_IMIMAGE); + +	aim_addtlvtochain16(&itl, 0x000a, 0x0001); +	aim_addtlvtochain_raw(&itl, 0x0003, 4, ip); +	aim_addtlvtochain16(&itl, 0x0005, port); +	aim_addtlvtochain_noval(&itl, 0x000f); +	 +	aim_writetlvchain(&hdrbs, &itl); + +	aim_addtlvtochain_raw(&tl, 0x0005, aim_bstream_curpos(&hdrbs), hdr); + +	aim_writetlvchain(&fr->data, &tl); + +	g_free(hdr); +	aim_freetlvchain(&itl); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_request_sendfile(aim_session_t *sess, const char *sn, const char *filename, guint16 numfiles, guint32 totsize, guint8 *ip, guint16 port, guint8 *ckret) +{ +	aim_conn_t *conn; +	int i; +	guint8 ck[8]; +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) +		return -EINVAL; + +	if (!sn || !filename) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn)+2+2+2+8+16+6+8+6+4+2+2+2+2+4+strlen(filename)+4))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + +	for (i = 0; i < 7; i++) +		aimutil_put8(ck+i, 0x30 + ((guint8) rand() % 10)); +	ck[7] = '\0'; + +	if (ckret) +		memcpy(ckret, ck, 8); + +	/* +	 * Cookie +	 */ +	aimbs_putraw(&fr->data, ck, 8); + +	/* +	 * Channel (2) +	 */ +	aimbs_put16(&fr->data, 0x0002); + +	/* +	 * Dest sn +	 */ +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + +	/* +	 * TLV t(0005) +	 * +	 * Encompasses everything below. Gee. +	 */ +	aimbs_put16(&fr->data, 0x0005); +	aimbs_put16(&fr->data, 2+8+16+6+8+6+4+2+2+2+2+4+strlen(filename)+4); + +	aimbs_put16(&fr->data, 0x0000); +	aimbs_putraw(&fr->data, ck, 8); +	aim_putcap(&fr->data, AIM_CAPS_SENDFILE); + +	/* TLV t(000a) */ +	aimbs_put16(&fr->data, 0x000a); +	aimbs_put16(&fr->data, 0x0002); +	aimbs_put16(&fr->data, 0x0001); + +	/* TLV t(0003) (IP) */ +	aimbs_put16(&fr->data, 0x0003); +	aimbs_put16(&fr->data, 0x0004); +	aimbs_putraw(&fr->data, ip, 4); + +	/* TLV t(0005) (port) */ +	aimbs_put16(&fr->data, 0x0005); +	aimbs_put16(&fr->data, 0x0002); +	aimbs_put16(&fr->data, port); + +	/* TLV t(000f) */ +	aimbs_put16(&fr->data, 0x000f); +	aimbs_put16(&fr->data, 0x0000); + +	/* TLV t(2711) */ +	aimbs_put16(&fr->data, 0x2711); +	aimbs_put16(&fr->data, 2+2+4+strlen(filename)+4); + +	/* ? */ +	aimbs_put16(&fr->data, 0x0001); +	aimbs_put16(&fr->data, numfiles); +	aimbs_put32(&fr->data, totsize); +	aimbs_putraw(&fr->data, (guint8 *)filename, strlen(filename)); + +	/* ? */ +	aimbs_put32(&fr->data, 0x00000000); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/** + * Request the status message of the given ICQ user. + * + * @param sess The oscar session. + * @param sn The UIN of the user of whom you wish to request info. + * @param type The type of info you wish to request.  This should be the current  + *        state of the user, as one of the AIM_ICQ_STATE_* defines. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_send_im_ch2_geticqmessage(aim_session_t *sess, const char *sn, int type) +{ +	aim_conn_t *conn; +	int i; +	guint8 ck[8]; +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004)) || !sn) +		return -EINVAL; + +	for (i = 0; i < 8; i++) +		aimutil_put8(ck+i, (guint8) rand()); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sn) + 4+0x5e + 4))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid); + +	/* Cookie */ +	aimbs_putraw(&fr->data, ck, 8); + +	/* Channel (2) */ +	aimbs_put16(&fr->data, 0x0002); + +	/* Dest sn */ +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + +	/* TLV t(0005) - Encompasses almost everything below. */ +	aimbs_put16(&fr->data, 0x0005); /* T */ +	aimbs_put16(&fr->data, 0x005e); /* L */ +	{ /* V */ +		aimbs_put16(&fr->data, 0x0000); + +		/* Cookie */ +		aimbs_putraw(&fr->data, ck, 8); + +		/* Put the 16 byte server relay capability */ +		aim_putcap(&fr->data, AIM_CAPS_ICQSERVERRELAY); + +		/* TLV t(000a) */ +		aimbs_put16(&fr->data, 0x000a); +		aimbs_put16(&fr->data, 0x0002); +		aimbs_put16(&fr->data, 0x0001); + +		/* TLV t(000f) */ +		aimbs_put16(&fr->data, 0x000f); +		aimbs_put16(&fr->data, 0x0000); + +		/* TLV t(2711) */ +		aimbs_put16(&fr->data, 0x2711); +		aimbs_put16(&fr->data, 0x0036); +		{ /* V */ +			aimbs_putle16(&fr->data, 0x001b); /* L */ +			aimbs_putle16(&fr->data, 0x0008); /* AAA - Protocol version */ +			aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ +			aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ +			aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ +			aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ +			aimbs_putle16(&fr->data, 0x0000); /* Unknown */ +			aimbs_putle16(&fr->data, 0x0003); /* Client features? */ +			aimbs_putle16(&fr->data, 0x0000); /* Unknown */ +			aimbs_putle8(&fr->data, 0x00); /* Unkizown */ +			aimbs_putle16(&fr->data, 0xffff); /* Sequence number?  XXX - This should decrement by 1 with each request */ + +			aimbs_putle16(&fr->data, 0x000e); /* L */ +			aimbs_putle16(&fr->data, 0xffff); /* Sequence number?  XXX - This should decrement by 1 with each request */ +			aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ +			aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ +			aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ + +			/* The type of status message being requested */ +			if (type & AIM_ICQ_STATE_CHAT) +				aimbs_putle16(&fr->data, 0x03ec); +			else if(type & AIM_ICQ_STATE_DND) +				aimbs_putle16(&fr->data, 0x03eb); +			else if(type & AIM_ICQ_STATE_OUT) +				aimbs_putle16(&fr->data, 0x03ea); +			else if(type & AIM_ICQ_STATE_BUSY) +				aimbs_putle16(&fr->data, 0x03e9); +			else if(type & AIM_ICQ_STATE_AWAY) +				aimbs_putle16(&fr->data, 0x03e8); + +			aimbs_putle16(&fr->data, 0x0000); /* Status? */ +			aimbs_putle16(&fr->data, 0x0001); /* Priority of this message? */ +			aimbs_putle16(&fr->data, 0x0001); /* L? */ +			aimbs_putle8(&fr->data, 0x00); /* Null termination? */ +		} /* End TLV t(2711) */ +	} /* End TLV t(0005) */ + +	/* TLV t(0003) */ +	aimbs_put16(&fr->data, 0x0003); +	aimbs_put16(&fr->data, 0x0000); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/** + * answers status message requests + * @param sess the oscar session + * @param sender the guy whos asking + * @param cookie message id which we are answering for + * @param message away message + * @param state our current away state the way icq requests it (0xE8 for away, 0xE9 occupied, ...) + * @return 0 if no error + */ +int aim_send_im_ch2_statusmessage(aim_session_t *sess, const char *sender, const guint8 *cookie, +        const char *message, const guint8 state, const guint16 dc) +{ +    aim_conn_t *conn; +    aim_frame_t *fr; +    aim_snacid_t snacid; + +    if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) +                return -EINVAL; +         +    if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02,  +					10+8+2+1+strlen(sender)+2+0x1d+0x10+9+strlen(message)+1))) +                return -ENOMEM; + +    snacid = aim_cachesnac(sess, 0x0004, 0x000b, 0x0000, NULL, 0); +    aim_putsnac(&fr->data, 0x0004, 0x000b, 0x0000, snacid); +     +    aimbs_putraw(&fr->data, cookie, 8); +     +    aimbs_put16(&fr->data, 0x0002); /* channel */ +    aimbs_put8(&fr->data, strlen(sender)); +    aimbs_putraw(&fr->data, (guint8 *)sender, strlen(sender)); + +    aimbs_put16(&fr->data, 0x0003); /* reason: channel specific */ + +    aimbs_putle16(&fr->data, 0x001b); /* length of data SEQ1 */ +    aimbs_putle16(&fr->data, 0x0008); /* protocol version */ + +    aimbs_putle32(&fr->data, 0x0000); /* no plugin -> 16 times 0x00 */  +    aimbs_putle32(&fr->data, 0x0000);  +    aimbs_putle32(&fr->data, 0x0000);  +    aimbs_putle32(&fr->data, 0x0000); + +    aimbs_putle16(&fr->data, 0x0000); /* unknown */ +    aimbs_putle32(&fr->data, 0x0003); /* client features */ +    aimbs_putle8(&fr->data, 0x00); /* unknown */ +    aimbs_putle16(&fr->data, dc); /* Sequence number?  XXX - This should decrement by 1 with each request */ +    /* end of SEQ1 */ + +    aimbs_putle16(&fr->data, 0x000e); /* Length of SEQ2 */ +    aimbs_putle16(&fr->data, dc); /* Sequence number? same as above +                                       * XXX - This should decrement by 1 with each request */ +    aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ +    aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ +    aimbs_putle32(&fr->data, 0x00000000); /* Unknown */ +    /* end of SEQ2 */ + +    /* now for the real fun */ +    aimbs_putle8(&fr->data, state); /* away state */ +    aimbs_putle8(&fr->data, 0x03); /* msg-flag: 03 for states */ +    aimbs_putle16(&fr->data, 0x0000); /* status code ? */ +    aimbs_putle16(&fr->data, 0x0000); /* priority code */ +    aimbs_putle16(&fr->data, strlen(message) + 1); /* message length + termination */ +    aimbs_putraw(&fr->data, (guint8 *) message, strlen(message) + 1); /* null terminated string */ +     +    aim_tx_enqueue(sess, fr); + + +    return 0; +} + + +static int outgoingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int i, ret = 0; +	aim_rxcallback_t userfunc; +	guint8 cookie[8]; +	guint16 channel; +	aim_tlvlist_t *tlvlist; +	char *sn; +	int snlen; +	guint16 icbmflags = 0; +	guint8 flag1 = 0, flag2 = 0; +	char *msg = NULL; +	aim_tlv_t *msgblock; + +	/* ICBM Cookie. */ +	for (i = 0; i < 8; i++) +		cookie[i] = aimbs_get8(bs); + +	/* Channel ID */ +	channel = aimbs_get16(bs); + +	if (channel != 0x01) { +		imcb_error(sess->aux_data, "icbm: ICBM received on unsupported channel.  Ignoring."); +		return 0; +	} + +	snlen = aimbs_get8(bs); +	sn = aimbs_getstr(bs, snlen); + +	tlvlist = aim_readtlvchain(bs); + +	if (aim_gettlv(tlvlist, 0x0003, 1)) +		icbmflags |= AIM_IMFLAGS_ACK; +	if (aim_gettlv(tlvlist, 0x0004, 1)) +		icbmflags |= AIM_IMFLAGS_AWAY; + +	if ((msgblock = aim_gettlv(tlvlist, 0x0002, 1))) { +		aim_bstream_t mbs; +		int featurelen, msglen; + +		aim_bstream_init(&mbs, msgblock->value, msgblock->length); + +		aimbs_get8(&mbs); +		aimbs_get8(&mbs); +		for (featurelen = aimbs_get16(&mbs); featurelen; featurelen--) +			aimbs_get8(&mbs); +		aimbs_get8(&mbs); +		aimbs_get8(&mbs); + +		msglen = aimbs_get16(&mbs) - 4; /* final block length */ + +		flag1 = aimbs_get16(&mbs); +		flag2 = aimbs_get16(&mbs); + +		msg = aimbs_getstr(&mbs, msglen); +	} + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, channel, sn, msg, icbmflags, flag1, flag2); + +	g_free(sn); +	aim_freetlvchain(&tlvlist); + +	return ret; +} + +/* + * Ahh, the joys of nearly ridiculous over-engineering. + * + * Not only do AIM ICBM's support multiple channels.  Not only do they + * support multiple character sets.  But they support multiple character  + * sets / encodings within the same ICBM. + * + * These multipart messages allow for complex space savings techniques, which + * seem utterly unnecessary by today's standards.  In fact, there is only + * one client still in popular use that still uses this method: AOL for the + * Macintosh, Version 5.0.  Obscure, yes, I know.   + * + * In modern (non-"legacy") clients, if the user tries to send a character + * that is not ISO-8859-1 or ASCII, the client will send the entire message + * as UNICODE, meaning that every character in the message will occupy the + * full 16 bit UNICODE field, even if the high order byte would be zero. + * Multipart messages prevent this wasted space by allowing the client to + * only send the characters in UNICODE that need to be sent that way, and + * the rest of the message can be sent in whatever the native character  + * set is (probably ASCII). + * + * An important note is that sections will be displayed in the order that + * they appear in the ICBM.  There is no facility for merging or rearranging + * sections at run time.  So if you have, say, ASCII then UNICODE then ASCII, + * you must supply two ASCII sections with a UNICODE in the middle, and incur + * the associated overhead. + * + * Normally I would have laughed and given a firm 'no' to supporting this + * seldom-used feature, but something is attracting me to it.  In the future, + * it may be possible to abuse this to send mixed-media messages to other + * open source clients (like encryption or something) -- see faimtest for + * examples of how to do this. + * + * I would definitly recommend avoiding this feature unless you really + * know what you are doing, and/or you have something neat to do with it. + * + */ +int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm) +{ + +	memset(mpm, 0, sizeof(aim_mpmsg_t)); + +	return 0; +} + +static int mpmsg_addsection(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, guint8 *data, guint16 datalen) +{ +	aim_mpmsg_section_t *sec;  +	 +	if (!(sec = g_new0(aim_mpmsg_section_t,1))) +		return -1; + +	sec->charset = charset; +	sec->charsubset = charsubset; +	sec->data = data; +	sec->datalen = datalen; +	sec->next = NULL; + +	if (!mpm->parts) +		mpm->parts = sec; +	else { +		aim_mpmsg_section_t *cur; + +		for (cur = mpm->parts; cur->next; cur = cur->next) +			; +		cur->next = sec; +	} + +	mpm->numparts++; + +	return 0; +} + +int aim_mpmsg_addraw(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, const guint8 *data, guint16 datalen) +{ +	guint8 *dup; + +	if (!(dup = g_malloc(datalen))) +		return -1; +	memcpy(dup, data, datalen); + +	if (mpmsg_addsection(sess, mpm, charset, charsubset, dup, datalen) == -1) { +		g_free(dup); +		return -1; +	} + +	return 0; +} + +/* XXX should provide a way of saying ISO-8859-1 specifically */ +int aim_mpmsg_addascii(aim_session_t *sess, aim_mpmsg_t *mpm, const char *ascii) +{ +	char *dup; + +	if (!(dup = g_strdup(ascii)))  +		return -1; + +	if (mpmsg_addsection(sess, mpm, 0x0000, 0x0000, (guint8 *)dup, (guint16) strlen(ascii)) == -1) { +		g_free(dup); +		return -1; +	} + +	return 0; +} + +int aim_mpmsg_addunicode(aim_session_t *sess, aim_mpmsg_t *mpm, const guint16 *unicode, guint16 unicodelen) +{ +	guint8 *buf; +	aim_bstream_t bs; +	int i; + +	if (!(buf = g_malloc(unicodelen * 2))) +		return -1; + +	aim_bstream_init(&bs, buf, unicodelen * 2); + +	/* We assume unicode is in /host/ byte order -- convert to network */ +	for (i = 0; i < unicodelen; i++) +		aimbs_put16(&bs, unicode[i]); + +	if (mpmsg_addsection(sess, mpm, 0x0002, 0x0000, buf, aim_bstream_curpos(&bs)) == -1) { +		g_free(buf); +		return -1; +	} +	 +	return 0; +} + +void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm) +{ +	aim_mpmsg_section_t *cur; + +	for (cur = mpm->parts; cur; ) { +		aim_mpmsg_section_t *tmp; +		 +		tmp = cur->next; +		g_free(cur->data); +		g_free(cur); +		cur = tmp; +	} +	 +	mpm->numparts = 0; +	mpm->parts = NULL; + +	return; +} + +/* + * Start by building the multipart structures, then pick the first  + * human-readable section and stuff it into args->msg so no one gets + * suspicious. + * + */ +static int incomingim_ch1_parsemsgs(aim_session_t *sess, guint8 *data, int len, struct aim_incomingim_ch1_args *args) +{ +	static const guint16 charsetpri[] = { +		0x0000, /* ASCII first */ +		0x0003, /* then ISO-8859-1 */ +		0x0002, /* UNICODE as last resort */ +	}; +	static const int charsetpricount = 3; +	int i; +	aim_bstream_t mbs; +	aim_mpmsg_section_t *sec; + +	aim_bstream_init(&mbs, data, len); + +	while (aim_bstream_empty(&mbs)) { +		guint16 msglen, flag1, flag2; +		char *msgbuf; + +		aimbs_get8(&mbs); /* 01 */ +		aimbs_get8(&mbs); /* 01 */ + +		/* Message string length, including character set info. */ +		msglen = aimbs_get16(&mbs); + +		/* Character set info */ +		flag1 = aimbs_get16(&mbs); +		flag2 = aimbs_get16(&mbs); + +		/* Message. */ +		msglen -= 4; + +		/* +		 * For now, we don't care what the encoding is.  Just copy +		 * it into a multipart struct and deal with it later. However, +		 * always pad the ending with a NULL.  This makes it easier +		 * to treat ASCII sections as strings.  It won't matter for +		 * UNICODE or binary data, as you should never read past +		 * the specified data length, which will not include the pad. +		 * +		 * XXX There's an API bug here.  For sending, the UNICODE is +		 * given in host byte order (aim_mpmsg_addunicode), but here +		 * the received messages are given in network byte order. +		 * +		 */ +		msgbuf = aimbs_getstr(&mbs, msglen); +		mpmsg_addsection(sess, &args->mpmsg, flag1, flag2, (guint8 *)msgbuf, (guint16) msglen); + +	} /* while */ + +	args->icbmflags |= AIM_IMFLAGS_MULTIPART; /* always set */ + +	/* +	 * Clients that support multiparts should never use args->msg, as it +	 * will point to an arbitrary section. +	 * +	 * Here, we attempt to provide clients that do not support multipart +	 * messages with something to look at -- hopefully a human-readable +	 * string.  But, failing that, a UNICODE message, or nothing at all. +	 * +	 * Which means that even if args->msg is NULL, it does not mean the +	 * message was blank. +	 * +	 */ +	for (i = 0; i < charsetpricount; i++) { +		for (sec = args->mpmsg.parts; sec; sec = sec->next) { + +			if (sec->charset != charsetpri[i]) +				continue; + +			/* Great. We found one.  Fill it in. */ +			args->charset = sec->charset; +			args->charsubset = sec->charsubset; +			args->icbmflags |= AIM_IMFLAGS_CUSTOMCHARSET; + +			/* Set up the simple flags */ +			if (args->charset == 0x0000) +				; /* ASCII */ +			else if (args->charset == 0x0002) +				args->icbmflags |= AIM_IMFLAGS_UNICODE; +			else if (args->charset == 0x0003) +				args->icbmflags |= AIM_IMFLAGS_ISO_8859_1; +			else if (args->charset == 0xffff) +				; /* no encoding (yeep!) */ + +			if (args->charsubset == 0x0000) +				; /* standard subencoding? */ +			else if (args->charsubset == 0x000b) +				args->icbmflags |= AIM_IMFLAGS_SUBENC_MACINTOSH; +			else if (args->charsubset == 0xffff) +				; /* no subencoding */ +#if 0 +			/* XXX this isn't really necesary... */	 +			if (	((args.flag1 != 0x0000) && +				 (args.flag1 != 0x0002) && +				 (args.flag1 != 0x0003) && +				 (args.flag1 != 0xffff)) || +				((args.flag2 != 0x0000) && +				 (args.flag2 != 0x000b) && +				 (args.flag2 != 0xffff))) { +				faimdprintf(sess, 0, "icbm: **warning: encoding flags are being used! {%04x, %04x}\n", args.flag1, args.flag2); +			} +#endif + +			args->msg = (char *)sec->data; +			args->msglen = sec->datalen; + +			return 0; +		} +	} + +	/* No human-readable sections found.  Oh well. */ +	args->charset = args->charsubset = 0xffff; +	args->msg = NULL; +	args->msglen = 0; + +	return 0; +} + +static int incomingim_ch1(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_bstream_t *bs, guint8 *cookie) +{ +	guint16 type, length; +	aim_rxcallback_t userfunc; +	int ret = 0; +	struct aim_incomingim_ch1_args args; +	int endpos; + +	memset(&args, 0, sizeof(args)); + +	aim_mpmsg_init(sess, &args.mpmsg); + +	/* +	 * This used to be done using tlvchains.  For performance reasons, +	 * I've changed it to process the TLVs in-place.  This avoids lots +	 * of per-IM memory allocations. +	 */ +	while (aim_bstream_empty(bs)) { + +		type = aimbs_get16(bs); +		length = aimbs_get16(bs); + +		endpos = aim_bstream_curpos(bs) + length; + +		if (type == 0x0002) { /* Message Block */ + +			/* +			 * This TLV consists of the following: +			 *   - 0501 -- Unknown +			 *   - Features: Don't know how to interpret these +			 *   - 0101 -- Unknown +			 *   - Message +			 * +			 */ + +			aimbs_get8(bs); /* 05 */ +			aimbs_get8(bs); /* 01 */ + +			args.featureslen = aimbs_get16(bs); +			/* XXX XXX this is all evil! */ +			args.features = bs->data + bs->offset; +			aim_bstream_advance(bs, args.featureslen); +			args.icbmflags |= AIM_IMFLAGS_CUSTOMFEATURES; + +			/* +			 * The rest of the TLV contains one or more message +			 * blocks... +			 */ +			incomingim_ch1_parsemsgs(sess, bs->data + bs->offset /* XXX evil!!! */, length - 2 - 2 - args.featureslen, &args); + +		} else if (type == 0x0003) { /* Server Ack Requested */ + +			args.icbmflags |= AIM_IMFLAGS_ACK; + +		} else if (type == 0x0004) { /* Message is Auto Response */ + +			args.icbmflags |= AIM_IMFLAGS_AWAY; + +		} else if (type == 0x0006) { /* Message was received offline. */ + +			/* XXX not sure if this actually gets sent. */ +			args.icbmflags |= AIM_IMFLAGS_OFFLINE; + +		} else if (type == 0x0008) { /* I-HAVE-A-REALLY-PURTY-ICON Flag */ + +			args.iconlen = aimbs_get32(bs); +			aimbs_get16(bs); /* 0x0001 */ +			args.iconsum = aimbs_get16(bs); +			args.iconstamp = aimbs_get32(bs); + +			/* +			 * This looks to be a client bug.  MacAIM 4.3 will +			 * send this tag, but with all zero values, in the +			 * first message of a conversation. This makes no +			 * sense whatsoever, so I'm going to say its a bug. +			 * +			 * You really shouldn't advertise a zero-length icon +			 * anyway. +			 *  +			 */ +			if (args.iconlen) +				args.icbmflags |= AIM_IMFLAGS_HASICON; + +		} else if (type == 0x0009) { + +			args.icbmflags |= AIM_IMFLAGS_BUDDYREQ; + +		} else if (type == 0x0017) { + +			args.extdatalen = length; +			args.extdata = aimbs_getraw(bs, args.extdatalen); + +		} else { +			// imcb_error(sess->aux_data, "Unknown TLV encountered"); +		} + +		/* +		 * This is here to protect ourselves from ourselves.  That +		 * is, if something above doesn't completly parse its value +		 * section, or, worse, overparses it, this will set the +		 * stream where it needs to be in order to land on the next +		 * TLV when the loop continues. +		 * +		 */ +		aim_bstream_setpos(bs, endpos); +	} + + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, channel, userinfo, &args); + +	aim_mpmsg_free(sess, &args.mpmsg); +	g_free(args.extdata); + +	return ret; +} + + +static void incomingim_ch2_chat_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args) +{ + +	/* XXX aim_chat_roominfo_free() */ +	g_free(args->info.chat.roominfo.name); + +	return; +} + +static void incomingim_ch2_chat(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata) +{ + +	/* +	 * Chat room info. +	 */ +	if (servdata) +		aim_chat_readroominfo(servdata, &args->info.chat.roominfo); + +	args->destructor = (void *)incomingim_ch2_chat_free; + +	return; +} + +static void incomingim_ch2_icqserverrelay_free(aim_session_t *sess, struct aim_incomingim_ch2_args *args) +{ + +	g_free((char *)args->info.rtfmsg.rtfmsg); + +	return; +} + +/* + * The relationship between AIM_CAPS_ICQSERVERRELAY and AIM_CAPS_ICQRTF is  + * kind of odd. This sends the client ICQRTF since that is all that I've seen + * SERVERRELAY used for. + * + * Note that this is all little-endian.  Cringe. + * + * This cap is used for auto status message replies, too [ft] + * + */ +static void incomingim_ch2_icqserverrelay(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args, aim_bstream_t *servdata) +{ +	guint16 hdrlen, msglen, dc; +	guint8 msgtype, msgflags; +    guint8 *plugin; +    int i = 0, tmp = 0; +    struct im_connection *ic = sess->aux_data; + +    /* at the moment we just can deal with requests, not with cancel or accept */ +    if (args->status != 0) return; + +	hdrlen = aimbs_getle16(servdata); + +    aim_bstream_advance(servdata, 0x02); /* protocol version */ +    plugin = aimbs_getraw(servdata, 0x10); /* following data is a message or  +                                              something plugin specific */ +    /* as there is no plugin handling, just skip the rest */ +    aim_bstream_advance(servdata, hdrlen - 0x12); + +	hdrlen = aimbs_getle16(servdata); +    dc = aimbs_getle16(servdata); /* save the sequence number */ +	aim_bstream_advance(servdata, hdrlen - 0x02); + +    /* TODO is it a message or something for a plugin? */ +    for (i = 0; i < 0x10; i++) { +        tmp |= plugin[i]; +    } + +    if (!tmp) { /* message follows */ + +        msgtype = aimbs_getle8(servdata); +        msgflags = aimbs_getle8(servdata); + +        aim_bstream_advance(servdata, 0x04); /* status code and priority code */ + +        msglen = aimbs_getle16(servdata); /* message string length */ +	args->info.rtfmsg.rtfmsg = aimbs_getstr(servdata, msglen); + +        switch(msgtype) { +            case AIM_MTYPE_PLAIN: + +                args->info.rtfmsg.fgcolor = aimbs_getle32(servdata); +                args->info.rtfmsg.bgcolor = aimbs_getle32(servdata); + +                hdrlen = aimbs_getle32(servdata); +                aim_bstream_advance(servdata, hdrlen); + +                /* XXX This is such a hack. */ +                args->reqclass = AIM_CAPS_ICQRTF; +                break; + +            case AIM_MTYPE_AUTOAWAY:  +            case AIM_MTYPE_AUTOBUSY: +            case AIM_MTYPE_AUTONA: +            case AIM_MTYPE_AUTODND: +            case AIM_MTYPE_AUTOFFC: +	    case 0x9c:	/* ICQ 5 seems to send this */ +                aim_send_im_ch2_statusmessage(sess, userinfo->sn, args->cookie, +                        ic->away ? ic->away : "", sess->aim_icq_state, dc); +                break; + +        } +    } /* message or plugin specific */ + +    g_free(plugin); +	args->destructor = (void *)incomingim_ch2_icqserverrelay_free; + +	return; +} + +typedef void (*ch2_args_destructor_t)(aim_session_t *sess, struct aim_incomingim_ch2_args *args); + +static int incomingim_ch2(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_tlvlist_t *tlvlist, guint8 *cookie) +{ +	aim_rxcallback_t userfunc; +	aim_tlv_t *block1, *servdatatlv; +	aim_tlvlist_t *list2; +	struct aim_incomingim_ch2_args args; +	aim_bstream_t bbs, sdbs, *sdbsptr = NULL; +	guint8 *cookie2; +	int ret = 0; + +	char clientip1[30] = {""}; +	char clientip2[30] = {""}; +	char verifiedip[30] = {""}; + +	memset(&args, 0, sizeof(args)); + +	/* +	 * There's another block of TLVs embedded in the type 5 here.  +	 */ +	block1 = aim_gettlv(tlvlist, 0x0005, 1); +	aim_bstream_init(&bbs, block1->value, block1->length); + +	/* +	 * First two bytes represent the status of the connection. +	 * +	 * 0 is a request, 1 is a deny (?), 2 is an accept +	 */  +	args.status = aimbs_get16(&bbs); + +	/* +	 * Next comes the cookie.  Should match the ICBM cookie. +	 */ +	cookie2 = aimbs_getraw(&bbs, 8); +	if (memcmp(cookie, cookie2, 8) != 0)  +		imcb_error(sess->aux_data, "rend: warning cookies don't match!"); +	memcpy(args.cookie, cookie2, 8); +	g_free(cookie2); + +	/* +	 * The next 16bytes are a capability block so we can +	 * identify what type of rendezvous this is. +	 */ +	args.reqclass = aim_getcap(sess, &bbs, 0x10); + +	/*  +	 * What follows may be TLVs or nothing, depending on the +	 * purpose of the message. +	 * +	 * Ack packets for instance have nothing more to them. +	 */ +	list2 = aim_readtlvchain(&bbs); + +	/* +	 * IP address from the perspective of the client. +	 */ +	if (aim_gettlv(list2, 0x0002, 1)) { +		aim_tlv_t *iptlv; + +		iptlv = aim_gettlv(list2, 0x0002, 1); + +		g_snprintf(clientip1, sizeof(clientip1), "%d.%d.%d.%d", +				aimutil_get8(iptlv->value+0), +				aimutil_get8(iptlv->value+1), +				aimutil_get8(iptlv->value+2), +				aimutil_get8(iptlv->value+3)); +	} + +	/* +	 * Secondary IP address from the perspective of the client. +	 */ +	if (aim_gettlv(list2, 0x0003, 1)) { +		aim_tlv_t *iptlv; + +		iptlv = aim_gettlv(list2, 0x0003, 1); + +		g_snprintf(clientip2, sizeof(clientip2), "%d.%d.%d.%d", +				aimutil_get8(iptlv->value+0), +				aimutil_get8(iptlv->value+1), +				aimutil_get8(iptlv->value+2), +				aimutil_get8(iptlv->value+3)); +	} + +	/* +	 * Verified IP address (from the perspective of Oscar). +	 * +	 * This is added by the server. +	 */ +	if (aim_gettlv(list2, 0x0004, 1)) { +		aim_tlv_t *iptlv; + +		iptlv = aim_gettlv(list2, 0x0004, 1); + +		g_snprintf(verifiedip, sizeof(verifiedip), "%d.%d.%d.%d", +				aimutil_get8(iptlv->value+0), +				aimutil_get8(iptlv->value+1), +				aimutil_get8(iptlv->value+2), +				aimutil_get8(iptlv->value+3)); +	} + +	/* +	 * Port number for something. +	 */ +	if (aim_gettlv(list2, 0x0005, 1)) +		args.port = aim_gettlv16(list2, 0x0005, 1); + +	/* +	 * Error code. +	 */ +	if (aim_gettlv(list2, 0x000b, 1)) +		args.errorcode = aim_gettlv16(list2, 0x000b, 1); + +	/* +	 * Invitation message / chat description. +	 */ +	if (aim_gettlv(list2, 0x000c, 1)) +		args.msg = aim_gettlv_str(list2, 0x000c, 1); + +	/* +	 * Character set. +	 */ +	if (aim_gettlv(list2, 0x000d, 1)) +		args.encoding = aim_gettlv_str(list2, 0x000d, 1); +	 +	/* +	 * Language. +	 */ +	if (aim_gettlv(list2, 0x000e, 1)) +		args.language = aim_gettlv_str(list2, 0x000e, 1); + +	/* Unknown -- two bytes = 0x0001 */ +	if (aim_gettlv(list2, 0x000a, 1)) +		; + +	/* Unknown -- no value */ +	if (aim_gettlv(list2, 0x000f, 1)) +		; + +	if (strlen(clientip1)) +		args.clientip = (char *)clientip1; +	if (strlen(clientip2)) +		args.clientip2 = (char *)clientip2; +	if (strlen(verifiedip)) +		args.verifiedip = (char *)verifiedip; + +	/* +	 * This is must be present in PROPOSALs, but will probably not +	 * exist in CANCELs and ACCEPTs. +	 * +	 * Service Data blocks are module-specific in format. +	 */ +	if ((servdatatlv = aim_gettlv(list2, 0x2711 /* 10001 */, 1))) { + +		aim_bstream_init(&sdbs, servdatatlv->value, servdatatlv->length); +		sdbsptr = &sdbs; +	} + +	if (args.reqclass & AIM_CAPS_ICQSERVERRELAY) +		incomingim_ch2_icqserverrelay(sess, mod, rx, snac, userinfo, &args, sdbsptr); +	else if (args.reqclass & AIM_CAPS_CHAT) +		incomingim_ch2_chat(sess, mod, rx, snac, userinfo, &args, sdbsptr); + + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, channel, userinfo, &args); + + +	if (args.destructor) +		((ch2_args_destructor_t)args.destructor)(sess, &args); + +	g_free((char *)args.msg); +	g_free((char *)args.encoding); +	g_free((char *)args.language); + +	aim_freetlvchain(&list2); + +	return ret; +} + +static int incomingim_ch4(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_tlvlist_t *tlvlist, guint8 *cookie) +{ +	aim_bstream_t meat; +	aim_rxcallback_t userfunc; +	aim_tlv_t *block; +	struct aim_incomingim_ch4_args args; +	int ret = 0; + +	/* +	 * Make a bstream for the meaty part.  Yum.  Meat. +	 */ +	if (!(block = aim_gettlv(tlvlist, 0x0005, 1))) +		return -1; +	aim_bstream_init(&meat, block->value, block->length); + +	args.uin = aimbs_getle32(&meat); +	args.type = aimbs_getle16(&meat); +	args.msg = (char *)aimbs_getraw(&meat, aimbs_getle16(&meat)); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, channel, userinfo, &args); + +	g_free(args.msg); + +	return ret; +} + +/* + * It can easily be said that parsing ICBMs is THE single + * most difficult thing to do in the in AIM protocol.  In + * fact, I think I just did say that. + * + * Below is the best damned solution I've come up with + * over the past sixteen months of battling with it. This + * can parse both away and normal messages from every client + * I have access to.  Its not fast, its not clean.  But it works. + * + */ +static int incomingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int i, ret = 0; +	guint8 cookie[8]; +	guint16 channel; +	aim_userinfo_t userinfo; + +	memset(&userinfo, 0x00, sizeof(aim_userinfo_t)); + +	/* +	 * Read ICBM Cookie.  And throw away. +	 */ +	for (i = 0; i < 8; i++) +		cookie[i] = aimbs_get8(bs); + +	/* +	 * Channel ID. +	 * +	 * Channel 0x0001 is the message channel.  There are  +	 * other channels for things called "rendevous" +	 * which represent chat and some of the other new +	 * features of AIM2/3/3.5.  +	 * +	 * Channel 0x0002 is the Rendevous channel, which +	 * is where Chat Invitiations and various client-client +	 * connection negotiations come from. +	 * +	 * Channel 0x0004 is used for ICQ authorization, or  +	 * possibly any system notice. +	 * +	 */ +	channel = aimbs_get16(bs); + +	/* +	 * Extract the standard user info block. +	 * +	 * Note that although this contains TLVs that appear contiguous +	 * with the TLVs read below, they are two different pieces.  The +	 * userinfo block contains the number of TLVs that contain user +	 * information, the rest are not even though there is no seperation. +	 * aim_extractuserinfo() returns the number of bytes used by the +	 * userinfo tlvs, so you can start reading the rest of them right +	 * afterward.   +	 * +	 * That also means that TLV types can be duplicated between the +	 * userinfo block and the rest of the message, however there should +	 * never be two TLVs of the same type in one block. +	 *  +	 */ +	aim_extractuserinfo(sess, bs, &userinfo); + +	/* +	 * From here on, its depends on what channel we're on. +	 * +	 * Technically all channels have a TLV list have this, however, +	 * for the common channel 1 case, in-place parsing is used for +	 * performance reasons (less memory allocation). +	 */ +	if (channel == 1) { + +		ret = incomingim_ch1(sess, mod, rx, snac, channel, &userinfo, bs, cookie); + +	} else if (channel == 2) { +		aim_tlvlist_t *tlvlist; + +		/* +		 * Read block of TLVs (not including the userinfo data).  All  +		 * further data is derived from what is parsed here. +		 */ +		tlvlist = aim_readtlvchain(bs); + +		ret = incomingim_ch2(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie); + +		aim_freetlvchain(&tlvlist); + +	} else if (channel == 4) { +		aim_tlvlist_t *tlvlist; + +		tlvlist = aim_readtlvchain(bs); +		ret = incomingim_ch4(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie); +		aim_freetlvchain(&tlvlist); + +	} else { + +		imcb_error(sess->aux_data, "ICBM received on an unsupported channel.  Ignoring."); + +		return 0; +	} + +	return ret; +} + +/* + * Possible codes: + *    AIM_TRANSFER_DENY_NOTSUPPORTED -- "client does not support" + *    AIM_TRANSFER_DENY_DECLINE -- "client has declined transfer" + *    AIM_TRANSFER_DENY_NOTACCEPTING -- "client is not accepting transfers" + *  + */ +int aim_denytransfer(aim_session_t *sess, const char *sender, const guint8 *cookie, guint16 code) +{ +	aim_conn_t *conn; +	aim_frame_t *fr; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL; +	 +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+8+2+1+strlen(sender)+6))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x000b, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0004, 0x000b, 0x0000, snacid); +	 +	aimbs_putraw(&fr->data, cookie, 8); + +	aimbs_put16(&fr->data, 0x0002); /* channel */ +	aimbs_put8(&fr->data, strlen(sender)); +	aimbs_putraw(&fr->data, (guint8 *)sender, strlen(sender)); + +	aim_addtlvtochain16(&tl, 0x0003, code); +	aim_writetlvchain(&fr->data, &tl); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * aim_reqicbmparaminfo() + * + * Request ICBM parameter information. + * + */ +int aim_reqicbmparams(aim_session_t *sess) +{ +	aim_conn_t *conn; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) +		return -EINVAL; + +	return aim_genericreq_n(sess, conn, 0x0004, 0x0004); +} + +/* + * + * I definitly recommend sending this.  If you don't, you'll be stuck + * with the rather unreasonable defaults.  You don't want those.  Send this. + *  + */ +int aim_seticbmparam(aim_session_t *sess, struct aim_icbmparameters *params) +{ +	aim_conn_t *conn; +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0004))) +		return -EINVAL; + +	if (!params) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+16))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x0002, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0004, 0x0002, 0x0000, snacid); + +	/* This is read-only (see Parameter Reply). Must be set to zero here. */ +	aimbs_put16(&fr->data, 0x0000); + +	/* These are all read-write */ +	aimbs_put32(&fr->data, params->flags);  +	aimbs_put16(&fr->data, params->maxmsglen); +	aimbs_put16(&fr->data, params->maxsenderwarn);  +	aimbs_put16(&fr->data, params->maxrecverwarn);  +	aimbs_put32(&fr->data, params->minmsginterval); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +static int paraminfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	struct aim_icbmparameters params; +	aim_rxcallback_t userfunc; + +	params.maxchan = aimbs_get16(bs); +	params.flags = aimbs_get32(bs); +	params.maxmsglen = aimbs_get16(bs); +	params.maxsenderwarn = aimbs_get16(bs); +	params.maxrecverwarn = aimbs_get16(bs); +	params.minmsginterval = aimbs_get32(bs); +	 +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx, ¶ms); + +	return 0; +} + +static int missedcall(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	aim_rxcallback_t userfunc; +	guint16 channel, nummissed, reason; +	aim_userinfo_t userinfo; + +	while (aim_bstream_empty(bs)) {	 + +		channel = aimbs_get16(bs); +		aim_extractuserinfo(sess, bs, &userinfo); +		nummissed = aimbs_get16(bs); +		reason = aimbs_get16(bs); + +		if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +			 ret = userfunc(sess, rx, channel, &userinfo, nummissed, reason); +	} + +	return ret; +} + +/* + * Receive the response from an ICQ status message request.  This contains the  + * ICQ status message.  Go figure. + */ +static int clientautoresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	aim_rxcallback_t userfunc; +	guint16 channel, reason; +	char *sn; +	guint8 *ck, snlen; + +	ck = aimbs_getraw(bs, 8); +	channel = aimbs_get16(bs); +	snlen = aimbs_get8(bs); +	sn = aimbs_getstr(bs, snlen); +	reason = aimbs_get16(bs); + +	switch (reason) { +		case 0x0003: { /* ICQ status message.  Maybe other stuff too, you never know with these people. */ +			guint8 statusmsgtype, *msg; +			guint16 len; +			guint32 state; + +			len = aimbs_getle16(bs); /* Should be 0x001b */ +			aim_bstream_advance(bs, len); /* Unknown */ + +			len = aimbs_getle16(bs); /* Should be 0x000e */ +			aim_bstream_advance(bs, len); /* Unknown */ + +			statusmsgtype = aimbs_getle8(bs); +			switch (statusmsgtype) { +				case 0xe8: +					state = AIM_ICQ_STATE_AWAY; +					break; +				case 0xe9: +					state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_BUSY; +					break; +				case 0xea: +					state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_OUT; +					break; +				case 0xeb: +					state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_DND | AIM_ICQ_STATE_BUSY; +					break; +				case 0xec: +					state = AIM_ICQ_STATE_CHAT; +					break; +				default: +					state = 0; +					break; +			} + +			aimbs_getle8(bs); /* Unknown - 0x03 Maybe this means this is an auto-reply */ +			aimbs_getle16(bs); /* Unknown - 0x0000 */ +			aimbs_getle16(bs); /* Unknown - 0x0000 */ + +			len = aimbs_getle16(bs); +			msg = aimbs_getraw(bs, len); + +			if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +				ret = userfunc(sess, rx, channel, sn, reason, state, msg); + +			g_free(msg); +		} break; + +		default: { +			if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +				ret = userfunc(sess, rx, channel, sn, reason); +		} break; +	} /* end switch */ + +	g_free(ck); +	g_free(sn); + +	return ret; +} + +static int msgack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	guint16 type; +	guint8 snlen, *ck; +	char *sn; +	int ret = 0; + +	ck = aimbs_getraw(bs, 8); +	type = aimbs_get16(bs); +	snlen = aimbs_get8(bs); +	sn = aimbs_getstr(bs, snlen); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, type, sn); + +	g_free(sn); +	g_free(ck); + +	return ret; +} + +/* + * Subtype 0x0014 - Send a mini typing notification (mtn) packet. + * + * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer,  + * and Gaim 0.60 and newer. + * + */ +int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2) +{ +	aim_conn_t *conn; +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !(conn = aim_conn_findbygroup(sess, 0x0002))) +		return -EINVAL; + +	if (!sn) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+11+strlen(sn)+2))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x0014, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0004, 0x0014, 0x0000, snacid); + +	/* +	 * 8 days of light +	 * Er, that is to say, 8 bytes of 0's +	 */ +	aimbs_put16(&fr->data, 0x0000); +	aimbs_put16(&fr->data, 0x0000); +	aimbs_put16(&fr->data, 0x0000); +	aimbs_put16(&fr->data, 0x0000); + +	/* +	 * Type 1 (should be 0x0001 for mtn) +	 */ +	aimbs_put16(&fr->data, type1); + +	/* +	 * Dest sn +	 */ +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (const guint8*)sn, strlen(sn)); + +	/* +	 * Type 2 (should be 0x0000, 0x0001, or 0x0002 for mtn) +	 */ +	aimbs_put16(&fr->data, type2); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Subtype 0x0014 - Receive a mini typing notification (mtn) packet. + * + * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer,  + * and Gaim 0.60 and newer. + * + */ +static int mtn_receive(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	aim_rxcallback_t userfunc; +	char *sn; +	guint8 snlen; +	guint16 type1, type2; + +	aim_bstream_advance(bs, 8); /* Unknown - All 0's */ +	type1 = aimbs_get16(bs); +	snlen = aimbs_get8(bs); +	sn = aimbs_getstr(bs, snlen); +	type2 = aimbs_get16(bs); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, type1, sn, type2); + +	g_free(sn); + +	return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0005) +		return paraminfo(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0006) +		return outgoingim(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0007) +		return incomingim(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x000a) +		return missedcall(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x000b) +		return clientautoresp(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x000c) +		return msgack(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0014) +		return mtn_receive(sess, mod, rx, snac, bs); + +	return 0; +} + +int msg_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x0004; +	mod->version = 0x0001; +	mod->toolid = 0x0110; +	mod->toolversion = 0x0629; +	mod->flags = 0; +	strncpy(mod->name, "messaging", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} diff --git a/protocols/oscar/im.h b/protocols/oscar/im.h new file mode 100644 index 00000000..42a8a6b1 --- /dev/null +++ b/protocols/oscar/im.h @@ -0,0 +1,200 @@ +#ifndef __OSCAR_IM_H__ +#define __OSCAR_IM_H__ + +#define AIM_CB_FAM_MSG 0x0004 + +/* + * SNAC Family: Messaging Services. + */  +#define AIM_CB_MSG_ERROR 0x0001 +#define AIM_CB_MSG_PARAMINFO 0x0005 +#define AIM_CB_MSG_INCOMING 0x0007 +#define AIM_CB_MSG_EVIL 0x0009 +#define AIM_CB_MSG_MISSEDCALL 0x000a +#define AIM_CB_MSG_CLIENTAUTORESP 0x000b +#define AIM_CB_MSG_ACK 0x000c +#define AIM_CB_MSG_MTN 0x0014 +#define AIM_CB_MSG_DEFAULT 0xffff + +#define AIM_IMFLAGS_AWAY		0x0001 /* mark as an autoreply */ +#define AIM_IMFLAGS_ACK			0x0002 /* request a receipt notice */ +#define AIM_IMFLAGS_UNICODE		0x0004 +#define AIM_IMFLAGS_ISO_8859_1		0x0008 +#define AIM_IMFLAGS_BUDDYREQ		0x0010 /* buddy icon requested */ +#define AIM_IMFLAGS_HASICON		0x0020 /* already has icon */ +#define AIM_IMFLAGS_SUBENC_MACINTOSH	0x0040 /* damn that Steve Jobs! */ +#define AIM_IMFLAGS_CUSTOMFEATURES 	0x0080 /* features field present */ +#define AIM_IMFLAGS_EXTDATA		0x0100 +#define AIM_IMFLAGS_CUSTOMCHARSET	0x0200 /* charset fields set */ +#define AIM_IMFLAGS_MULTIPART		0x0400 /* ->mpmsg section valid */ +#define AIM_IMFLAGS_OFFLINE		0x0800 /* send to offline user */ + +/* + * Multipart message structures. + */ +typedef struct aim_mpmsg_section_s { +	guint16 charset; +	guint16 charsubset; +	guint8 *data; +	guint16 datalen; +	struct aim_mpmsg_section_s *next; +} aim_mpmsg_section_t; + +typedef struct aim_mpmsg_s { +	int numparts; +	aim_mpmsg_section_t *parts; +} aim_mpmsg_t; + +int aim_mpmsg_init(aim_session_t *sess, aim_mpmsg_t *mpm); +int aim_mpmsg_addraw(aim_session_t *sess, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, const guint8 *data, guint16 datalen); +int aim_mpmsg_addascii(aim_session_t *sess, aim_mpmsg_t *mpm, const char *ascii); +int aim_mpmsg_addunicode(aim_session_t *sess, aim_mpmsg_t *mpm, const guint16 *unicode, guint16 unicodelen); +void aim_mpmsg_free(aim_session_t *sess, aim_mpmsg_t *mpm); + +/* + * Arguments to aim_send_im_ext(). + * + * This is really complicated.  But immensely versatile. + * + */ +struct aim_sendimext_args { + +	/* These are _required_ */ +	const char *destsn; +	guint32 flags; /* often 0 */ + +	/* Only required if not using multipart messages */ +	const char *msg; +	int msglen; + +	/* Required if ->msg is not provided */ +	aim_mpmsg_t *mpmsg; + +	/* Only used if AIM_IMFLAGS_HASICON is set */ +	guint32 iconlen; +	time_t iconstamp; +	guint32 iconsum; + +	/* Only used if AIM_IMFLAGS_CUSTOMFEATURES is set */ +	guint8 *features; +	guint8 featureslen; + +	/* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set and mpmsg not used */ +	guint16 charset; +	guint16 charsubset; +}; + +/* + * Arguments to aim_send_rtfmsg(). + */ +struct aim_sendrtfmsg_args { +	const char *destsn; +	guint32 fgcolor; +	guint32 bgcolor; +	const char *rtfmsg; /* must be in RTF */ +}; + +/* + * This information is provided in the Incoming ICBM callback for + * Channel 1 ICBM's.   + * + * Note that although CUSTOMFEATURES and CUSTOMCHARSET say they + * are optional, both are always set by the current libfaim code. + * That may or may not change in the future.  It is mainly for + * consistency with aim_sendimext_args. + * + * Multipart messages require some explanation. If you want to use them, + * I suggest you read all the comments in im.c. + * + */ +struct aim_incomingim_ch1_args { + +	/* Always provided */ +	aim_mpmsg_t mpmsg; +	guint32 icbmflags; /* some flags apply only to ->msg, not all mpmsg */ +	 +	/* Only provided if message has a human-readable section */ +	char *msg; +	int msglen; + +	/* Only provided if AIM_IMFLAGS_HASICON is set */ +	time_t iconstamp; +	guint32 iconlen; +	guint16 iconsum; + +	/* Only provided if AIM_IMFLAGS_CUSTOMFEATURES is set */ +	guint8 *features; +	guint8 featureslen; + +	/* Only provided if AIM_IMFLAGS_EXTDATA is set */ +	guint8 extdatalen; +	guint8 *extdata; + +	/* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set */ +	guint16 charset; +	guint16 charsubset; +}; + +/* Valid values for channel 2 args->status */ +#define AIM_RENDEZVOUS_PROPOSE 0x0000 +#define AIM_RENDEZVOUS_CANCEL  0x0001 +#define AIM_RENDEZVOUS_ACCEPT  0x0002 + +struct aim_incomingim_ch2_args { +	guint8 cookie[8]; +	guint16 reqclass; +	guint16 status; +	guint16 errorcode; +	const char *clientip; +	const char *clientip2; +	const char *verifiedip; +	guint16 port; +	const char *msg; /* invite message or file description */ +	const char *encoding; +	const char *language; +	union { +		struct { +			guint32 checksum; +			guint32 length; +			time_t timestamp; +			guint8 *icon; +		} icon; +		struct { +			struct aim_chat_roominfo roominfo; +		} chat; +		struct { +			guint32 fgcolor; +			guint32 bgcolor; +			const char *rtfmsg; +		} rtfmsg; +	} info; +	void *destructor; /* used internally only */ +}; + +/* Valid values for channel 4 args->type */ +#define AIM_ICQMSG_AUTHREQUEST 0x0006 +#define AIM_ICQMSG_AUTHDENIED 0x0007 +#define AIM_ICQMSG_AUTHGRANTED 0x0008 + +struct aim_incomingim_ch4_args { +	guint32 uin; /* Of the sender of the ICBM */ +	guint16 type; +	char *msg; /* Reason for auth request, deny, or accept */ +}; + +int aim_send_rtfmsg(aim_session_t *sess, struct aim_sendrtfmsg_args *args); +int aim_send_im_ext(aim_session_t *sess, struct aim_sendimext_args *args); +int aim_send_im(aim_session_t *, const char *destsn, unsigned short flags, const char *msg); +int aim_send_icon(aim_session_t *sess, const char *sn, const guint8 *icon, int iconlen, time_t stamp, guint16 iconsum); +guint16 aim_iconsum(const guint8 *buf, int buflen); +int aim_send_typing(aim_session_t *sess, aim_conn_t *conn, int typing); +int aim_send_im_direct(aim_session_t *, aim_conn_t *, const char *msg, int len); +const char *aim_directim_getsn(aim_conn_t *conn); +aim_conn_t *aim_directim_initiate(aim_session_t *, const char *destsn); +aim_conn_t *aim_directim_connect(aim_session_t *, const char *sn, const char *addr, const guint8 *cookie); + +int aim_send_im_ch2_geticqmessage(aim_session_t *sess, const char *sn, int type); +int aim_im_sendmtn(aim_session_t *sess, guint16 type1, const char *sn, guint16 type2); +int aim_send_im_ch2_statusmessage(aim_session_t *sess, const char *sender, const guint8 *cookie, const char *message, const guint8 state, const guint16 dc); + +#endif /* __OSCAR_IM_H__ */ diff --git a/protocols/oscar/info.c b/protocols/oscar/info.c new file mode 100644 index 00000000..7cc1dbbc --- /dev/null +++ b/protocols/oscar/info.c @@ -0,0 +1,726 @@ +/* + * aim_info.c + * + * The functions here are responsible for requesting and parsing information- + * gathering SNACs.  Or something like that.  + * + */ + +#include <aim.h> +#include "info.h" + +struct aim_priv_inforeq { +	char sn[MAXSNLEN+1]; +	guint16 infotype; +}; + +int aim_getinfo(aim_session_t *sess, aim_conn_t *conn, const char *sn, guint16 infotype) +{ +	struct aim_priv_inforeq privdata; +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !conn || !sn) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 12+1+strlen(sn)))) +		return -ENOMEM; + +	strncpy(privdata.sn, sn, sizeof(privdata.sn)); +	privdata.infotype = infotype; +	snacid = aim_cachesnac(sess, 0x0002, 0x0005, 0x0000, &privdata, sizeof(struct aim_priv_inforeq)); +	 +	aim_putsnac(&fr->data, 0x0002, 0x0005, 0x0000, snacid); +	aimbs_put16(&fr->data, infotype); +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +const char *aim_userinfo_sn(aim_userinfo_t *ui) +{ + +	if (!ui) +		return NULL; + +	return ui->sn; +} + +guint16 aim_userinfo_flags(aim_userinfo_t *ui) +{ + +	if (!ui) +		return 0; + +	return ui->flags; +} + +guint16 aim_userinfo_idle(aim_userinfo_t *ui) +{ + +	if (!ui) +		return 0; + +	return ui->idletime; +} + +float aim_userinfo_warnlevel(aim_userinfo_t *ui) +{ + +	if (!ui) +		return 0.00; + +	return (ui->warnlevel / 10); +} + +time_t aim_userinfo_membersince(aim_userinfo_t *ui) +{ + +	if (!ui) +		return 0; + +	return (time_t)ui->membersince; +} + +time_t aim_userinfo_onlinesince(aim_userinfo_t *ui) +{ + +	if (!ui) +		return 0; + +	return (time_t)ui->onlinesince; +} + +guint32 aim_userinfo_sessionlen(aim_userinfo_t *ui) +{ + +	if (!ui) +		return 0; + +	return ui->sessionlen; +} + +int aim_userinfo_hascap(aim_userinfo_t *ui, guint32 cap) +{ + +	if (!ui || !(ui->present & AIM_USERINFO_PRESENT_CAPABILITIES)) +		return -1; + +	return !!(ui->capabilities & cap); +} + + +/* + * Capability blocks.  + * + * These are CLSIDs. They should actually be of the form: + * + * {0x0946134b, 0x4c7f, 0x11d1, + *  {0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}}, + * + * But, eh. + */ +static const struct { +	guint32 flag; +	guint8 data[16]; +} aim_caps[] = { + +	/* +	 * Chat is oddball. +	 */ +	{AIM_CAPS_CHAT, +	 {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,  +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	/* +	 * These are mostly in order. +	 */ +	{AIM_CAPS_VOICE, +	 {0x09, 0x46, 0x13, 0x41, 0x4c, 0x7f, 0x11, 0xd1,  +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_SENDFILE, +	 {0x09, 0x46, 0x13, 0x43, 0x4c, 0x7f, 0x11, 0xd1,  +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	/* +	 * Advertised by the EveryBuddy client. +	 */ +	{AIM_CAPS_ICQ, +	 {0x09, 0x46, 0x13, 0x44, 0x4c, 0x7f, 0x11, 0xd1,  +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_IMIMAGE, +	 {0x09, 0x46, 0x13, 0x45, 0x4c, 0x7f, 0x11, 0xd1,  +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_BUDDYICON, +	 {0x09, 0x46, 0x13, 0x46, 0x4c, 0x7f, 0x11, 0xd1,  +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_SAVESTOCKS, +	 {0x09, 0x46, 0x13, 0x47, 0x4c, 0x7f, 0x11, 0xd1, +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_GETFILE, +	 {0x09, 0x46, 0x13, 0x48, 0x4c, 0x7f, 0x11, 0xd1, +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +    /* +     * Client supports channel 2 extended, TLV(0x2711) based messages. +     * Currently used only by ICQ clients. ICQ clients and clones use this GUID +     * as message format sign. Trillian client use another GUID in channel 2 +     * messages to implement its own message format (trillian doesn't use +     * TLV(x2711) in SecureIM channel 2 messages!). +     */ +	{AIM_CAPS_ICQSERVERRELAY, +	 {0x09, 0x46, 0x13, 0x49, 0x4c, 0x7f, 0x11, 0xd1, +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	/* +	 * Indeed, there are two of these.  The former appears to be correct,  +	 * but in some versions of winaim, the second one is set.  Either they  +	 * forgot to fix endianness, or they made a typo. It really doesn't  +	 * matter which. +	 */ +	{AIM_CAPS_GAMES, +	 {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1, +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, +	{AIM_CAPS_GAMES2, +	 {0x09, 0x46, 0x13, 0x4a, 0x4c, 0x7f, 0x11, 0xd1, +	  0x22, 0x82, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_SENDBUDDYLIST, +	 {0x09, 0x46, 0x13, 0x4b, 0x4c, 0x7f, 0x11, 0xd1, +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_UTF8, +	 {0x09, 0x46, 0x13, 0x4E, 0x4C, 0x7F, 0x11, 0xD1, +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_ICQRTF, +	 {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34,  +	  0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x92}}, + +	{AIM_CAPS_ICQUNKNOWN, +	 {0x2e, 0x7a, 0x64, 0x75, 0xfa, 0xdf, 0x4d, 0xc8, +	  0x88, 0x6f, 0xea, 0x35, 0x95, 0xfd, 0xb6, 0xdf}}, + +	{AIM_CAPS_EMPTY, +	 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + +	{AIM_CAPS_TRILLIANCRYPT, +	 {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb, +	  0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00}}, + +	{AIM_CAPS_APINFO,  +     {0xAA, 0x4A, 0x32, 0xB5, 0xF8, 0x84, 0x48, 0xc6, +      0xA3, 0xD7, 0x8C, 0x50, 0x97, 0x19, 0xFD, 0x5B}}, + +	{AIM_CAPS_INTEROP, +	 {0x09, 0x46, 0x13, 0x4d, 0x4c, 0x7f, 0x11, 0xd1, +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_ICHAT, +	 {0x09, 0x46, 0x00, 0x00, 0x4c, 0x7f, 0x11, 0xd1,  +	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}, + +	{AIM_CAPS_LAST} +}; + +/* + * This still takes a length parameter even with a bstream because capabilities + * are not naturally bounded. + *  + */ +guint32 aim_getcap(aim_session_t *sess, aim_bstream_t *bs, int len) +{ +	guint32 flags = 0; +	int offset; + +	for (offset = 0; aim_bstream_empty(bs) && (offset < len); offset += 0x10) { +		guint8 *cap; +		int i, identified; + +		cap = aimbs_getraw(bs, 0x10); + +		for (i = 0, identified = 0; !(aim_caps[i].flag & AIM_CAPS_LAST); i++) { + +			if (memcmp(&aim_caps[i].data, cap, 0x10) == 0) { +				flags |= aim_caps[i].flag; +				identified++; +				break; /* should only match once... */ + +			} +		} + +		if (!identified) { +			/*FIXME*/ +			/*REMOVEME :-) +			g_strdup_printf("unknown capability: {%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n", +					cap[0], cap[1], cap[2], cap[3], +					cap[4], cap[5], +					cap[6], cap[7], +					cap[8], cap[9], +					cap[10], cap[11], cap[12], cap[13], +					cap[14], cap[15]); +			*/ +		} + +		g_free(cap); +	} + +	return flags; +} + +int aim_putcap(aim_bstream_t *bs, guint32 caps) +{ +	int i; + +	if (!bs) +		return -EINVAL; + +	for (i = 0; aim_bstream_empty(bs); i++) { + +		if (aim_caps[i].flag == AIM_CAPS_LAST) +			break; + +		if (caps & aim_caps[i].flag) +			aimbs_putraw(bs, aim_caps[i].data, 0x10); + +	} + +	return 0; +} + +/* + * AIM is fairly regular about providing user info.  This is a generic  + * routine to extract it in its standard form. + */ +int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo) +{ +	int curtlv, tlvcnt; +	guint8 snlen; + +	if (!bs || !outinfo) +		return -EINVAL; + +	/* Clear out old data first */ +	memset(outinfo, 0x00, sizeof(aim_userinfo_t)); + +	/* +	 * Screen name.  Stored as an unterminated string prepended with a  +	 * byte containing its length. +	 */ +	snlen = aimbs_get8(bs); +	aimbs_getrawbuf(bs, (guint8 *)outinfo->sn, snlen); + +	/* +	 * Warning Level.  Stored as an unsigned short. +	 */ +	outinfo->warnlevel = aimbs_get16(bs); + +	/* +	 * TLV Count. Unsigned short representing the number of  +	 * Type-Length-Value triples that follow. +	 */ +	tlvcnt = aimbs_get16(bs); + +	/*  +	 * Parse out the Type-Length-Value triples as they're found. +	 */ +	for (curtlv = 0; curtlv < tlvcnt; curtlv++) { +		int endpos; +		guint16 type, length; + +		type = aimbs_get16(bs); +		length = aimbs_get16(bs); + +		endpos = aim_bstream_curpos(bs) + length; + +		if (type == 0x0001) { +			/* +			 * Type = 0x0001: User flags +			 *  +			 * Specified as any of the following ORed together: +			 *      0x0001  Trial (user less than 60days) +			 *      0x0002  Unknown bit 2 +			 *      0x0004  AOL Main Service user +			 *      0x0008  Unknown bit 4 +			 *      0x0010  Free (AIM) user  +			 *      0x0020  Away +			 *      0x0400  ActiveBuddy +			 * +			 */ +			outinfo->flags = aimbs_get16(bs); +			outinfo->present |= AIM_USERINFO_PRESENT_FLAGS; + +		} else if (type == 0x0002) { +			/* +			 * Type = 0x0002: Member-Since date.  +			 * +			 * The time/date that the user originally registered for +			 * the service, stored in time_t format. +			 */ +			outinfo->membersince = aimbs_get32(bs); +			outinfo->present |= AIM_USERINFO_PRESENT_MEMBERSINCE; + +		} else if (type == 0x0003) { +			/* +			 * Type = 0x0003: On-Since date. +			 * +			 * The time/date that the user started their current  +			 * session, stored in time_t format. +			 */ +			outinfo->onlinesince = aimbs_get32(bs); +			outinfo->present |= AIM_USERINFO_PRESENT_ONLINESINCE; + +		} else if (type == 0x0004) { +			/* +			 * Type = 0x0004: Idle time. +			 * +			 * Number of seconds since the user actively used the  +			 * service. +			 * +			 * Note that the client tells the server when to start +			 * counting idle times, so this may or may not be  +			 * related to reality. +			 */ +			outinfo->idletime = aimbs_get16(bs); +			outinfo->present |= AIM_USERINFO_PRESENT_IDLE; + +		} else if (type == 0x0006) { +			/* +			 * Type = 0x0006: ICQ Online Status +			 * +			 * ICQ's Away/DND/etc "enriched" status. Some decoding  +			 * of values done by Scott <darkagl@pcnet.com> +			 */ +			aimbs_get16(bs); +			outinfo->icqinfo.status = aimbs_get16(bs); +			outinfo->present |= AIM_USERINFO_PRESENT_ICQEXTSTATUS; + +		} else if (type == 0x000a) { +			/* +			 * Type = 0x000a +			 * +			 * ICQ User IP Address. +			 * Ahh, the joy of ICQ security. +			 */ +			outinfo->icqinfo.ipaddr = aimbs_get32(bs); +			outinfo->present |= AIM_USERINFO_PRESENT_ICQIPADDR; + +		} else if (type == 0x000c) { +			/*  +			 * Type = 0x000c +			 * +			 * random crap containing the IP address, +			 * apparently a port number, and some Other Stuff. +			 * +			 */ +			aimbs_getrawbuf(bs, outinfo->icqinfo.crap, 0x25); +			outinfo->present |= AIM_USERINFO_PRESENT_ICQDATA; + +		} else if (type == 0x000d) { +			/* +			 * Type = 0x000d +			 * +			 * Capability information. +			 * +			 */ +			outinfo->capabilities = aim_getcap(sess, bs, length); +			outinfo->present |= AIM_USERINFO_PRESENT_CAPABILITIES; + +		} else if (type == 0x000e) { +			/* +			 * Type = 0x000e +			 * +			 * Unknown.  Always of zero length, and always only +			 * on AOL users. +			 * +			 * Ignore. +			 * +			 */ + +		} else if ((type == 0x000f) || (type == 0x0010)) { +			/* +			 * Type = 0x000f: Session Length. (AIM) +			 * Type = 0x0010: Session Length. (AOL) +			 * +			 * The duration, in seconds, of the user's current  +			 * session. +			 * +			 * Which TLV type this comes in depends on the +			 * service the user is using (AIM or AOL). +			 * +			 */ +			outinfo->sessionlen = aimbs_get32(bs); +			outinfo->present |= AIM_USERINFO_PRESENT_SESSIONLEN; + +		} else { + +			/* +			 * Reaching here indicates that either AOL has +			 * added yet another TLV for us to deal with,  +			 * or the parsing has gone Terribly Wrong. +			 * +			 * Either way, inform the owner and attempt +			 * recovery. +			 * +			 */ +#ifdef DEBUG +			// imcb_error(sess->aux_data, G_STRLOC); +#endif + +		} + +		/* Save ourselves. */ +		aim_bstream_setpos(bs, endpos); +	} + +	return 0; +} + +/* + * Inverse of aim_extractuserinfo() + */ +int aim_putuserinfo(aim_bstream_t *bs, aim_userinfo_t *info) +{ +	aim_tlvlist_t *tlvlist = NULL; + +	if (!bs || !info) +		return -EINVAL; + +	aimbs_put8(bs, strlen(info->sn)); +	aimbs_putraw(bs, (guint8 *)info->sn, strlen(info->sn)); + +	aimbs_put16(bs, info->warnlevel); + + +	aim_addtlvtochain16(&tlvlist, 0x0001, info->flags); +	aim_addtlvtochain32(&tlvlist, 0x0002, info->membersince); +	aim_addtlvtochain32(&tlvlist, 0x0003, info->onlinesince); +	aim_addtlvtochain16(&tlvlist, 0x0004, info->idletime); + +#if ICQ_OSCAR_SUPPORT +	if (atoi(info->sn) != 0) { +		aim_addtlvtochain16(&tlvlist, 0x0006, info->icqinfo.status); +		aim_addtlvtochain32(&tlvlist, 0x000a, info->icqinfo.ipaddr); +	} +#endif + +	aim_addtlvtochain_caps(&tlvlist, 0x000d, info->capabilities); + +	aim_addtlvtochain32(&tlvlist, (guint16)((info->flags & AIM_FLAG_AOL) ? 0x0010 : 0x000f), info->sessionlen); + +	aimbs_put16(bs, aim_counttlvchain(&tlvlist)); +	aim_writetlvchain(bs, &tlvlist); +	aim_freetlvchain(&tlvlist); + +	return 0; +} + +int aim_sendbuddyoncoming(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *info) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !conn || !info) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0003, 0x000b, 0x0000, NULL, 0); +	 +	aim_putsnac(&fr->data, 0x0003, 0x000b, 0x0000, snacid); +	aim_putuserinfo(&fr->data, info); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_sendbuddyoffgoing(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !conn || !sn) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0003, 0x000c, 0x0000, NULL, 0); +	 +	aim_putsnac(&fr->data, 0x0003, 0x000c, 0x0000, snacid); +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Huh? What is this? + */ +int aim_0002_000b(aim_session_t *sess, aim_conn_t *conn, const char *sn) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !conn || !sn) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+1+strlen(sn)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0002, 0x000b, 0x0000, NULL, 0); +	 +	aim_putsnac(&fr->data, 0x0002, 0x000b, 0x0000, snacid); +	aimbs_put8(&fr->data, strlen(sn)); +	aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn)); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Normally contains: + *   t(0001)  - short containing max profile length (value = 1024) + *   t(0002)  - short - unknown (value = 16) [max MIME type length?] + *   t(0003)  - short - unknown (value = 10) + *   t(0004)  - short - unknown (value = 2048) [ICQ only?] + */ +static int rights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_tlvlist_t *tlvlist; +	aim_rxcallback_t userfunc; +	int ret = 0; +	guint16 maxsiglen = 0; + +	tlvlist = aim_readtlvchain(bs); + +	if (aim_gettlv(tlvlist, 0x0001, 1)) +		maxsiglen = aim_gettlv16(tlvlist, 0x0001, 1); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, maxsiglen); + +	aim_freetlvchain(&tlvlist); + +	return ret; +} + +static int userinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_userinfo_t userinfo; +	char *text_encoding = NULL, *text = NULL; +	guint16 text_length = 0; +	aim_rxcallback_t userfunc; +	aim_tlvlist_t *tlvlist; +	aim_tlv_t *tlv; +	aim_snac_t *origsnac = NULL; +	struct aim_priv_inforeq *inforeq; +	int ret = 0; + +	origsnac = aim_remsnac(sess, snac->id); + +	if (!origsnac || !origsnac->data) { +		imcb_error(sess->aux_data, "major problem: no snac stored!"); +		return 0; +	} + +	inforeq = (struct aim_priv_inforeq *)origsnac->data; + +	if ((inforeq->infotype != AIM_GETINFO_GENERALINFO) && +			(inforeq->infotype != AIM_GETINFO_AWAYMESSAGE) && +			(inforeq->infotype != AIM_GETINFO_CAPABILITIES)) { +		imcb_error(sess->aux_data, "unknown infotype in request!"); +		return 0; +	} + +	aim_extractuserinfo(sess, bs, &userinfo); + +	tlvlist = aim_readtlvchain(bs); + +	/*  +	 * Depending on what informational text was requested, different +	 * TLVs will appear here. +	 * +	 * Profile will be 1 and 2, away message will be 3 and 4, caps +	 * will be 5. +	 */ +	if (inforeq->infotype == AIM_GETINFO_GENERALINFO) { +		text_encoding = aim_gettlv_str(tlvlist, 0x0001, 1); +		if((tlv = aim_gettlv(tlvlist, 0x0002, 1))) { +			text = g_new0(char, tlv->length); +			memcpy(text, tlv->value, tlv->length); +			text_length = tlv->length; +		} +	} else if (inforeq->infotype == AIM_GETINFO_AWAYMESSAGE) { +		text_encoding = aim_gettlv_str(tlvlist, 0x0003, 1); +		if((tlv = aim_gettlv(tlvlist, 0x0004, 1))) { +			text = g_new0(char, tlv->length); +			memcpy(text, tlv->value, tlv->length); +			text_length = tlv->length; +		} +	} else if (inforeq->infotype == AIM_GETINFO_CAPABILITIES) { +		aim_tlv_t *ct; + +		if ((ct = aim_gettlv(tlvlist, 0x0005, 1))) { +			aim_bstream_t cbs; + +			aim_bstream_init(&cbs, ct->value, ct->length); + +			userinfo.capabilities = aim_getcap(sess, &cbs, ct->length); +			userinfo.present = AIM_USERINFO_PRESENT_CAPABILITIES; +		} +	} + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, &userinfo, inforeq->infotype, text_encoding, text, text_length); + +	g_free(text_encoding); +	g_free(text); + +	aim_freetlvchain(&tlvlist); + +	if (origsnac) +		g_free(origsnac->data); +	g_free(origsnac); + +	return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0003) +		return rights(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0006) +		return userinfo(sess, mod, rx, snac, bs); + +	return 0; +} + +int locate_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x0002; +	mod->version = 0x0001; +	mod->toolid = 0x0110; +	mod->toolversion = 0x0629; +	mod->flags = 0; +	strncpy(mod->name, "locate", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} diff --git a/protocols/oscar/info.h b/protocols/oscar/info.h new file mode 100644 index 00000000..b4d99e9f --- /dev/null +++ b/protocols/oscar/info.h @@ -0,0 +1,44 @@ +#ifndef __OSCAR_INFO_H__ +#define __OSCAR_INFO_H__ + +#define AIM_CB_FAM_LOC 0x0002 + +/* + * SNAC Family: Location Services. + */  +#define AIM_CB_LOC_ERROR 0x0001 +#define AIM_CB_LOC_REQRIGHTS 0x0002 +#define AIM_CB_LOC_RIGHTSINFO 0x0003 +#define AIM_CB_LOC_SETUSERINFO 0x0004 +#define AIM_CB_LOC_REQUSERINFO 0x0005 +#define AIM_CB_LOC_USERINFO 0x0006 +#define AIM_CB_LOC_WATCHERSUBREQ 0x0007 +#define AIM_CB_LOC_WATCHERNOT 0x0008 +#define AIM_CB_LOC_DEFAULT 0xffff + +#define AIM_CAPS_BUDDYICON      0x00000001 +#define AIM_CAPS_VOICE          0x00000002 +#define AIM_CAPS_IMIMAGE        0x00000004 +#define AIM_CAPS_CHAT           0x00000008 +#define AIM_CAPS_GETFILE        0x00000010 +#define AIM_CAPS_SENDFILE       0x00000020 +#define AIM_CAPS_GAMES          0x00000040 +#define AIM_CAPS_SAVESTOCKS     0x00000080 +#define AIM_CAPS_SENDBUDDYLIST  0x00000100 +#define AIM_CAPS_GAMES2         0x00000200 +#define AIM_CAPS_ICQ            0x00000400 +#define AIM_CAPS_APINFO         0x00000800 +#define AIM_CAPS_ICQRTF	        0x00001000 +#define AIM_CAPS_EMPTY	        0x00002000 +#define AIM_CAPS_ICQSERVERRELAY 0x00004000 +#define AIM_CAPS_ICQUNKNOWN     0x00008000 +#define AIM_CAPS_TRILLIANCRYPT  0x00010000 +#define AIM_CAPS_UTF8           0x00020000 +#define AIM_CAPS_INTEROP        0x00040000 +#define AIM_CAPS_ICHAT          0x00080000 +#define AIM_CAPS_EXTCHAN2       0x00100000 +#define AIM_CAPS_LAST           0x00200000 + +int aim_0002_000b(aim_session_t *sess, aim_conn_t *conn, const char *sn); + +#endif /* __OSCAR_INFO_H__ */ diff --git a/protocols/oscar/misc.c b/protocols/oscar/misc.c new file mode 100644 index 00000000..e5c5c26f --- /dev/null +++ b/protocols/oscar/misc.c @@ -0,0 +1,396 @@ + +/* + * aim_misc.c + * + * TODO: Seperate a lot of this into an aim_bos.c. + * + * Other things... + * + *   - Idle setting  + *  + * + */ + +#include <aim.h>  + +/* + * aim_bos_setbuddylist(buddylist) + * + * This just builds the "set buddy list" command then queues it. + * + * buddy_list = "Screen Name One&ScreenNameTwo&"; + * + * TODO: Clean this up.   + * + * XXX: I can't stress the TODO enough. + * + */ +int aim_bos_setbuddylist(aim_session_t *sess, aim_conn_t *conn, const char *buddy_list) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	int len = 0; +	char *localcpy = NULL; +	char *tmpptr = NULL; + +	if (!buddy_list || !(localcpy = g_strdup(buddy_list)))  +		return -EINVAL; + +	for (tmpptr = strtok(localcpy, "&"); tmpptr; ) { +		len += 1 + strlen(tmpptr); +		tmpptr = strtok(NULL, "&"); +	} + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+len))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0003, 0x0004, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0003, 0x0004, 0x0000, snacid); + +	strncpy(localcpy, buddy_list, strlen(buddy_list) + 1); + +	for (tmpptr = strtok(localcpy, "&"); tmpptr; ) { + +		aimbs_put8(&fr->data, strlen(tmpptr)); +		aimbs_putraw(&fr->data, (guint8 *)tmpptr, strlen(tmpptr)); +		tmpptr = strtok(NULL, "&"); +	} + +	aim_tx_enqueue(sess, fr); + +	g_free(localcpy); + +	return 0; +} + +/*  + * aim_bos_setprofile(profile) + * + * Gives BOS your profile. + *  + */ +int aim_bos_setprofile(aim_session_t *sess, aim_conn_t *conn, const char *profile, const char *awaymsg, guint32 caps) +{ +	static const char defencoding[] = {"text/aolrtf; charset=\"utf-8\""}; +	aim_frame_t *fr; +	aim_tlvlist_t *tl = NULL; +	aim_snacid_t snacid; + +	/* Build to packet first to get real length */ +	if (profile) { +		aim_addtlvtochain_raw(&tl, 0x0001, strlen(defencoding), (guint8 *)defencoding); +		aim_addtlvtochain_raw(&tl, 0x0002, strlen(profile), (guint8 *)profile); +	} + +	/* +	 * So here's how this works: +	 *   - You are away when you have a non-zero-length type 4 TLV stored. +	 *   - You become unaway when you clear the TLV with a zero-length +	 *       type 4 TLV. +	 *   - If you do not send the type 4 TLV, your status does not change +	 *       (that is, if you were away, you'll remain away). +	 */ +	if (awaymsg) { +		if (strlen(awaymsg)) { +			aim_addtlvtochain_raw(&tl, 0x0003, strlen(defencoding), (guint8 *)defencoding); +			aim_addtlvtochain_raw(&tl, 0x0004, strlen(awaymsg), (guint8 *)awaymsg); +		} else +			aim_addtlvtochain_noval(&tl, 0x0004); +	} + +	aim_addtlvtochain_caps(&tl, 0x0005, caps); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + aim_sizetlvchain(&tl)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0002, 0x0004, 0x0000, NULL, 0); +	 +	aim_putsnac(&fr->data, 0x0002, 0x004, 0x0000, snacid); +	aim_writetlvchain(&fr->data, &tl); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * aim_bos_reqbuddyrights() + * + * Request Buddy List rights. + * + */ +int aim_bos_reqbuddyrights(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, 0x0003, 0x0002); +} + +/* + * Send a warning to destsn. + *  + * Flags: + *  AIM_WARN_ANON  Send as an anonymous (doesn't count as much) + * + * returns -1 on error (couldn't alloc packet), 0 on success.  + * + */ +int aim_send_warning(aim_session_t *sess, aim_conn_t *conn, const char *destsn, guint32 flags) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	guint16 outflags = 0x0000; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, strlen(destsn)+13))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0004, 0x0008, 0x0000, destsn, strlen(destsn)+1); + +	aim_putsnac(&fr->data, 0x0004, 0x0008, 0x0000, snacid); + +	if (flags & AIM_WARN_ANON) +		outflags |= 0x0001; + +	aimbs_put16(&fr->data, outflags);  +	aimbs_put8(&fr->data, strlen(destsn)); +	aimbs_putraw(&fr->data, (guint8 *)destsn, strlen(destsn)); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Generic routine for sending commands. + * + * + * I know I can do this in a smarter way...but I'm not thinking straight + * right now... + * + * I had one big function that handled all three cases, but then it broke + * and I split it up into three.  But then I fixed it.  I just never went + * back to the single.  I don't see any advantage to doing it either way. + * + */ +int aim_genericreq_n(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid = 0x00000000; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) +		return -ENOMEM; + +	aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_genericreq_n_snacid(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_genericreq_l(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype, guint32 *longdata) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!longdata) +		return aim_genericreq_n(sess, conn, family, subtype); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4))) +		return -ENOMEM;  + +	snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0); + +	aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); +	aimbs_put32(&fr->data, *longdata); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_genericreq_s(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 subtype, guint16 *shortdata) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!shortdata) +		return aim_genericreq_n(sess, conn, family, subtype); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2))) +		return -ENOMEM;  + +	snacid = aim_cachesnac(sess, family, subtype, 0x0000, NULL, 0); + +	aim_putsnac(&fr->data, family, subtype, 0x0000, snacid); +	aimbs_put16(&fr->data, *shortdata); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * aim_bos_reqlocaterights() + * + * Request Location services rights. + * + */ +int aim_bos_reqlocaterights(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, 0x0002, 0x0002); +} + +/*  + * Set directory profile data (not the same as aim_bos_setprofile!) + * + * privacy: 1 to allow searching, 0 to disallow. + */ +int aim_setdirectoryinfo(aim_session_t *sess, aim_conn_t *conn, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, guint16 privacy)  +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL; + + +	aim_addtlvtochain16(&tl, 0x000a, privacy); + +	if (first) +		aim_addtlvtochain_raw(&tl, 0x0001, strlen(first), (guint8 *)first); +	if (last) +		aim_addtlvtochain_raw(&tl, 0x0002, strlen(last), (guint8 *)last); +	if (middle) +		aim_addtlvtochain_raw(&tl, 0x0003, strlen(middle), (guint8 *)middle); +	if (maiden) +		aim_addtlvtochain_raw(&tl, 0x0004, strlen(maiden), (guint8 *)maiden); + +	if (state) +		aim_addtlvtochain_raw(&tl, 0x0007, strlen(state), (guint8 *)state); +	if (city) +		aim_addtlvtochain_raw(&tl, 0x0008, strlen(city), (guint8 *)city); + +	if (nickname) +		aim_addtlvtochain_raw(&tl, 0x000c, strlen(nickname), (guint8 *)nickname); +	if (zip) +		aim_addtlvtochain_raw(&tl, 0x000d, strlen(zip), (guint8 *)zip); + +	if (street) +		aim_addtlvtochain_raw(&tl, 0x0021, strlen(street), (guint8 *)street); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0002, 0x0009, 0x0000, NULL, 0); +	 +	aim_putsnac(&fr->data, 0x0002, 0x0009, 0x0000, snacid); +	aim_writetlvchain(&fr->data, &tl); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* XXX pass these in better */ +int aim_setuserinterests(aim_session_t *sess, aim_conn_t *conn, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL; + +	/* ?? privacy ?? */ +	aim_addtlvtochain16(&tl, 0x000a, privacy); + +	if (interest1) +		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest1), (guint8 *)interest1); +	if (interest2) +		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest2), (guint8 *)interest2); +	if (interest3) +		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest3), (guint8 *)interest3); +	if (interest4) +		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest4), (guint8 *)interest4); +	if (interest5) +		aim_addtlvtochain_raw(&tl, 0x0000b, strlen(interest5), (guint8 *)interest5); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+aim_sizetlvchain(&tl)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0002, 0x000f, 0x0000, NULL, 0); + +	aim_putsnac(&fr->data, 0x0002, 0x000f, 0x0000, 0); +	aim_writetlvchain(&fr->data, &tl); +	aim_freetlvchain(&tl); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Should be generic enough to handle the errors for all groups. + * + */ +static int generror(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	int error = 0; +	aim_rxcallback_t userfunc; +	aim_snac_t *snac2; + +	snac2 = aim_remsnac(sess, snac->id); + +	if (aim_bstream_empty(bs)) +		error = aimbs_get16(bs); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, error, snac2 ? snac2->data : NULL); + +	if (snac2) +		g_free(snac2->data); +	g_free(snac2); + +	return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0001) +		return generror(sess, mod, rx, snac, bs); +	else if ((snac->family == 0xffff) && (snac->subtype == 0xffff)) { +		aim_rxcallback_t userfunc; + +		if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +			return userfunc(sess, rx); +	} + +	return 0; +} + +int misc_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0xffff; +	mod->version = 0x0000; +	mod->flags = AIM_MODFLAG_MULTIFAMILY; +	strncpy(mod->name, "misc", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} + + diff --git a/protocols/oscar/msgcookie.c b/protocols/oscar/msgcookie.c new file mode 100644 index 00000000..efeb8cbf --- /dev/null +++ b/protocols/oscar/msgcookie.c @@ -0,0 +1,164 @@ +/* + * Cookie Caching stuff. Adam wrote this, apparently just some + * derivatives of n's SNAC work. I cleaned it up, added comments. + *  + */ + +/* + * I'm assuming that cookies are type-specific. that is, we can have + * "1234578" for type 1 and type 2 concurrently. if i'm wrong, then we + * lose some error checking. if we assume cookies are not type-specific and are + * wrong, we get quirky behavior when cookies step on each others' toes. + */ + +#include <aim.h> +#include "info.h" + +/** + * aim_cachecookie - appends a cookie to the cookie list + * @sess: session to add to + * @cookie: pointer to struct to append + * + * if cookie->cookie for type cookie->type is found, updates the + * ->addtime of the found structure; otherwise adds the given cookie + * to the cache + * + * returns -1 on error, 0 on append, 1 on update.  the cookie you pass + * in may be free'd, so don't count on its value after calling this! + *  + */ +int aim_cachecookie(aim_session_t *sess, aim_msgcookie_t *cookie) +{ +	aim_msgcookie_t *newcook; + +	if (!sess || !cookie) +		return -EINVAL; + +	newcook = aim_checkcookie(sess, cookie->cookie, cookie->type); +	 +	if (newcook == cookie) { +		newcook->addtime = time(NULL); +		return 1; +	} else if (newcook) +		aim_cookie_free(sess, newcook); + +	cookie->addtime = time(NULL);   + +	cookie->next = sess->msgcookies; +	sess->msgcookies = cookie; + +	return 0; +} + +/** + * aim_uncachecookie - grabs a cookie from the cookie cache (removes it from the list) + * @sess: session to grab cookie from + * @cookie: cookie string to look for + * @type: cookie type to look for + * + * takes a cookie string and a cookie type and finds the cookie struct associated with that duple, removing it from the cookie list ikn the process. + * + * if found, returns the struct; if none found (or on error), returns NULL: + */ +aim_msgcookie_t *aim_uncachecookie(aim_session_t *sess, guint8 *cookie, int type) +{ +	aim_msgcookie_t *cur, **prev; + +	if (!cookie || !sess->msgcookies) +		return NULL; + +	for (prev = &sess->msgcookies; (cur = *prev); ) { +		if ((cur->type == type) &&  +				(memcmp(cur->cookie, cookie, 8) == 0)) { +			*prev = cur->next; +			return cur; +		} +		prev = &cur->next; +	} + +	return NULL; +} + +/** + * aim_mkcookie - generate an aim_msgcookie_t *struct from a cookie string, a type, and a data pointer. + * @c: pointer to the cookie string array + * @type: cookie type to use + * @data: data to be cached with the cookie + * + * returns NULL on error, a pointer to the newly-allocated cookie on + * success. + * + */ +aim_msgcookie_t *aim_mkcookie(guint8 *c, int type, void *data)  +{ +	aim_msgcookie_t *cookie; + +	if (!c) +		return NULL; + +	if (!(cookie = g_new0(aim_msgcookie_t,1))) +		return NULL; + +	cookie->data = data; +	cookie->type = type; +	memcpy(cookie->cookie, c, 8); + +	return cookie; +} + +/** + * aim_checkcookie - check to see if a cookietuple has been cached + * @sess: session to check for the cookie in + * @cookie: pointer to the cookie string array + * @type: type of the cookie to look for + * + * this returns a pointer to the cookie struct (still in the list) on + * success; returns NULL on error/not found + * + */ + +aim_msgcookie_t *aim_checkcookie(aim_session_t *sess, const guint8 *cookie, int type) +{ +	aim_msgcookie_t *cur; + +	for (cur = sess->msgcookies; cur; cur = cur->next) { +		if ((cur->type == type) &&  +				(memcmp(cur->cookie, cookie, 8) == 0)) +			return cur;    +	} + +	return NULL; +} + +/** + * aim_cookie_free - free an aim_msgcookie_t struct + * @sess: session to remove the cookie from + * @cookiep: the address of a pointer to the cookie struct to remove + * + * this function removes the cookie *cookie from teh list of cookies + * in sess, and then frees all memory associated with it. including + * its data! if you want to use the private data after calling this, + * make sure you copy it first. + * + * returns -1 on error, 0 on success. + * + */ +int aim_cookie_free(aim_session_t *sess, aim_msgcookie_t *cookie)  +{ +	aim_msgcookie_t *cur, **prev; + +	if (!sess || !cookie) +		return -EINVAL; + +	for (prev = &sess->msgcookies; (cur = *prev); ) { +		if (cur == cookie) +			*prev = cur->next; +		else +			prev = &cur->next; +	} + +	g_free(cookie->data); +	g_free(cookie); + +	return 0; +}  diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c new file mode 100644 index 00000000..0d23b7e8 --- /dev/null +++ b/protocols/oscar/oscar.c @@ -0,0 +1,2646 @@ +/* + * gaim + * + * Some code copyright (C) 2002-2006, Jelmer Vernooij <jelmer@samba.org> + *                                    and the BitlBee team. + * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + */ + +#include <errno.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <sys/stat.h> +#include <glib.h> +#include "nogaim.h" +#include "bitlbee.h" +#include "proxy.h" +#include "sock.h" + +#include "aim.h" +#include "icq.h" +#include "bos.h" +#include "ssi.h" +#include "im.h" +#include "info.h" +#include "buddylist.h" +#include "chat.h" +#include "chatnav.h" + +/* constants to identify proto_opts */ +#define USEROPT_AUTH      0 +#define USEROPT_AUTHPORT  1 + +#define UC_AOL		0x02 +#define UC_ADMIN	0x04 +#define UC_UNCONFIRMED	0x08 +#define UC_NORMAL	0x10 +#define UC_AB		0x20 +#define UC_WIRELESS	0x40 + +#define AIMHASHDATA "http://gaim.sourceforge.net/aim_data.php3" + +#define OSCAR_GROUP "Friends" + +#define BUF_LEN 2048 +#define BUF_LONG ( BUF_LEN * 2 ) + +/* Don't know if support for UTF8 is really working. For now it's UTF16 here. +   static int gaim_caps = AIM_CAPS_UTF8; */ + +static int gaim_caps = AIM_CAPS_INTEROP | AIM_CAPS_ICHAT | AIM_CAPS_ICQSERVERRELAY | AIM_CAPS_CHAT; +static guint8 gaim_features[] = {0x01, 0x01, 0x01, 0x02}; + +struct oscar_data { +	aim_session_t *sess; +	aim_conn_t *conn; + +	guint cnpa; +	guint paspa; + +	GSList *create_rooms; + +	gboolean conf; +	gboolean reqemail; +	gboolean setemail; +	char *email; +	gboolean setnick; +	char *newsn; +	gboolean chpass; +	char *oldp; +	char *newp; + +	GSList *oscar_chats; + +	gboolean killme, no_reconnect; +	gboolean icq; +	GSList *evilhack; +	 +	GHashTable *ips; + +	struct { +		guint maxbuddies; /* max users you can watch */ +		guint maxwatchers; /* max users who can watch you */ +		guint maxpermits; /* max users on permit list */ +		guint maxdenies; /* max users on deny list */ +		guint maxsiglen; /* max size (bytes) of profile */ +		guint maxawaymsglen; /* max size (bytes) of posted away message */ +	} rights; +}; + +struct create_room { +	char *name; +	int exchange; +}; + +struct chat_connection { +	char *name; +	char *show; /* AOL did something funny to us */ +	guint16 exchange; +	guint16 instance; +	int fd; /* this is redundant since we have the conn below */ +	aim_conn_t *conn; +	int inpa; +	int id; +	struct im_connection *ic; /* i hate this. */ +	struct groupchat *cnv; /* bah. */ +	int maxlen; +	int maxvis; +}; + +struct ask_direct { +	struct im_connection *ic; +	char *sn; +	char ip[64]; +	guint8 cookie[8]; +}; + +struct icq_auth { +	struct im_connection *ic; +	guint32 uin; +}; + +static char *extract_name(const char *name) { +	char *tmp; +	int i, j; +	char *x = strchr(name, '-'); +	if (!x) return g_strdup(name); +	x = strchr(++x, '-'); +	if (!x) return g_strdup(name); +	tmp = g_strdup(++x); + +	for (i = 0, j = 0; x[i]; i++) { +		char hex[3]; +		if (x[i] != '%') { +			tmp[j++] = x[i]; +			continue; +		} +		strncpy(hex, x + ++i, 2); hex[2] = 0; +		i++; +		tmp[j++] = (char)strtol(hex, NULL, 16); +	} + +	tmp[j] = 0; +	return tmp; +} + +static struct chat_connection *find_oscar_chat_by_conn(struct im_connection *ic, +							aim_conn_t *conn) { +	GSList *g = ((struct oscar_data *)ic->proto_data)->oscar_chats; +	struct chat_connection *c = NULL; + +	while (g) { +		c = (struct chat_connection *)g->data; +		if (c->conn == conn) +			break; +		g = g->next; +		c = NULL; +	} + +	return c; +} + +static int gaim_parse_auth_resp  (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_login      (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_logout     (aim_session_t *, aim_frame_t *, ...); +static int gaim_handle_redirect  (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_oncoming   (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_offgoing   (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_incoming_im(aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_misses     (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_motd       (aim_session_t *, aim_frame_t *, ...); +static int gaim_chatnav_info     (aim_session_t *, aim_frame_t *, ...); +static int gaim_chat_join        (aim_session_t *, aim_frame_t *, ...); +static int gaim_chat_leave       (aim_session_t *, aim_frame_t *, ...); +static int gaim_chat_info_update (aim_session_t *, aim_frame_t *, ...); +static int gaim_chat_incoming_msg(aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_ratechange (aim_session_t *, aim_frame_t *, ...); +static int gaim_bosrights        (aim_session_t *, aim_frame_t *, ...); +static int conninitdone_bos      (aim_session_t *, aim_frame_t *, ...); +static int conninitdone_admin    (aim_session_t *, aim_frame_t *, ...); +static int conninitdone_chat     (aim_session_t *, aim_frame_t *, ...); +static int conninitdone_chatnav  (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_msgerr     (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_locaterights(aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_buddyrights(aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_locerr     (aim_session_t *, aim_frame_t *, ...); +static int gaim_icbm_param_info  (aim_session_t *, aim_frame_t *, ...); +static int gaim_parse_genericerr (aim_session_t *, aim_frame_t *, ...); +static int gaim_selfinfo         (aim_session_t *, aim_frame_t *, ...); +static int gaim_offlinemsg       (aim_session_t *, aim_frame_t *, ...); +static int gaim_offlinemsgdone   (aim_session_t *, aim_frame_t *, ...); +static int gaim_ssi_parserights  (aim_session_t *, aim_frame_t *, ...); +static int gaim_ssi_parselist    (aim_session_t *, aim_frame_t *, ...); +static int gaim_ssi_parseack     (aim_session_t *, aim_frame_t *, ...); +static int gaim_parsemtn         (aim_session_t *, aim_frame_t *, ...); +static int gaim_icqinfo          (aim_session_t *, aim_frame_t *, ...); +static int gaim_parseaiminfo     (aim_session_t *, aim_frame_t *, ...); + +static char *msgerrreason[] = { +	"Invalid error", +	"Invalid SNAC", +	"Rate to host", +	"Rate to client", +	"Not logged in", +	"Service unavailable", +	"Service not defined", +	"Obsolete SNAC", +	"Not supported by host", +	"Not supported by client", +	"Refused by client", +	"Reply too big", +	"Responses lost", +	"Request denied", +	"Busted SNAC payload", +	"Insufficient rights", +	"In local permit/deny", +	"Too evil (sender)", +	"Too evil (receiver)", +	"User temporarily unavailable", +	"No match", +	"List overflow", +	"Request ambiguous", +	"Queue full", +	"Not while on AOL" +}; +static int msgerrreasonlen = 25; + +/* Hurray, this function is NOT thread-safe \o/ */ +static char *normalize(const char *s) +{ +	static char buf[BUF_LEN]; +	char *t, *u; +	int x = 0; + +	g_return_val_if_fail((s != NULL), NULL); + +	u = t = g_strdup(s); +	g_strdown(t); + +	while (*t && (x < BUF_LEN - 1)) { +		if (*t != ' ' && *t != '!') { +			buf[x] = *t; +			x++; +		} +		t++; +	} +	buf[x] = '\0'; +	g_free(u); +	return buf; +} + +static gboolean oscar_callback(gpointer data, gint source, +				b_input_condition condition) { +	aim_conn_t *conn = (aim_conn_t *)data; +	aim_session_t *sess = aim_conn_getsess(conn); +	struct im_connection *ic = sess ? sess->aux_data : NULL; +	struct oscar_data *odata; + +	if (!ic) { +		/* ic is null. we return, else we seg SIGSEG on next line. */ +		return FALSE; +	} +       +	if (!g_slist_find(get_connections(), ic)) { +		/* oh boy. this is probably bad. i guess the only thing we  +		 * can really do is return? */ +		return FALSE; +	} + +	odata = (struct oscar_data *)ic->proto_data; + +	if (condition & B_EV_IO_READ) { +		if (aim_get_command(odata->sess, conn) >= 0) { +			aim_rxdispatch(odata->sess); +                               if (odata->killme) +                                       imc_logout(ic, !odata->no_reconnect); +		} else { +			if ((conn->type == AIM_CONN_TYPE_BOS) || +				   !(aim_getconn_type(odata->sess, AIM_CONN_TYPE_BOS))) { +				imcb_error(ic, _("Disconnected.")); +				imc_logout(ic, TRUE); +			} else if (conn->type == AIM_CONN_TYPE_CHAT) { +				struct chat_connection *c = find_oscar_chat_by_conn(ic, conn); +				c->conn = NULL; +				if (c->inpa > 0) +					b_event_remove(c->inpa); +				c->inpa = 0; +				c->fd = -1; +				aim_conn_kill(odata->sess, &conn); +				imcb_error(sess->aux_data, _("You have been disconnected from chat room %s."), c->name); +			} else if (conn->type == AIM_CONN_TYPE_CHATNAV) { +				if (odata->cnpa > 0) +					b_event_remove(odata->cnpa); +				odata->cnpa = 0; +				while (odata->create_rooms) { +					struct create_room *cr = odata->create_rooms->data; +					g_free(cr->name); +					odata->create_rooms = +						g_slist_remove(odata->create_rooms, cr); +					g_free(cr); +					imcb_error(sess->aux_data, _("Chat is currently unavailable")); +				} +				aim_conn_kill(odata->sess, &conn); +			} else if (conn->type == AIM_CONN_TYPE_AUTH) { +				if (odata->paspa > 0) +					b_event_remove(odata->paspa); +				odata->paspa = 0; +				aim_conn_kill(odata->sess, &conn); +			} else { +				aim_conn_kill(odata->sess, &conn); +			} +		} +	} else { +		/* WTF??? */ +		return FALSE; +	} +		 +	return TRUE; +} + +static gboolean oscar_login_connect(gpointer data, gint source, b_input_condition cond) +{ +	struct im_connection *ic = data; +	struct oscar_data *odata; +	aim_session_t *sess; +	aim_conn_t *conn; + +	if (!g_slist_find(get_connections(), ic)) { +		closesocket(source); +		return FALSE; +	} + +	odata = ic->proto_data; +	sess = odata->sess; +	conn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH); + +	if (source < 0) { +		imcb_error(ic, _("Couldn't connect to host")); +		imc_logout(ic, TRUE); +		return FALSE; +	} + +	aim_conn_completeconnect(sess, conn); +	ic->inpa = b_input_add(conn->fd, B_EV_IO_READ, +			oscar_callback, conn); +	 +	return FALSE; +} + +static void oscar_init(account_t *acc) +{ +	set_t *s; +	gboolean icq = isdigit(acc->user[0]); +	 +	if (icq) { +		set_add(&acc->set, "ignore_auth_requests", "false", set_eval_bool, acc); +		set_add(&acc->set, "old_icq_auth", "false", set_eval_bool, acc); +	} +	 +	s = set_add(&acc->set, "server", +	            icq ? AIM_DEFAULT_LOGIN_SERVER_ICQ +	                : AIM_DEFAULT_LOGIN_SERVER_AIM, set_eval_account, acc); +	s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY; +	 +	if (icq) { +		s = set_add(&acc->set, "web_aware", "false", set_eval_bool, acc); +		s->flags |= ACC_SET_OFFLINE_ONLY; +	} +	 +	acc->flags |= ACC_FLAG_AWAY_MESSAGE; +} + +static void oscar_login(account_t *acc) { +	aim_session_t *sess; +	aim_conn_t *conn; +	struct im_connection *ic = imcb_new(acc); +	struct oscar_data *odata = ic->proto_data = g_new0(struct oscar_data, 1); + +	if (isdigit(acc->user[0])) +		odata->icq = TRUE; +	else +		ic->flags |= OPT_DOES_HTML; + +	sess = g_new0(aim_session_t, 1); + +	aim_session_init(sess, AIM_SESS_FLAGS_NONBLOCKCONNECT, 0); + +	/* we need an immediate queue because we don't use a while-loop to +	 * see if things need to be sent. */ +	aim_tx_setenqueue(sess, AIM_TX_IMMEDIATE, NULL); +	odata->sess = sess; +	sess->aux_data = ic; + +	conn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, NULL); +	if (conn == NULL) { +		imcb_error(ic, _("Unable to login to AIM")); +		imc_logout(ic, TRUE); +		return; +	} +	 +	imcb_log(ic, _("Signon: %s"), ic->acc->user); + +	aim_conn_addhandler(sess, conn, 0x0017, 0x0007, gaim_parse_login, 0); +	aim_conn_addhandler(sess, conn, 0x0017, 0x0003, gaim_parse_auth_resp, 0); + +	conn->status |= AIM_CONN_STATUS_INPROGRESS; +	conn->fd = proxy_connect(set_getstr(&acc->set, "server"), +	                         AIM_LOGIN_PORT, oscar_login_connect, ic); +	if (conn->fd < 0) { +		imcb_error(ic, _("Couldn't connect to host")); +		imc_logout(ic, TRUE); +		return; +	} +	aim_request_login(sess, conn, ic->acc->user); +} + +static void oscar_logout(struct im_connection *ic) { +	struct oscar_data *odata = (struct oscar_data *)ic->proto_data; +	 +	while (odata->oscar_chats) { +		struct chat_connection *n = odata->oscar_chats->data; +		if (n->inpa > 0) +			b_event_remove(n->inpa); +		g_free(n->name); +		g_free(n->show); +		odata->oscar_chats = g_slist_remove(odata->oscar_chats, n); +		g_free(n); +	} +	while (odata->create_rooms) { +		struct create_room *cr = odata->create_rooms->data; +		g_free(cr->name); +		odata->create_rooms = g_slist_remove(odata->create_rooms, cr); +		g_free(cr); +	} +	if (odata->ips) +		g_hash_table_destroy(odata->ips); +	if (odata->email) +		g_free(odata->email); +	if (odata->newp) +		g_free(odata->newp); +	if (odata->oldp) +		g_free(odata->oldp); +	if (ic->inpa > 0) +		b_event_remove(ic->inpa); +	if (odata->cnpa > 0) +		b_event_remove(odata->cnpa); +	if (odata->paspa > 0) +		b_event_remove(odata->paspa); +	aim_session_kill(odata->sess); +	g_free(odata->sess); +	odata->sess = NULL; +	g_free(ic->proto_data); +	ic->proto_data = NULL; +} + +static gboolean oscar_bos_connect(gpointer data, gint source, b_input_condition cond) { +	struct im_connection *ic = data; +	struct oscar_data *odata; +	aim_session_t *sess; +	aim_conn_t *bosconn; + +	if (!g_slist_find(get_connections(), ic)) { +		closesocket(source); +		return FALSE; +	} + +	odata = ic->proto_data; +	sess = odata->sess; +	bosconn = odata->conn; + +	if (source < 0) { +		imcb_error(ic, _("Could Not Connect")); +		imc_logout(ic, TRUE); +		return FALSE; +	} + +	aim_conn_completeconnect(sess, bosconn); +	ic->inpa = b_input_add(bosconn->fd, B_EV_IO_READ, +			oscar_callback, bosconn); +	imcb_log(ic, _("Connection established, cookie sent")); +	 +	return FALSE; +} + +static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	struct aim_authresp_info *info; +	int i; char *host; int port; +	aim_conn_t *bosconn; + +	struct im_connection *ic = sess->aux_data; +        struct oscar_data *od = ic->proto_data; +	port = AIM_LOGIN_PORT; + +	va_start(ap, fr); +	info = va_arg(ap, struct aim_authresp_info *); +	va_end(ap); + +	if (info->errorcode || !info->bosip || !info->cookie) { +		switch (info->errorcode) { +		case 0x05: +			/* Incorrect nick/password */ +			imcb_error(ic, _("Incorrect nickname or password.")); +			{ +				int max = od->icq ? 8 : 16; +				if (strlen(ic->acc->pass) > max) +					imcb_log(ic, "Note that the maximum password " +					         "length supported by this protocol is " +					         "%d characters, try logging in using " +					         "a shorter password.", max); +			} +//			plugin_event(event_error, (void *)980, 0, 0, 0); +			break; +		case 0x11: +			/* Suspended account */ +			imcb_error(ic, _("Your account is currently suspended.")); +			break; +		case 0x18: +			/* connecting too frequently */ +			od->no_reconnect = TRUE; +			imcb_error(ic, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); +			break; +		case 0x1c: +			/* client too old */ +			imcb_error(ic, _("The client version you are using is too old. Please upgrade at " WEBSITE)); +			break; +		default: +			imcb_error(ic, _("Authentication Failed")); +			break; +		} +		od->killme = TRUE; +		return 1; +	} + + +	aim_conn_kill(sess, &fr->conn); + +	bosconn = aim_newconn(sess, AIM_CONN_TYPE_BOS, NULL); +	if (bosconn == NULL) { +		imcb_error(ic, _("Internal Error")); +		od->killme = TRUE; +		return 0; +	} + +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_bos, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_RIGHTS, gaim_bosrights, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ACK, AIM_CB_ACK_ACK, NULL, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_REDIRECT, gaim_handle_redirect, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_RIGHTSINFO, gaim_parse_locaterights, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_RIGHTSINFO, gaim_parse_buddyrights, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ONCOMING, gaim_parse_oncoming, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_OFFGOING, gaim_parse_offgoing, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_INCOMING, gaim_parse_incoming_im, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_ERROR, gaim_parse_locerr, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MISSEDCALL, gaim_parse_misses, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_RATECHANGE, gaim_parse_ratechange, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_ERROR, gaim_parse_msgerr, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_MOTD, gaim_parse_motd, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_PARAMINFO, gaim_icbm_param_info, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_ERROR, gaim_parse_genericerr, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ERROR, gaim_parse_genericerr, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_ERROR, gaim_parse_genericerr, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_SELFINFO, gaim_selfinfo, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG, gaim_offlinemsg, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE, gaim_offlinemsgdone, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_INFO, gaim_icqinfo, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_RIGHTSINFO, gaim_ssi_parserights, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_LIST, gaim_ssi_parselist, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SSI, AIM_CB_SSI_SRVACK, gaim_ssi_parseack, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_LOC, AIM_CB_LOC_USERINFO, gaim_parseaiminfo, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_MSG, AIM_CB_MSG_MTN, gaim_parsemtn, 0); +	aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR, gaim_parse_logout, 0); + +	((struct oscar_data *)ic->proto_data)->conn = bosconn; +	for (i = 0; i < (int)strlen(info->bosip); i++) { +		if (info->bosip[i] == ':') { +			port = atoi(&(info->bosip[i+1])); +			break; +		} +	} +	host = g_strndup(info->bosip, i); +	bosconn->status |= AIM_CONN_STATUS_INPROGRESS; +	bosconn->fd = proxy_connect(host, port, oscar_bos_connect, ic); +	g_free(host); +	if (bosconn->fd < 0) { +		imcb_error(ic, _("Could Not Connect")); +		od->killme = TRUE; +		return 0; +	} +	aim_sendcookie(sess, bosconn, info->cookie); +	b_event_remove(ic->inpa); + +	return 1; +} + +/* size of icbmui.ocm, the largest module in AIM 3.5 */ +#define AIM_MAX_FILE_SIZE 98304 + +static int gaim_parse_login(aim_session_t *sess, aim_frame_t *fr, ...) { +#if 0 +	struct client_info_s info = {"gaim", 4, 1, 2010, "us", "en", 0x0004, 0x0000, 0x04b}; +#else +	struct client_info_s info = AIM_CLIENTINFO_KNOWNGOOD; +#endif +	char *key; +	va_list ap; +	struct im_connection *ic = sess->aux_data; + +	va_start(ap, fr); +	key = va_arg(ap, char *); +	va_end(ap); + +	aim_send_login(sess, fr->conn, ic->acc->user, ic->acc->pass, &info, key); + +	return 1; +} + +static int gaim_parse_logout(aim_session_t *sess, aim_frame_t *fr, ...) { +	struct im_connection *ic = sess->aux_data; +	struct oscar_data *odata = ic->proto_data; +	int code; +	va_list ap; + +	va_start(ap, fr); +	code = va_arg(ap, int); +	va_end(ap); +	 +	imcb_error( ic, "Connection aborted by server: %s", code == 1 ? +	                "someone else logged in with your account" : +	                "unknown reason" ); +	 +	/* Tell BitlBee to disable auto_reconnect if code == 1, since that +	   means a concurrent login somewhere else. */ +	odata->no_reconnect = code == 1; +	 +	/* DO NOT log out here! Just tell the callback to do it. */ +	odata->killme = TRUE; + +	return 1; +} + +static int conninitdone_chat(aim_session_t *sess, aim_frame_t *fr, ...) { +	struct im_connection *ic = sess->aux_data; +	struct chat_connection *chatcon; +	struct groupchat *c = NULL; +	static int id = 1; + +	aim_conn_addhandler(sess, fr->conn, 0x000e, 0x0001, gaim_parse_genericerr, 0); +	aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERJOIN, gaim_chat_join, 0); +	aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_USERLEAVE, gaim_chat_leave, 0); +	aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_ROOMINFOUPDATE, gaim_chat_info_update, 0); +	aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CHT, AIM_CB_CHT_INCOMINGMSG, gaim_chat_incoming_msg, 0); + +	aim_clientready(sess, fr->conn); + +	chatcon = find_oscar_chat_by_conn(ic, fr->conn); +	chatcon->id = id; +	 +	c = bee_chat_by_title(ic->bee, ic, chatcon->show); +	if (c && !c->data) +		chatcon->cnv = c; +	else +		chatcon->cnv = imcb_chat_new(ic, chatcon->show); +	chatcon->cnv->data = chatcon; + +	return 1; +} + +static int conninitdone_chatnav(aim_session_t *sess, aim_frame_t *fr, ...) { + +	aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CTN, AIM_CB_CTN_ERROR, gaim_parse_genericerr, 0); +	aim_conn_addhandler(sess, fr->conn, AIM_CB_FAM_CTN, AIM_CB_CTN_INFO, gaim_chatnav_info, 0); + +	aim_clientready(sess, fr->conn); + +	aim_chatnav_reqrights(sess, fr->conn); + +	return 1; +} + +static gboolean oscar_chatnav_connect(gpointer data, gint source, b_input_condition cond) { +	struct im_connection *ic = data; +	struct oscar_data *odata; +	aim_session_t *sess; +	aim_conn_t *tstconn; + +	if (!g_slist_find(get_connections(), ic)) { +		closesocket(source); +		return FALSE; +	} + +	odata = ic->proto_data; +	sess = odata->sess; +	tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_CHATNAV); + +	if (source < 0) { +		aim_conn_kill(sess, &tstconn); +		return FALSE; +	} + +	aim_conn_completeconnect(sess, tstconn); +	odata->cnpa = b_input_add(tstconn->fd, B_EV_IO_READ, +					oscar_callback, tstconn); +	 +	return FALSE; +} + +static gboolean oscar_auth_connect(gpointer data, gint source, b_input_condition cond) +{ +	struct im_connection *ic = data; +	struct oscar_data *odata; +	aim_session_t *sess; +	aim_conn_t *tstconn; + +	if (!g_slist_find(get_connections(), ic)) { +		closesocket(source); +		return FALSE; +	} + +	odata = ic->proto_data; +	sess = odata->sess; +	tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH); + +	if (source < 0) { +		aim_conn_kill(sess, &tstconn); +		return FALSE; +	} + +	aim_conn_completeconnect(sess, tstconn); +	odata->paspa = b_input_add(tstconn->fd, B_EV_IO_READ, +				oscar_callback, tstconn); +	 +	return FALSE; +} + +static gboolean oscar_chat_connect(gpointer data, gint source, b_input_condition cond) +{ +	struct chat_connection *ccon = data; +	struct im_connection *ic = ccon->ic; +	struct oscar_data *odata; +	aim_session_t *sess; +	aim_conn_t *tstconn; + +	if (!g_slist_find(get_connections(), ic)) { +		closesocket(source); +		g_free(ccon->show); +		g_free(ccon->name); +		g_free(ccon); +		return FALSE; +	} + +	odata = ic->proto_data; +	sess = odata->sess; +	tstconn = ccon->conn; + +	if (source < 0) { +		aim_conn_kill(sess, &tstconn); +		g_free(ccon->show); +		g_free(ccon->name); +		g_free(ccon); +		return FALSE; +	} + +	aim_conn_completeconnect(sess, ccon->conn); +	ccon->inpa = b_input_add(tstconn->fd, +			B_EV_IO_READ, +			oscar_callback, tstconn); +	odata->oscar_chats = g_slist_append(odata->oscar_chats, ccon); +	 +	return FALSE; +} + +/* Hrmph. I don't know how to make this look better. --mid */ +static int gaim_handle_redirect(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	struct aim_redirect_data *redir; +	struct im_connection *ic = sess->aux_data; +	aim_conn_t *tstconn; +	int i; +	char *host; +	int port; + +	va_start(ap, fr); +	redir = va_arg(ap, struct aim_redirect_data *); +	va_end(ap); + +	port = AIM_LOGIN_PORT; +	for (i = 0; i < (int)strlen(redir->ip); i++) { +		if (redir->ip[i] == ':') { +			port = atoi(&(redir->ip[i+1])); +			break; +		} +	} +	host = g_strndup(redir->ip, i); + +	switch(redir->group) { +	case 0x7: /* Authorizer */ +		tstconn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, NULL); +		if (tstconn == NULL) { +			g_free(host); +			return 1; +		} +		aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_admin, 0); +//		aim_conn_addhandler(sess, tstconn, 0x0007, 0x0003, gaim_info_change, 0); +//		aim_conn_addhandler(sess, tstconn, 0x0007, 0x0005, gaim_info_change, 0); +//		aim_conn_addhandler(sess, tstconn, 0x0007, 0x0007, gaim_account_confirm, 0); + +		tstconn->status |= AIM_CONN_STATUS_INPROGRESS; +		tstconn->fd = proxy_connect(host, port, oscar_auth_connect, ic); +		if (tstconn->fd < 0) { +			aim_conn_kill(sess, &tstconn); +			g_free(host); +			return 1; +		} +		aim_sendcookie(sess, tstconn, redir->cookie); +		break; +	case 0xd: /* ChatNav */ +		tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHATNAV, NULL); +		if (tstconn == NULL) { +			g_free(host); +			return 1; +		} +		aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_chatnav, 0); + +		tstconn->status |= AIM_CONN_STATUS_INPROGRESS; +		tstconn->fd = proxy_connect(host, port, oscar_chatnav_connect, ic); +		if (tstconn->fd < 0) { +			aim_conn_kill(sess, &tstconn); +			g_free(host); +			return 1; +		} +		aim_sendcookie(sess, tstconn, redir->cookie); +		break; +	case 0xe: /* Chat */ +		{ +		struct chat_connection *ccon; + +		tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHAT, NULL); +		if (tstconn == NULL) { +			g_free(host); +			return 1; +		} + +		aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, conninitdone_chat, 0); + +		ccon = g_new0(struct chat_connection, 1); +		ccon->conn = tstconn; +		ccon->ic = ic; +		ccon->fd = -1; +		ccon->name = g_strdup(redir->chat.room); +		ccon->exchange = redir->chat.exchange; +		ccon->instance = redir->chat.instance; +		ccon->show = extract_name(redir->chat.room); +		 +		ccon->conn->status |= AIM_CONN_STATUS_INPROGRESS; +		ccon->conn->fd = proxy_connect(host, port, oscar_chat_connect, ccon); +		if (ccon->conn->fd < 0) { +			aim_conn_kill(sess, &tstconn); +			g_free(host); +			g_free(ccon->show); +			g_free(ccon->name); +			g_free(ccon); +			return 1; +		} +		aim_sendcookie(sess, tstconn, redir->cookie); +		} +		break; +	default: /* huh? */ +		break; +	} + +	g_free(host); +	return 1; +} + +static int gaim_parse_oncoming(aim_session_t *sess, aim_frame_t *fr, ...) { +	struct im_connection *ic = sess->aux_data; +	struct oscar_data *od = ic->proto_data; +	aim_userinfo_t *info; +	time_t time_idle = 0, signon = 0; +	int flags = OPT_LOGGED_IN; +	char *tmp, *state_string = NULL; + +	va_list ap; +	va_start(ap, fr); +	info = va_arg(ap, aim_userinfo_t *); +	va_end(ap); + +	if ((!od->icq) && (info->present & AIM_USERINFO_PRESENT_FLAGS)) { +		if (info->flags & AIM_FLAG_AWAY) +			flags |= OPT_AWAY; +	} +	 +	/* Maybe this should be done just for AIM contacts, not sure. */ +	if (info->flags & AIM_FLAG_WIRELESS) +		flags |= OPT_MOBILE; +	 +	if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS) { +		if (!(info->icqinfo.status & AIM_ICQ_STATE_CHAT) && +		      (info->icqinfo.status != AIM_ICQ_STATE_NORMAL)) { +			flags |= OPT_AWAY; +		} +		 +		if( info->icqinfo.status & AIM_ICQ_STATE_DND ) +			state_string = "Do Not Disturb"; +		else if( info->icqinfo.status & AIM_ICQ_STATE_OUT ) +			state_string = "Not Available"; +		else if( info->icqinfo.status & AIM_ICQ_STATE_BUSY ) +			state_string = "Occupied"; +		else if( info->icqinfo.status & AIM_ICQ_STATE_INVISIBLE ) +			state_string = "Invisible"; +	} + +	if (info->present & AIM_USERINFO_PRESENT_IDLE) { +		time(&time_idle); +		time_idle -= info->idletime*60; +	} + +	if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN) +		signon = time(NULL) - info->sessionlen; + +	if (info->present & AIM_USERINFO_PRESENT_ICQIPADDR) { +		uint32_t *uin = g_new0(uint32_t, 1); +		 +		if (od->ips == NULL) +			od->ips = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, NULL); +		 +		if (sscanf(info->sn, "%d", uin) == 1) +			g_hash_table_insert(od->ips, uin, (gpointer) (long) info->icqinfo.ipaddr); +	} + +	if (!aim_sncmp(ic->acc->user, info->sn)) +		g_snprintf(ic->displayname, sizeof(ic->displayname), "%s", info->sn); + +	tmp = normalize(info->sn); +	imcb_buddy_status(ic, tmp, flags, state_string, NULL); +	imcb_buddy_times(ic, tmp, signon, time_idle); + + +	return 1; +} + +static int gaim_parse_offgoing(aim_session_t *sess, aim_frame_t *fr, ...) { +	aim_userinfo_t *info; +	va_list ap; +	struct im_connection *ic = sess->aux_data; + +	va_start(ap, fr); +	info = va_arg(ap, aim_userinfo_t *); +	va_end(ap); + +	imcb_buddy_status(ic, normalize(info->sn), 0, NULL, NULL ); + +	return 1; +} + +static int incomingim_chan1(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch1_args *args) { +	char *tmp = g_malloc(BUF_LONG + 1); +	struct im_connection *ic = sess->aux_data; +	int flags = 0; +	 +	if (args->icbmflags & AIM_IMFLAGS_AWAY) +		flags |= OPT_AWAY; +	 +	if ((args->icbmflags & AIM_IMFLAGS_UNICODE) || (args->icbmflags & AIM_IMFLAGS_ISO_8859_1)) { +		char *src; +		 +		if (args->icbmflags & AIM_IMFLAGS_UNICODE) +			src = "UNICODEBIG"; +		else +			src = "ISO8859-1"; +		 +		/* Try to use iconv first to convert the message to UTF8 - which is what BitlBee expects */ +		if (do_iconv(src, "UTF-8", args->msg, tmp, args->msglen, BUF_LONG) >= 0) { +			// Successfully converted! +		} else if (args->icbmflags & AIM_IMFLAGS_UNICODE) { +			int i; +			 +			for (i = 0, tmp[0] = '\0'; i < args->msglen; i += 2) { +				unsigned short uni; +				 +				uni = ((args->msg[i] & 0xff) << 8) | (args->msg[i+1] & 0xff); +	 +				if ((uni < 128) || ((uni >= 160) && (uni <= 255))) { /* ISO 8859-1 */ +					g_snprintf(tmp+strlen(tmp), BUF_LONG-strlen(tmp), "%c", uni); +				} else { /* something else, do UNICODE entity */ +					g_snprintf(tmp+strlen(tmp), BUF_LONG-strlen(tmp), "&#%04x;", uni); +				} +			} +		} else { +			g_snprintf(tmp, BUF_LONG, "%s", args->msg); +		} +	} else if (args->mpmsg.numparts == 0) { +		g_snprintf(tmp, BUF_LONG, "%s", args->msg); +	} else { +		aim_mpmsg_section_t *part; +		 +		*tmp = 0; +		for (part = args->mpmsg.parts; part; part = part->next) { +			if (part->data) { +				g_strlcat(tmp, (char*) part->data, BUF_LONG); +				g_strlcat(tmp, "\n", BUF_LONG); +			} +		} +	} +	 +	strip_linefeed(tmp); +	imcb_buddy_msg(ic, normalize(userinfo->sn), tmp, flags, 0); +	g_free(tmp); +	 +	return 1; +} + +void oscar_accept_chat(void *data); +void oscar_reject_chat(void *data); +	 +static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args) { +	struct im_connection *ic = sess->aux_data; + +	if (args->status != AIM_RENDEZVOUS_PROPOSE) +		return 1; + +	if (args->reqclass & AIM_CAPS_CHAT) { +		char *name = extract_name(args->info.chat.roominfo.name); +		int *exch = g_new0(int, 1); +		GList *m = NULL; +		char txt[1024]; +		struct aim_chat_invitation * inv = g_new0(struct aim_chat_invitation, 1); + +		m = g_list_append(m, g_strdup(name ? name : args->info.chat.roominfo.name)); +		*exch = args->info.chat.roominfo.exchange; +		m = g_list_append(m, exch); + +		g_snprintf(txt, 1024, "Got an invitation to chatroom %s from %s: %s", name, userinfo->sn, args->msg); + +		inv->ic = ic; +		inv->exchange = *exch; +		inv->name = g_strdup(name); +		 +		imcb_ask(ic, txt, inv, oscar_accept_chat, oscar_reject_chat); +	 +		if (name) +			g_free(name); +	} else if (args->reqclass & AIM_CAPS_ICQRTF) { +		// TODO: constify +		char text[strlen(args->info.rtfmsg.rtfmsg)+1]; +		strncpy(text, args->info.rtfmsg.rtfmsg, sizeof(text)); +		imcb_buddy_msg(ic, normalize(userinfo->sn), text, 0, 0); +	} + +	return 1; +} + +static void gaim_icq_authgrant(void *data_) { +	struct icq_auth *data = data_; +	char *uin, message; +	struct oscar_data *od = (struct oscar_data *)data->ic->proto_data; +	 +	uin = g_strdup_printf("%u", data->uin); +	message = 0; +	aim_ssi_auth_reply(od->sess, od->conn, uin, 1, ""); +	// aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHGRANTED, &message); +	imcb_ask_add(data->ic, uin, NULL); +	 +	g_free(uin); +	g_free(data); +} + +static void gaim_icq_authdeny(void *data_) { +	struct icq_auth *data = data_; +	char *uin, *message; +	struct oscar_data *od = (struct oscar_data *)data->ic->proto_data; +	 +	uin = g_strdup_printf("%u", data->uin); +	message = g_strdup_printf("No reason given."); +	aim_ssi_auth_reply(od->sess, od->conn, uin, 0, ""); +	// aim_send_im_ch4(od->sess, uin, AIM_ICQMSG_AUTHDENIED, message); +	g_free(message); +	 +	g_free(uin); +	g_free(data); +} + +/* + * For when other people ask you for authorization + */ +static void gaim_icq_authask(struct im_connection *ic, guint32 uin, char *msg) { +	struct icq_auth *data; +	char *reason = NULL; +	char *dialog_msg; + +	if (set_getbool(&ic->acc->set, "ignore_auth_requests")) +		return; +	 +	data = g_new(struct icq_auth, 1); + +	if (strlen(msg) > 6) +		reason = msg + 6; +	 +	dialog_msg = g_strdup_printf("The user %u wants to add you to their buddy list for the following reason: %s", uin, reason ? reason : "No reason given."); +	data->ic = ic; +	data->uin = uin; +	imcb_ask(ic, dialog_msg, data, gaim_icq_authgrant, gaim_icq_authdeny); +	g_free(dialog_msg); +} + +static int incomingim_chan4(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch4_args *args) { +	struct im_connection *ic = sess->aux_data; + +	switch (args->type) { +		case 0x0001: { /* An almost-normal instant message.  Mac ICQ sends this.  It's peculiar. */ +			char *uin, *message; +			uin = g_strdup_printf("%u", args->uin); +			message = g_strdup(args->msg); +			strip_linefeed(message); +			imcb_buddy_msg(ic, normalize(uin), message, 0, 0); +			g_free(uin); +			g_free(message); +		} break; + +		case 0x0004: { /* Someone sent you a URL */ +		  	char *uin, *message; +			char **m; +	 +			uin = g_strdup_printf("%u", args->uin); +			m = g_strsplit(args->msg, "\376", 2); + +			if ((strlen(m[0]) != 0)) { +			  message = g_strjoinv(" -- ", m); +			} else { +			  message = m[1]; +			} + +			strip_linefeed(message); +			imcb_buddy_msg(ic, normalize(uin), message, 0, 0); +			g_free(uin); +			g_free(m); +			g_free(message); +		} break; +		 +		case 0x0006: { /* Someone requested authorization */ +			gaim_icq_authask(ic, args->uin, args->msg); +		} break; + +		case 0x0007: { /* Someone has denied you authorization */ +			imcb_log(sess->aux_data, "The user %u has denied your request to add them to your contact list for the following reason:\n%s", args->uin, args->msg ? args->msg : _("No reason given.") ); +		} break; + +		case 0x0008: { /* Someone has granted you authorization */ +			imcb_log(sess->aux_data, "The user %u has granted your request to add them to your contact list for the following reason:\n%s", args->uin, args->msg ? args->msg : _("No reason given.") ); +		} break; + +		case 0x0012: { +			/* Ack for authorizing/denying someone.  Or possibly an ack for sending any system notice */ +		} break; + +		default: {; +		} break; +	} + +	return 1; +} + +static int gaim_parse_incoming_im(aim_session_t *sess, aim_frame_t *fr, ...) { +	int channel, ret = 0; +	aim_userinfo_t *userinfo; +	va_list ap; + +	va_start(ap, fr); +	channel = va_arg(ap, int); +	userinfo = va_arg(ap, aim_userinfo_t *); + +	switch (channel) { +		case 1: { /* standard message */ +			struct aim_incomingim_ch1_args *args; +			args = va_arg(ap, struct aim_incomingim_ch1_args *); +			ret = incomingim_chan1(sess, fr->conn, userinfo, args); +		} break; + +		case 2: { /* rendevous */ +			struct aim_incomingim_ch2_args *args; +			args = va_arg(ap, struct aim_incomingim_ch2_args *); +			ret = incomingim_chan2(sess, fr->conn, userinfo, args); +		} break; + +		case 4: { /* ICQ */ +			struct aim_incomingim_ch4_args *args; +			args = va_arg(ap, struct aim_incomingim_ch4_args *); +			ret = incomingim_chan4(sess, fr->conn, userinfo, args); +		} break; + +		default: {; +		} break; +	} + +	va_end(ap); + +	return ret; +} + +static int gaim_parse_misses(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	guint16 chan, nummissed, reason; +	aim_userinfo_t *userinfo; + +	va_start(ap, fr); +	chan = (guint16)va_arg(ap, unsigned int); +	userinfo = va_arg(ap, aim_userinfo_t *); +	nummissed = (guint16)va_arg(ap, unsigned int); +	reason = (guint16)va_arg(ap, unsigned int); +	va_end(ap); + +	switch(reason) { +		case 0: +			/* Invalid (0) */ +			imcb_error(sess->aux_data, +				   nummissed == 1 ?  +				   _("You missed %d message from %s because it was invalid.") : +				   _("You missed %d messages from %s because they were invalid."), +				   nummissed, +				   userinfo->sn); +			break; +		case 1: +			/* Message too large */ +			imcb_error(sess->aux_data, +				   nummissed == 1 ? +				   _("You missed %d message from %s because it was too large.") : +				   _("You missed %d messages from %s because they were too large."), +				   nummissed, +				   userinfo->sn); +			break; +		case 2: +			/* Rate exceeded */ +			imcb_error(sess->aux_data, +				   nummissed == 1 ?  +				   _("You missed %d message from %s because the rate limit has been exceeded.") : +				   _("You missed %d messages from %s because the rate limit has been exceeded."), +				   nummissed, +				   userinfo->sn); +			break; +		case 3: +			/* Evil Sender */ +			imcb_error(sess->aux_data, +				   nummissed == 1 ? +				   _("You missed %d message from %s because it was too evil.") :  +				   _("You missed %d messages from %s because they are too evil."), +				   nummissed, +				   userinfo->sn); +			break; +		case 4: +			/* Evil Receiver */ +			imcb_error(sess->aux_data, +				   nummissed == 1 ?  +				   _("You missed %d message from %s because you are too evil.") : +				   _("You missed %d messages from %s because you are too evil."), +				   nummissed, +				   userinfo->sn); +			break; +		default: +			imcb_error(sess->aux_data, +				   nummissed == 1 ?  +				   _("You missed %d message from %s for unknown reasons.") : +				   _("You missed %d messages from %s for unknown reasons."), +				   nummissed, +				   userinfo->sn); +			break; +	} + +	return 1; +} + +static int gaim_parse_genericerr(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	guint16 reason; + +	va_start(ap, fr); +	reason = (guint16)va_arg(ap, unsigned int); +	va_end(ap); + +	imcb_error(sess->aux_data, _("SNAC threw error: %s"), +	          reason < msgerrreasonlen ? msgerrreason[reason] : "Unknown error"); + +	return 1; +} + +static int gaim_parse_msgerr(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	char *destn; +	guint16 reason; + +	va_start(ap, fr); +	reason = (guint16)va_arg(ap, unsigned int); +	destn = va_arg(ap, char *); +	va_end(ap); + +	imcb_error(sess->aux_data, _("Your message to %s did not get sent: %s"), destn, +			(reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown")); + +	return 1; +} + +static int gaim_parse_locerr(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	char *destn; +	guint16 reason; + +	va_start(ap, fr); +	reason = (guint16)va_arg(ap, unsigned int); +	destn = va_arg(ap, char *); +	va_end(ap); + +	imcb_error(sess->aux_data, _("User information for %s unavailable: %s"), destn, +			(reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown")); + +	return 1; +} + +static int gaim_parse_motd(aim_session_t *sess, aim_frame_t *fr, ...) { +	char *msg; +	guint16 id; +	va_list ap; + +	va_start(ap, fr); +	id  = (guint16)va_arg(ap, unsigned int); +	msg = va_arg(ap, char *); +	va_end(ap); + +	if (id < 4) +		imcb_error(sess->aux_data, _("Your connection may be lost.")); + +	return 1; +} + +static int gaim_chatnav_info(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	guint16 type; +	struct im_connection *ic = sess->aux_data; +	struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + +	va_start(ap, fr); +	type = (guint16)va_arg(ap, unsigned int); + +	switch(type) { +		case 0x0002: { +			guint8 maxrooms; +			struct aim_chat_exchangeinfo *exchanges; +			int exchangecount; // i; + +			maxrooms = (guint8)va_arg(ap, unsigned int); +			exchangecount = va_arg(ap, int); +			exchanges = va_arg(ap, struct aim_chat_exchangeinfo *); +			va_end(ap); + +			while (odata->create_rooms) { +				struct create_room *cr = odata->create_rooms->data; +				aim_chatnav_createroom(sess, fr->conn, cr->name, cr->exchange); +				g_free(cr->name); +				odata->create_rooms = g_slist_remove(odata->create_rooms, cr); +				g_free(cr); +			} +			} +			break; +		case 0x0008: { +			char *fqcn, *name, *ck; +			guint16 instance, flags, maxmsglen, maxoccupancy, unknown, exchange; +			guint8 createperms; +			guint32 createtime; + +			fqcn = va_arg(ap, char *); +			instance = (guint16)va_arg(ap, unsigned int); +			exchange = (guint16)va_arg(ap, unsigned int); +			flags = (guint16)va_arg(ap, unsigned int); +			createtime = va_arg(ap, guint32); +			maxmsglen = (guint16)va_arg(ap, unsigned int); +			maxoccupancy = (guint16)va_arg(ap, unsigned int); +			createperms = (guint8)va_arg(ap, int); +			unknown = (guint16)va_arg(ap, unsigned int); +			name = va_arg(ap, char *); +			ck = va_arg(ap, char *); +			va_end(ap); + +			aim_chat_join(odata->sess, odata->conn, exchange, ck, instance); +			} +			break; +		default: +			va_end(ap); +			break; +	} +	return 1; +} + +static int gaim_chat_join(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	int count, i; +	aim_userinfo_t *info; +	struct im_connection *g = sess->aux_data; + +	struct chat_connection *c = NULL; + +	va_start(ap, fr); +	count = va_arg(ap, int); +	info  = va_arg(ap, aim_userinfo_t *); +	va_end(ap); + +	c = find_oscar_chat_by_conn(g, fr->conn); +	if (!c) +		return 1; + +	for (i = 0; i < count; i++) +		imcb_chat_add_buddy(c->cnv, normalize(info[i].sn)); + +	return 1; +} + +static int gaim_chat_leave(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	int count, i; +	aim_userinfo_t *info; +	struct im_connection *g = sess->aux_data; + +	struct chat_connection *c = NULL; + +	va_start(ap, fr); +	count = va_arg(ap, int); +	info  = va_arg(ap, aim_userinfo_t *); +	va_end(ap); + +	c = find_oscar_chat_by_conn(g, fr->conn); +	if (!c) +		return 1; + +	for (i = 0; i < count; i++) +		imcb_chat_remove_buddy(c->cnv, normalize(info[i].sn), NULL); + +	return 1; +} + +static int gaim_chat_info_update(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	aim_userinfo_t *userinfo; +	struct aim_chat_roominfo *roominfo; +	char *roomname; +	int usercount; +	char *roomdesc; +	guint16 unknown_c9, unknown_d2, unknown_d5, maxmsglen, maxvisiblemsglen; +	guint32 creationtime; +	struct im_connection *ic = sess->aux_data; +	struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn); + +	va_start(ap, fr); +	roominfo = va_arg(ap, struct aim_chat_roominfo *); +	roomname = va_arg(ap, char *); +	usercount= va_arg(ap, int); +	userinfo = va_arg(ap, aim_userinfo_t *); +	roomdesc = va_arg(ap, char *); +	unknown_c9 = (guint16)va_arg(ap, int); +	creationtime = (guint32)va_arg(ap, unsigned long); +	maxmsglen = (guint16)va_arg(ap, int); +	unknown_d2 = (guint16)va_arg(ap, int); +	unknown_d5 = (guint16)va_arg(ap, int); +	maxvisiblemsglen = (guint16)va_arg(ap, int); +	va_end(ap); + +	ccon->maxlen = maxmsglen; +	ccon->maxvis = maxvisiblemsglen; + +	return 1; +} + +static int gaim_chat_incoming_msg(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	aim_userinfo_t *info; +	char *msg; +	struct im_connection *ic = sess->aux_data; +	struct chat_connection *ccon = find_oscar_chat_by_conn(ic, fr->conn); +	char *tmp; + +	va_start(ap, fr); +	info = va_arg(ap, aim_userinfo_t *); +	msg  = va_arg(ap, char *); + +	tmp = g_malloc(BUF_LONG); +	g_snprintf(tmp, BUF_LONG, "%s", msg); +	imcb_chat_msg(ccon->cnv, normalize(info->sn), tmp, 0, 0); +	g_free(tmp); + +	return 1; +} + +static int gaim_parse_ratechange(aim_session_t *sess, aim_frame_t *fr, ...) { +#if 0 +	static const char *codes[5] = { +		"invalid", +		 "change", +		 "warning", +		 "limit", +		 "limit cleared", +	}; +#endif +	va_list ap; +	guint16 code, rateclass; +	guint32 windowsize, clear, alert, limit, disconnect, currentavg, maxavg; + +	va_start(ap, fr);  +	code = (guint16)va_arg(ap, unsigned int); +	rateclass= (guint16)va_arg(ap, unsigned int); +	windowsize = (guint32)va_arg(ap, unsigned long); +	clear = (guint32)va_arg(ap, unsigned long); +	alert = (guint32)va_arg(ap, unsigned long); +	limit = (guint32)va_arg(ap, unsigned long); +	disconnect = (guint32)va_arg(ap, unsigned long); +	currentavg = (guint32)va_arg(ap, unsigned long); +	maxavg = (guint32)va_arg(ap, unsigned long); +	va_end(ap); + +	/* XXX fix these values */ +	if (code == AIM_RATE_CODE_CHANGE) { +		if (currentavg >= clear) +			aim_conn_setlatency(fr->conn, 0); +	} else if (code == AIM_RATE_CODE_WARNING) { +		aim_conn_setlatency(fr->conn, windowsize/4); +	} else if (code == AIM_RATE_CODE_LIMIT) { +		imcb_error(sess->aux_data, _("The last message was not sent because you are over the rate limit. " +			  "Please wait 10 seconds and try again.")); +		aim_conn_setlatency(fr->conn, windowsize/2); +	} else if (code == AIM_RATE_CODE_CLEARLIMIT) { +		aim_conn_setlatency(fr->conn, 0); +	} + +	return 1; +} + +static int gaim_selfinfo(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	aim_userinfo_t *info; +	struct im_connection *ic = sess->aux_data; + +	va_start(ap, fr); +	info = va_arg(ap, aim_userinfo_t *); +	va_end(ap); + +	ic->evil = info->warnlevel/10; +	/* ic->correction_time = (info->onlinesince - ic->login_time); */ + +	return 1; +} + +static int conninitdone_bos(aim_session_t *sess, aim_frame_t *fr, ...) { + +	aim_reqpersonalinfo(sess, fr->conn); +	aim_bos_reqlocaterights(sess, fr->conn); +	aim_bos_reqbuddyrights(sess, fr->conn); + +	aim_reqicbmparams(sess); + +	aim_bos_reqrights(sess, fr->conn); +	aim_bos_setgroupperm(sess, fr->conn, AIM_FLAG_ALLUSERS); +	aim_bos_setprivacyflags(sess, fr->conn, AIM_PRIVFLAGS_ALLOWIDLE | +						     AIM_PRIVFLAGS_ALLOWMEMBERSINCE); + +	return 1; +} + +static int conninitdone_admin(aim_session_t *sess, aim_frame_t *fr, ...) { +	struct im_connection *ic = sess->aux_data; +	struct oscar_data *od = ic->proto_data; + +	aim_clientready(sess, fr->conn); + +	if (od->chpass) { +		aim_admin_changepasswd(sess, fr->conn, od->newp, od->oldp); +		g_free(od->oldp); +		od->oldp = NULL; +		g_free(od->newp); +		od->newp = NULL; +		od->chpass = FALSE; +	} +	if (od->setnick) { +		aim_admin_setnick(sess, fr->conn, od->newsn); +		g_free(od->newsn); +		od->newsn = NULL; +		od->setnick = FALSE; +	} +	if (od->conf) { +		aim_admin_reqconfirm(sess, fr->conn); +		od->conf = FALSE; +	} +	if (od->reqemail) { +		aim_admin_getinfo(sess, fr->conn, 0x0011); +		od->reqemail = FALSE; +	} +	if (od->setemail) { +		aim_admin_setemail(sess, fr->conn, od->email); +		g_free(od->email); +		od->setemail = FALSE; +	} + +	return 1; +} + +static int gaim_icbm_param_info(aim_session_t *sess, aim_frame_t *fr, ...) { +	struct aim_icbmparameters *params; +	va_list ap; + +	va_start(ap, fr); +	params = va_arg(ap, struct aim_icbmparameters *); +	va_end(ap); + +	/* Maybe senderwarn and recverwarn should be user preferences... */ +	params->flags = 0x0000000b; +	params->maxmsglen = 8000; +	params->minmsginterval = 0; + +	aim_seticbmparam(sess, params); + +	return 1; +} + +static int gaim_parse_locaterights(aim_session_t *sess, aim_frame_t *fr, ...) +{ +	va_list ap; +	guint16 maxsiglen; +	struct im_connection *ic = sess->aux_data; +	struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + +	va_start(ap, fr); +	maxsiglen = va_arg(ap, int); +	va_end(ap); + +	odata->rights.maxsiglen = odata->rights.maxawaymsglen = (guint)maxsiglen; + +	/* FIXME: It seems we're not really using this, and it broke now that +	   struct aim_user is dead. +	aim_bos_setprofile(sess, fr->conn, ic->user->user_info, NULL, gaim_caps); +	*/ +	 +	return 1; +} + +static int gaim_parse_buddyrights(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	guint16 maxbuddies, maxwatchers; +	struct im_connection *ic = sess->aux_data; +	struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + +	va_start(ap, fr); +	maxbuddies = (guint16)va_arg(ap, unsigned int); +	maxwatchers = (guint16)va_arg(ap, unsigned int); +	va_end(ap); + +	odata->rights.maxbuddies = (guint)maxbuddies; +	odata->rights.maxwatchers = (guint)maxwatchers; + +	return 1; +} + +static int gaim_bosrights(aim_session_t *sess, aim_frame_t *fr, ...) { +	guint16 maxpermits, maxdenies; +	va_list ap; +	struct im_connection *ic = sess->aux_data; +	struct oscar_data *odata = (struct oscar_data *)ic->proto_data; + +	va_start(ap, fr); +	maxpermits = (guint16)va_arg(ap, unsigned int); +	maxdenies = (guint16)va_arg(ap, unsigned int); +	va_end(ap); + +	odata->rights.maxpermits = (guint)maxpermits; +	odata->rights.maxdenies = (guint)maxdenies; + +	aim_clientready(sess, fr->conn); + +	aim_reqservice(sess, fr->conn, AIM_CONN_TYPE_CHATNAV); + +	aim_ssi_reqrights(sess, fr->conn); +	aim_ssi_reqalldata(sess, fr->conn); + +	return 1; +} + +static int gaim_offlinemsg(aim_session_t *sess, aim_frame_t *fr, ...) { +	va_list ap; +	struct aim_icq_offlinemsg *msg; +	struct im_connection *ic = sess->aux_data; + +	va_start(ap, fr); +	msg = va_arg(ap, struct aim_icq_offlinemsg *); +	va_end(ap); + +	switch (msg->type) { +		case 0x0001: { /* Basic offline message */ +			char sender[32]; +			char *dialog_msg = g_strdup(msg->msg); +			time_t t = get_time(msg->year, msg->month, msg->day, msg->hour, msg->minute, 0); +			g_snprintf(sender, sizeof(sender), "%u", msg->sender); +			strip_linefeed(dialog_msg); +			imcb_buddy_msg(ic, normalize(sender), dialog_msg, 0, t); +			g_free(dialog_msg); +		} break; + +		case 0x0004: { /* Someone sent you a URL */ +		  	char sender[32]; +		  	char *dialog_msg; +			char **m; + +			time_t t = get_time(msg->year, msg->month, msg->day, msg->hour, msg->minute, 0); +			g_snprintf(sender, sizeof(sender), "%u", msg->sender); + +			m = g_strsplit(msg->msg, "\376", 2); + +			if ((strlen(m[0]) != 0)) { +			  dialog_msg = g_strjoinv(" -- ", m); +			} else { +			  dialog_msg = m[1]; +			} + +			strip_linefeed(dialog_msg); +			imcb_buddy_msg(ic, normalize(sender), dialog_msg, 0, t); +			g_free(dialog_msg); +			g_free(m); +		} break; +		 +		case 0x0006: { /* Authorization request */ +			gaim_icq_authask(ic, msg->sender, msg->msg); +		} break; + +		case 0x0007: { /* Someone has denied you authorization */ +			imcb_log(sess->aux_data, "The user %u has denied your request to add them to your contact list for the following reason:\n%s", msg->sender, msg->msg ? msg->msg : _("No reason given.") ); +		} break; + +		case 0x0008: { /* Someone has granted you authorization */ +			imcb_log(sess->aux_data, "The user %u has granted your request to add them to your contact list for the following reason:\n%s", msg->sender, msg->msg ? msg->msg : _("No reason given.") ); +		} break; + +		case 0x0012: { +			/* Ack for authorizing/denying someone.  Or possibly an ack for sending any system notice */ +		} break; + +		default: {; +		} +	} + +	return 1; +} + +static int gaim_offlinemsgdone(aim_session_t *sess, aim_frame_t *fr, ...) +{ +	aim_icq_ackofflinemsgs(sess); +	return 1; +} + +static void oscar_keepalive(struct im_connection *ic) { +	struct oscar_data *odata = (struct oscar_data *)ic->proto_data; +	aim_flap_nop(odata->sess, odata->conn); +} + +static int oscar_buddy_msg(struct im_connection *ic, char *name, char *message, int imflags) { +	struct oscar_data *odata = (struct oscar_data *)ic->proto_data; +	int ret = 0, len = strlen(message); +	if (imflags & OPT_AWAY) { +		ret = aim_send_im(odata->sess, name, AIM_IMFLAGS_AWAY, message); +	} else { +		struct aim_sendimext_args args; +		char *s; +		 +		args.flags = AIM_IMFLAGS_ACK; +		if (odata->icq) +			args.flags |= AIM_IMFLAGS_OFFLINE; +		for (s = message; *s; s++) +			if (*s & 128) +				break; +		 +		/* Message contains high ASCII chars, time for some translation! */ +		if (*s) { +			s = g_malloc(BUF_LONG); +			/* Try if we can put it in an ISO8859-1 string first. +			   If we can't, fall back to UTF16. */ +			if ((ret = do_iconv("UTF-8", "ISO8859-1", message, s, len, BUF_LONG)) >= 0) { +				args.flags |= AIM_IMFLAGS_ISO_8859_1; +				len = ret; +			} else if ((ret = do_iconv("UTF-8", "UNICODEBIG", message, s, len, BUF_LONG)) >= 0) { +				args.flags |= AIM_IMFLAGS_UNICODE; +				len = ret; +			} else { +				/* OOF, translation failed... Oh well.. */ +				g_free( s ); +				s = message; +			} +		} else { +			s = message; +		} +		 +		args.features = gaim_features; +		args.featureslen = sizeof(gaim_features); +		 +		args.destsn = name; +		args.msg    = s; +		args.msglen = len; +		 +		ret = aim_send_im_ext(odata->sess, &args); +		 +		if (s != message) { +			g_free(s); +		} +	} +	if (ret >= 0) +		return 1; +	return ret; +} + +static void oscar_get_info(struct im_connection *g, char *name) { +	struct oscar_data *odata = (struct oscar_data *)g->proto_data; +	if (odata->icq) +		aim_icq_getallinfo(odata->sess, name); +	else { +		aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_AWAYMESSAGE); +		aim_getinfo(odata->sess, odata->conn, name, AIM_GETINFO_GENERALINFO); +	} +} + +static void oscar_get_away(struct im_connection *g, char *who) { +	struct oscar_data *odata = (struct oscar_data *)g->proto_data; +	if (odata->icq) { +		/** FIXME(wilmer): Hmm, lost the ability to get away msgs here, do we care to get that back? +		struct buddy *budlight = imcb_find_buddy(g, who); +		if (budlight) +			if ((budlight->uc & 0xff80) >> 7) +				if (budlight->caps & AIM_CAPS_ICQSERVERRELAY) +					aim_send_im_ch2_geticqmessage(odata->sess, who, (budlight->uc & 0xff80) >> 7); +		*/ +	} else +		aim_getinfo(odata->sess, odata->conn, who, AIM_GETINFO_AWAYMESSAGE); +} + +static void oscar_set_away_aim(struct im_connection *ic, struct oscar_data *od, const char *state, const char *message) +{ +	if (state == NULL) +		state = ""; + +	if (!g_strcasecmp(state, _("Visible"))) { +		aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); +		return; +	} else if (!g_strcasecmp(state, _("Invisible"))) { +		aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_INVISIBLE); +		return; +	} else if (message == NULL) { +		message = state; +	} + +	if (od->rights.maxawaymsglen == 0) +		imcb_error(ic, "oscar_set_away_aim called before locate rights received"); + +	aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); + +	g_free(ic->away); +	ic->away = NULL; + +	if (!message) { +		aim_bos_setprofile(od->sess, od->conn, NULL, "", gaim_caps); +		return; +	} + +	if (strlen(message) > od->rights.maxawaymsglen) { +		imcb_error(ic, "Maximum away message length of %d bytes exceeded, truncating", od->rights.maxawaymsglen); +	} + +	ic->away = g_strndup(message, od->rights.maxawaymsglen); +	aim_bos_setprofile(od->sess, od->conn, NULL, ic->away, gaim_caps); + +	return; +} + +static void oscar_set_away_icq(struct im_connection *ic, struct oscar_data *od, const char *state, const char *message) +{ +	const char *msg = NULL; +	gboolean no_message = FALSE; + +	/* clean old states */ +	g_free(ic->away); +	ic->away = NULL; +	od->sess->aim_icq_state = 0; + +	/* if no message, then use an empty message */ +	if (message) { +		msg = message; +	} else { +		msg = ""; +		no_message = TRUE; +	} + +	if (state == NULL) { +		aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); +	} else if (!g_strcasecmp(state, "Away")) { +		aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY); +		ic->away = g_strdup(msg); +		od->sess->aim_icq_state = AIM_MTYPE_AUTOAWAY; +	} else if (!g_strcasecmp(state, "Do Not Disturb")) { +		aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_DND | AIM_ICQ_STATE_BUSY); +		ic->away = g_strdup(msg); +		od->sess->aim_icq_state = AIM_MTYPE_AUTODND; +	} else if (!g_strcasecmp(state, "Not Available")) { +		aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_OUT | AIM_ICQ_STATE_AWAY); +		ic->away = g_strdup(msg); +		od->sess->aim_icq_state = AIM_MTYPE_AUTONA; +	} else if (!g_strcasecmp(state, "Occupied")) { +		aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_BUSY); +		ic->away = g_strdup(msg); +		od->sess->aim_icq_state = AIM_MTYPE_AUTOBUSY; +	} else if (!g_strcasecmp(state, "Free For Chat")) { +		aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_CHAT); +		ic->away = g_strdup(msg); +		od->sess->aim_icq_state = AIM_MTYPE_AUTOFFC; +	} else if (!g_strcasecmp(state, "Invisible")) { +		aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_INVISIBLE); +		ic->away = g_strdup(msg); +	} else { +	 	if (no_message) { +			aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL); +		} else { +			aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY); +			ic->away = g_strdup(msg); +			od->sess->aim_icq_state = AIM_MTYPE_AUTOAWAY; +		} +	} + +	return; +} + +static void oscar_set_away(struct im_connection *ic, char *state, char *message) +{ +	struct oscar_data *od = (struct oscar_data *)ic->proto_data; + +	oscar_set_away_aim(ic, od, state, message); +	if (od->icq) +		oscar_set_away_icq(ic, od, state, message); + +	return; +} + +static void oscar_add_buddy(struct im_connection *g, char *name, char *group) { +	struct oscar_data *odata = (struct oscar_data *)g->proto_data; +	bee_user_t *bu; +	 +	if (group && (bu = bee_user_by_handle(g->bee, g, name)) && bu->group) +		aim_ssi_movebuddy(odata->sess, odata->conn, bu->group->name, group, name); +	else +		aim_ssi_addbuddies(odata->sess, odata->conn, group ? : OSCAR_GROUP, &name, 1, 0); +} + +static void oscar_remove_buddy(struct im_connection *g, char *name, char *group) { +	struct oscar_data *odata = (struct oscar_data *)g->proto_data; +	struct aim_ssi_item *ssigroup; +	while ((ssigroup = aim_ssi_itemlist_findparent(odata->sess->ssi.items, name)) && !aim_ssi_delbuddies(odata->sess, odata->conn, ssigroup->name, &name, 1)); +} + +static int gaim_ssi_parserights(aim_session_t *sess, aim_frame_t *fr, ...) { +	return 1; +} + +static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) { +	struct im_connection *ic = sess->aux_data; +	struct aim_ssi_item *curitem, *curgroup = NULL; +	int tmp; +	char *nrm; + +	/* Add from server list to local list */ +	tmp = 0; +	for (curitem=sess->ssi.items; curitem; curitem=curitem->next) { +		nrm = curitem->name ? normalize(curitem->name) : NULL; +		 +		switch (curitem->type) { +			case 0x0000: /* Buddy */ +				if ((curitem->name) && (!imcb_buddy_by_handle(ic, nrm))) { +					char *realname = NULL; + +					if (curitem->data && aim_gettlv(curitem->data, 0x0131, 1)) +						    realname = aim_gettlv_str(curitem->data, 0x0131, 1); +					 +					imcb_add_buddy(ic, nrm, curgroup ? (curgroup->gid == curitem->gid ? curgroup->name : NULL) : NULL); +					 +					if (realname) { +						imcb_buddy_nick_hint(ic, nrm, realname); +						imcb_rename_buddy(ic, nrm, realname); +						g_free(realname); +					} +				} +				break; + +			case 0x0001: /* Group */ +				curgroup = curitem; +				break; + +			case 0x0002: /* Permit buddy */ +				if (curitem->name) { +					GSList *list; +					for (list=ic->permit; (list && aim_sncmp(curitem->name, list->data)); list=list->next); +					if (!list) { +						char *name; +						name = g_strdup(nrm); +						ic->permit = g_slist_append(ic->permit, name); +						tmp++; +					} +				} +				break; + +			case 0x0003: /* Deny buddy */ +				if (curitem->name) { +					GSList *list; +					for (list=ic->deny; (list && aim_sncmp(curitem->name, list->data)); list=list->next); +					if (!list) { +						char *name; +						name = g_strdup(nrm); +						ic->deny = g_slist_append(ic->deny, name); +						tmp++; +					} +				} +				break; + +			case 0x0004: /* Permit/deny setting */ +				if (curitem->data) { +					guint8 permdeny; +					if ((permdeny = aim_ssi_getpermdeny(sess->ssi.items)) && (permdeny != ic->permdeny)) { +						ic->permdeny = permdeny; +						tmp++; +					} +				} +				break; + +			case 0x0005: /* Presence setting */ +				/* We don't want to change Gaim's setting because it applies to all accounts */ +				break; +		} /* End of switch on curitem->type */ +	} /* End of for loop */ + +	aim_ssi_enable(sess, fr->conn); +	 +	/* Request offline messages, now that the buddy list is complete. */ +	aim_icq_reqofflinemsgs(sess); +	 +	/* Now that we have a buddy list, we can tell BitlBee that we're online. */ +	imcb_connected(ic); +	 +	return 1; +} + +static int gaim_ssi_parseack( aim_session_t *sess, aim_frame_t *fr, ... ) +{ +	aim_snac_t *origsnac; +	va_list ap; + +	va_start( ap, fr ); +	origsnac = va_arg( ap, aim_snac_t * ); +	va_end( ap ); +	 +	if( origsnac && origsnac->family == AIM_CB_FAM_SSI && origsnac->type == AIM_CB_SSI_ADD && origsnac->data ) +	{ +		int i, st, count = aim_bstream_empty( &fr->data ); +		char *list; +		 +		if( count & 1 ) +		{ +			/* Hmm, the length should be even... */ +			imcb_error( sess->aux_data, "Received SSI ACK package with non-even length"); +			return( 0 ); +		} +		count >>= 1; +		 +		list = (char *) origsnac->data; +		for( i = 0; i < count; i ++ ) +		{ +			struct aim_ssi_item *ssigroup = aim_ssi_itemlist_findparent( sess->ssi.items, list ); +			char *group = ssigroup ? ssigroup->name : NULL; +			 +			st = aimbs_get16( &fr->data ); +			if( st == 0x00 ) +			{ +				imcb_add_buddy( sess->aux_data, normalize(list), group ); +			} +			else if( st == 0x0E ) +			{ +				imcb_log( sess->aux_data, "Buddy %s can't be added without authorization, requesting authorization", list ); +				 +				aim_ssi_auth_request( sess, fr->conn, list, "" ); +				aim_ssi_addbuddies( sess, fr->conn, OSCAR_GROUP, &list, 1, 1 ); +			} +			else +			{ +				imcb_error( sess->aux_data, "Error while adding buddy: 0x%04x", st ); +			} +			list += strlen( list ) + 1; +		} +	} +	 +	return( 1 ); +} + +static void oscar_set_permit_deny(struct im_connection *ic) { +	struct oscar_data *od = (struct oscar_data *)ic->proto_data; +	if (od->icq) { +		GSList *list; +		char buf[MAXMSGLEN]; +		int at; + +		switch(ic->permdeny) { +		case 1: +			aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, ic->acc->user); +			break; +		case 2: +			aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, ic->acc->user); +			break; +		case 3: +			list = ic->permit; +			at = 0; +			while (list) { +				at += g_snprintf(buf + at, sizeof(buf) - at, "%s&", (char *)list->data); +				list = list->next; +			} +			aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, buf); +			break; +		case 4: +			list = ic->deny; +			at = 0; +			while (list) { +				at += g_snprintf(buf + at, sizeof(buf) - at, "%s&", (char *)list->data); +				list = list->next; +			} +			aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, buf); +			break; +			default: +			break; +		} +	} else { +		if (od->sess->ssi.received_data) +			aim_ssi_setpermdeny(od->sess, od->conn, ic->permdeny, 0xffffffff); +	} +} + +static void oscar_add_permit(struct im_connection *ic, char *who) { +	struct oscar_data *od = (struct oscar_data *)ic->proto_data; +	if (od->icq) { +		aim_ssi_auth_reply(od->sess, od->conn, who, 1, ""); +	} else { +		if (od->sess->ssi.received_data) +			aim_ssi_addpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_PERMIT); +	} +} + +static void oscar_add_deny(struct im_connection *ic, char *who) { +	struct oscar_data *od = (struct oscar_data *)ic->proto_data; +	if (od->icq) { +		aim_ssi_auth_reply(od->sess, od->conn, who, 0, ""); +	} else { +		if (od->sess->ssi.received_data) +			aim_ssi_addpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_DENY); +	} +} + +static void oscar_rem_permit(struct im_connection *ic, char *who) { +	struct oscar_data *od = (struct oscar_data *)ic->proto_data; +	if (!od->icq) { +		if (od->sess->ssi.received_data) +			aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_PERMIT); +	} +} + +static void oscar_rem_deny(struct im_connection *ic, char *who) { +	struct oscar_data *od = (struct oscar_data *)ic->proto_data; +	if (!od->icq) { +		if (od->sess->ssi.received_data) +			aim_ssi_delpord(od->sess, od->conn, &who, 1, AIM_SSI_TYPE_DENY); +	} +} + +static GList *oscar_away_states(struct im_connection *ic) +{ +	struct oscar_data *od = ic->proto_data; + +	if (od->icq) { +		static GList *m = NULL; +		m = g_list_append(m, "Away"); +		m = g_list_append(m, "Do Not Disturb"); +		m = g_list_append(m, "Not Available"); +		m = g_list_append(m, "Occupied"); +		m = g_list_append(m, "Free For Chat"); +		m = g_list_append(m, "Invisible"); +		return m; +	} else { +		static GList *m = NULL; +		m = g_list_append(m, "Away"); +		return m; +	} +} + +static int gaim_icqinfo(aim_session_t *sess, aim_frame_t *fr, ...) +{ +	struct im_connection *ic = sess->aux_data; +	struct oscar_data *od = ic->proto_data; +	gchar who[16]; +	GString *str; +	va_list ap; +	struct aim_icq_info *info; +	uint32_t ip; + +	va_start(ap, fr); +	info = va_arg(ap, struct aim_icq_info *); +	va_end(ap); + +	if (!info->uin) +		return 0; + +	str = g_string_sized_new(512); +	g_snprintf(who, sizeof(who), "%u", info->uin); + +	g_string_printf(str, "%s: %s - %s: %s", _("UIN"), who, _("Nick"),  +	info->nick ? info->nick : "-"); +	g_string_append_printf(str, "\n%s: %s", _("First Name"), info->first); +	g_string_append_printf(str, "\n%s: %s", _("Last Name"), info->last); +	g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email); +	if (info->numaddresses && info->email2) { +		int i; +		for (i = 0; i < info->numaddresses; i++) { +			g_string_append_printf(str, "\n%s: %s", _("Email Address"), info->email2[i]); +		} +	} +	if (od->ips && (ip = (long) g_hash_table_lookup(od->ips, &info->uin)) != 0) { +		g_string_append_printf(str, "\n%s: %d.%d.%d.%d", _("Last used IP address"), +		                       (ip >> 24), (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); +	} +	g_string_append_printf(str, "\n%s: %s", _("Mobile Phone"), info->mobile); +	if (info->gender != 0) +		g_string_append_printf(str, "\n%s: %s", _("Gender"), info->gender==1 ? _("Female") : _("Male")); +	if (info->birthyear || info->birthmonth || info->birthday) { +		char date[30]; +		struct tm tm; +		memset(&tm, 0, sizeof(struct tm)); +		tm.tm_mday = (int)info->birthday; +		tm.tm_mon = (int)info->birthmonth-1; +		tm.tm_year = (int)info->birthyear%100; +		strftime(date, sizeof(date), "%Y-%m-%d", &tm); +		g_string_append_printf(str, "\n%s: %s", _("Birthday"), date); +	} +	if (info->age) { +		char age[5]; +		g_snprintf(age, sizeof(age), "%hhd", info->age); +		g_string_append_printf(str, "\n%s: %s", _("Age"), age); +	} +	g_string_append_printf(str, "\n%s: %s", _("Personal Web Page"), info->personalwebpage); +	if (info->info && info->info[0]) { +		g_string_sprintfa(str, "\n%s:\n%s\n%s", _("Additional Information"),  +		info->info, _("End of Additional Information")); +	} +	g_string_append_c(str, '\n'); +	if ((info->homeaddr && (info->homeaddr[0])) || (info->homecity && info->homecity[0]) || (info->homestate && info->homestate[0]) || (info->homezip && info->homezip[0])) { +		g_string_append_printf(str, "%s:", _("Home Address")); +		g_string_append_printf(str, "\n%s: %s", _("Address"), info->homeaddr); +		g_string_append_printf(str, "\n%s: %s", _("City"), info->homecity); +		g_string_append_printf(str, "\n%s: %s", _("State"), info->homestate);  +		g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->homezip); +		g_string_append_c(str, '\n'); +	} +	if ((info->workaddr && info->workaddr[0]) || (info->workcity && info->workcity[0]) || (info->workstate && info->workstate[0]) || (info->workzip && info->workzip[0])) { +		g_string_append_printf(str, "%s:", _("Work Address")); +		g_string_append_printf(str, "\n%s: %s", _("Address"), info->workaddr); +		g_string_append_printf(str, "\n%s: %s", _("City"), info->workcity); +		g_string_append_printf(str, "\n%s: %s", _("State"), info->workstate); +		g_string_append_printf(str, "\n%s: %s", _("Zip Code"), info->workzip); +		g_string_append_c(str, '\n'); +	} +	if ((info->workcompany && info->workcompany[0]) || (info->workdivision && info->workdivision[0]) || (info->workposition && info->workposition[0]) || (info->workwebpage && info->workwebpage[0])) { +		g_string_append_printf(str, "%s:", _("Work Information")); +		g_string_append_printf(str, "\n%s: %s", _("Company"), info->workcompany); +		g_string_append_printf(str, "\n%s: %s", _("Division"), info->workdivision); +		g_string_append_printf(str, "\n%s: %s", _("Position"), info->workposition); +		if (info->workwebpage && info->workwebpage[0]) { +			g_string_append_printf(str, "\n%s: %s", _("Web Page"), info->workwebpage); +		} +		g_string_append_c(str, '\n'); +	} + +	imcb_log(ic, "%s\n%s", _("User Info"), str->str); +	g_string_free(str, TRUE); + +	return 1; + +} + +static char *oscar_encoding_extract(const char *encoding) +{ +	char *ret = NULL; +	char *begin, *end; + +	g_return_val_if_fail(encoding != NULL, NULL); + +	/* Make sure encoding begins with charset= */ +	if (strncmp(encoding, "text/plain; charset=", 20) && +		strncmp(encoding, "text/aolrtf; charset=", 21) && +		strncmp(encoding, "text/x-aolrtf; charset=", 23)) +	{ +		return NULL; +	} + +	begin = strchr(encoding, '"'); +	end = strrchr(encoding, '"'); + +	if ((begin == NULL) || (end == NULL) || (begin >= end)) +		return NULL; + +	ret = g_strndup(begin+1, (end-1) - begin); + +	return ret; +} + +static char *oscar_encoding_to_utf8(char *encoding, char *text, int textlen) +{ +	char *utf8 = g_new0(char, 8192); + +	if ((encoding == NULL) || encoding[0] == '\0') { +		/*		gaim_debug_info("oscar", "Empty encoding, assuming UTF-8\n");*/ +	} else if (!g_strcasecmp(encoding, "iso-8859-1")) { +		do_iconv("iso-8859-1", "UTF-8", text, utf8, textlen, 8192); +	} else if (!g_strcasecmp(encoding, "ISO-8859-1-Windows-3.1-Latin-1")) { +		do_iconv("Windows-1252", "UTF-8", text, utf8, textlen, 8192); +	} else if (!g_strcasecmp(encoding, "unicode-2-0")) { +		do_iconv("UCS-2BE", "UTF-8", text, utf8, textlen, 8192); +	} else if (g_strcasecmp(encoding, "us-ascii") && strcmp(encoding, "utf-8")) { +		/*		gaim_debug_warning("oscar", "Unrecognized character encoding \"%s\", " +		  "attempting to convert to UTF-8 anyway\n", encoding);*/ +		do_iconv(encoding, "UTF-8", text, utf8, textlen, 8192); +	} + +	/* +	 * If utf8 is still NULL then either the encoding is us-ascii/utf-8 or +	 * we have been unable to convert the text to utf-8 from the encoding +	 * that was specified.  So we assume it's UTF-8 and hope for the best. +	 */ +	if (*utf8 == 0) { +	    strncpy(utf8, text, textlen); +	} + +	return utf8; +} + +static int gaim_parseaiminfo(aim_session_t *sess, aim_frame_t *fr, ...) +{ +	struct im_connection *ic = sess->aux_data; +	va_list ap; +	aim_userinfo_t *userinfo; +	guint16 infotype; +	char *text_encoding = NULL, *text = NULL, *extracted_encoding = NULL; +	guint16 text_length; +	char *utf8 = NULL; + +	va_start(ap, fr); +	userinfo = va_arg(ap, aim_userinfo_t *); +	infotype = va_arg(ap, int); +	text_encoding = va_arg(ap, char*); +	text = va_arg(ap, char*); +	text_length = va_arg(ap, int); +	va_end(ap); + +	if(text_encoding) +		extracted_encoding = oscar_encoding_extract(text_encoding); +	if(infotype == AIM_GETINFO_GENERALINFO) { +		/*Display idle time*/ +		char buff[256]; +		struct tm idletime; +		if(userinfo->idletime) { +			memset(&idletime, 0, sizeof(struct tm)); +			idletime.tm_mday = (userinfo->idletime / 60) / 24; +			idletime.tm_hour = (userinfo->idletime / 60) % 24; +			idletime.tm_min = userinfo->idletime % 60; +			idletime.tm_sec = 0; +			strftime(buff, 256, _("%d days %H hours %M minutes"), &idletime); +			imcb_log(ic, "%s: %s", _("Idle Time"), buff); +		} +		 +		if(text) { +			utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length); +			imcb_log(ic, "%s\n%s", _("User Info"), utf8); +		} else { +			imcb_log(ic, _("No user info available.")); +		} +	} else if(infotype == AIM_GETINFO_AWAYMESSAGE && userinfo->flags & AIM_FLAG_AWAY) { +		utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length); +		imcb_log(ic, "%s\n%s", _("Away Message"), utf8); +	} + +	g_free(utf8); +    +	return 1; +} + +int gaim_parsemtn(aim_session_t *sess, aim_frame_t *fr, ...) +{ +	struct im_connection * ic = sess->aux_data; +	va_list ap; +	guint16 type1, type2; +	char * sn; + +	va_start(ap, fr); +	type1 = va_arg(ap, int); +	sn = va_arg(ap, char*); +	type2 = va_arg(ap, int); +	va_end(ap); +     +	if(type2 == 0x0002) { +		/* User is typing */ +		imcb_buddy_typing(ic, normalize(sn), OPT_TYPING); +	}  +	else if (type2 == 0x0001) { +		/* User has typed something, but is not actively typing (stale) */ +		imcb_buddy_typing(ic, normalize(sn), OPT_THINKING); +	} +	else { +		/* User has stopped typing */ +		imcb_buddy_typing(ic, normalize(sn), 0); +	} +	 +	return 1; +} + +int oscar_send_typing(struct im_connection *ic, char * who, int typing) +{ +	struct oscar_data *od = ic->proto_data; +	return( aim_im_sendmtn(od->sess, 1, who, (typing & OPT_TYPING) ? 0x0002 : 0x0000) ); +} + +void oscar_chat_msg(struct groupchat *c, char *message, int msgflags) +{ +	struct im_connection *ic = c->ic; +	struct oscar_data * od = (struct oscar_data*)ic->proto_data; +	struct chat_connection * ccon; +	int ret; +	guint8 len = strlen(message); +	guint16 flags; +	char *s; +	 +	if (!(ccon = c->data)) +		return; +	  	 +	for (s = message; *s; s++) +		if (*s & 128) +			break; +	 +	flags = AIM_CHATFLAGS_NOREFLECT; +	 +	/* Message contains high ASCII chars, time for some translation! */ +	if (*s) { +		s = g_malloc(BUF_LONG); +		/* Try if we can put it in an ISO8859-1 string first. +		   If we can't, fall back to UTF16. */ +		if ((ret = do_iconv("UTF-8", "ISO8859-1", message, s, len, BUF_LONG)) >= 0) { +			flags |= AIM_CHATFLAGS_ISO_8859_1; +			len = ret; +		} else if ((ret = do_iconv("UTF-8", "UNICODEBIG", message, s, len, BUF_LONG)) >= 0) { +			flags |= AIM_CHATFLAGS_UNICODE; +			len = ret; +		} else { +			/* OOF, translation failed... Oh well.. */ +			g_free( s ); +			s = message; +		} +	} else { +		s = message; +	} +	  	 +	ret = aim_chat_send_im(od->sess, ccon->conn, flags, s, len); +	  	 +	if (s != message) {	 +		g_free(s); +  } +   +/*  return (ret >= 0); */ +} + +void oscar_chat_invite(struct groupchat *c, char *who, char *message) +{ +	struct im_connection *ic = c->ic; +	struct oscar_data * od = (struct oscar_data *)ic->proto_data; +	struct chat_connection *ccon; +	 +	if (!(ccon = c->data)) +		return; +	 +	aim_chat_invite(od->sess, od->conn, who, message ? message : "", +					ccon->exchange, ccon->name, 0x0); +} + +void oscar_chat_kill(struct im_connection *ic, struct chat_connection *cc) +{ +	struct oscar_data *od = (struct oscar_data *)ic->proto_data; + +	/* Notify the conversation window that we've left the chat */ +	imcb_chat_free(cc->cnv); + +	/* Destroy the chat_connection */ +	od->oscar_chats = g_slist_remove(od->oscar_chats, cc); +	if (cc->inpa > 0) +		b_event_remove(cc->inpa); +	aim_conn_kill(od->sess, &cc->conn); +	g_free(cc->name); +	g_free(cc->show); +	g_free(cc); +} + +void oscar_chat_leave(struct groupchat *c) +{ +	if (!c->data) +		return; +	oscar_chat_kill(c->ic, c->data); +} + +struct groupchat *oscar_chat_join_internal(struct im_connection *ic, const char *room, +	const char *nick, const char *password, int exchange_number) +{ +	struct oscar_data * od = (struct oscar_data *)ic->proto_data; +	struct groupchat *ret = imcb_chat_new(ic, room); +	aim_conn_t * cur; + +	if((cur = aim_getconn_type(od->sess, AIM_CONN_TYPE_CHATNAV))) { +		int st; +		 +		st = aim_chatnav_createroom(od->sess, cur, room, exchange_number); +		 +		return ret; +	} else { +		struct create_room * cr = g_new0(struct create_room, 1); +		 +		cr->exchange = exchange_number; +		cr->name = g_strdup(room); +		od->create_rooms = g_slist_append(od->create_rooms, cr); +		aim_reqservice(od->sess, od->conn, AIM_CONN_TYPE_CHATNAV); +		 +		return ret; +	} +} + +struct groupchat *oscar_chat_join(struct im_connection *ic, const char *room, +	const char *nick, const char *password, set_t **sets) +{ +	return oscar_chat_join_internal(ic, room, nick, password, set_getint(sets, "exchange_number")); +} + +struct groupchat *oscar_chat_with(struct im_connection * ic, char *who) +{ +	struct oscar_data * od = (struct oscar_data *)ic->proto_data; +	struct groupchat *ret; +	static int chat_id = 0; +	char * chatname, *s; +	struct groupchat *c; +	 +	chatname = g_strdup_printf("%s%s%d", isdigit(*ic->acc->user) ? "icq" : "", +	                           ic->acc->user, chat_id++); +	 +	for (s = chatname; *s; s ++) +		if (!isalnum(*s)) +			*s = '0'; +	 +	c = imcb_chat_new(ic, chatname); +	ret = oscar_chat_join_internal(ic, chatname, NULL, NULL, 4); +	aim_chat_invite(od->sess, od->conn, who, "", 4, chatname, 0x0); + +	g_free(chatname); +	 +	return NULL; +} + +void oscar_accept_chat(void *data) +{ +	struct aim_chat_invitation * inv = data; +	 +	oscar_chat_join_internal(inv->ic, inv->name, NULL, NULL, 4); +	g_free(inv->name); +	g_free(inv); +} + +void oscar_reject_chat(void *data) +{ +	struct aim_chat_invitation * inv = data; +	 +	g_free(inv->name); +	g_free(inv); +} + +void oscar_chat_add_settings(account_t *acc, set_t **head) +{ +	set_add(head, "exchange_number", "4", set_eval_int, NULL); +} + +void oscar_chat_free_settings(account_t *acc, set_t **head) +{ +	set_del(head, "exchange_number"); +} + +void oscar_initmodule()  +{ +	struct prpl *ret = g_new0(struct prpl, 1); +	ret->name = "oscar"; +    ret->mms = 2343;       /* this guess taken from libotr UPGRADING file */ +	ret->away_states = oscar_away_states; +	ret->init = oscar_init; +	ret->login = oscar_login; +	ret->keepalive = oscar_keepalive; +	ret->logout = oscar_logout; +	ret->buddy_msg = oscar_buddy_msg; +	ret->get_info = oscar_get_info; +	ret->set_away = oscar_set_away; +	ret->get_away = oscar_get_away; +	ret->add_buddy = oscar_add_buddy; +	ret->remove_buddy = oscar_remove_buddy; +	ret->chat_msg = oscar_chat_msg; +	ret->chat_invite = oscar_chat_invite; +	ret->chat_leave = oscar_chat_leave; +	ret->chat_with = oscar_chat_with; +	ret->chat_join = oscar_chat_join; +	ret->chat_add_settings = oscar_chat_add_settings; +	ret->chat_free_settings = oscar_chat_free_settings; +	ret->add_permit = oscar_add_permit; +	ret->add_deny = oscar_add_deny; +	ret->rem_permit = oscar_rem_permit; +	ret->rem_deny = oscar_rem_deny; +	ret->set_permit_deny = oscar_set_permit_deny; +	ret->send_typing = oscar_send_typing; +	 +	ret->handle_cmp = aim_sncmp; + +	register_protocol(ret); +} diff --git a/protocols/oscar/oscar_util.c b/protocols/oscar/oscar_util.c new file mode 100644 index 00000000..0ce06bd9 --- /dev/null +++ b/protocols/oscar/oscar_util.c @@ -0,0 +1,161 @@ +#include <aim.h> +#include <ctype.h> + +int aimutil_putstr(u_char *dest, const char *src, int len) +{ +	memcpy(dest, src, len); +	return len; +} + +/* + * Tokenizing functions.  Used to portably replace strtok/sep. + *   -- DMP. + * + */ +int aimutil_tokslen(char *toSearch, int index, char dl) +{ +	int curCount = 1; +	char *next; +	char *last; +	int toReturn; + +	last = toSearch; +	next = strchr(toSearch, dl); + +	while(curCount < index && next != NULL) { +		curCount++; +		last = next + 1; +		next = strchr(last, dl); +	} + +	if ((curCount < index) || (next == NULL)) +		toReturn = strlen(toSearch) - (curCount - 1); +	else +		toReturn = next - toSearch - (curCount - 1); + +	return toReturn; +} + +int aimutil_itemcnt(char *toSearch, char dl) +{ +	int curCount; +	char *next; + +	curCount = 1; + +	next = strchr(toSearch, dl); + +	while(next != NULL) { +		curCount++; +		next = strchr(next + 1, dl); +	} + +	return curCount; +} + +char *aimutil_itemidx(char *toSearch, int index, char dl) +{ +	int curCount; +	char *next; +	char *last; +	char *toReturn; + +	curCount = 0; + +	last = toSearch; +	next = strchr(toSearch, dl); + +	while (curCount < index && next != NULL) { +		curCount++; +		last = next + 1; +		next = strchr(last, dl); +	} + +	if (curCount < index) { +		toReturn = g_strdup(""); +	} +	next = strchr(last, dl); + +	if (curCount < index) { +		toReturn = g_strdup(""); +	} else { +		if (next == NULL) { +			toReturn = g_malloc((strlen(last) + 1) * sizeof(char)); +			strcpy(toReturn, last); +		} else { +			toReturn = g_malloc((next - last + 1) * sizeof(char)); +			memcpy(toReturn, last, (next - last)); +			toReturn[next - last] = '\0'; +		} +	} +	return toReturn; +} + +/* +* int snlen(const char *) +*  +* This takes a screen name and returns its length without +* spaces.  If there are no spaces in the SN, then the  +* return is equal to that of strlen(). +* +*/ +static int aim_snlen(const char *sn) +{ +	int i = 0; +	const char *curPtr = NULL; + +	if (!sn) +		return 0; + +	curPtr = sn; +	while ( (*curPtr) != (char) '\0') { +		if ((*curPtr) != ' ') +		i++; +		curPtr++; +	} + +	return i; +} + +/* +* int sncmp(const char *, const char *) +* +* This takes two screen names and compares them using the rules +* on screen names for AIM/AOL.  Mainly, this means case and space +* insensitivity (all case differences and spacing differences are +* ignored). +* +* Return: 0 if equal +*     non-0 if different +* +*/ + +int aim_sncmp(const char *sn1, const char *sn2) +{ +	const char *curPtr1 = NULL, *curPtr2 = NULL; + +	if (aim_snlen(sn1) != aim_snlen(sn2)) +		return 1; + +	curPtr1 = sn1; +	curPtr2 = sn2; +	while ( (*curPtr1 != (char) '\0') && (*curPtr2 != (char) '\0') ) { +		if ( (*curPtr1 == ' ') || (*curPtr2 == ' ') ) { +			if (*curPtr1 == ' ') +				curPtr1++; +			if (*curPtr2 == ' ') +				curPtr2++; +		} else { +			if ( toupper(*curPtr1) != toupper(*curPtr2)) +				return 1; +			curPtr1++; +			curPtr2++; +		} +	} + +	/* Should both be NULL */ +	if (*curPtr1 != *curPtr2) +		return 1; + +	return 0; +} diff --git a/protocols/oscar/rxhandlers.c b/protocols/oscar/rxhandlers.c new file mode 100644 index 00000000..7014e693 --- /dev/null +++ b/protocols/oscar/rxhandlers.c @@ -0,0 +1,373 @@ +/* + * aim_rxhandlers.c + * + * This file contains most all of the incoming packet handlers, along + * with aim_rxdispatch(), the Rx dispatcher.  Queue/list management is + * actually done in aim_rxqueue.c. + * + */ + +#include <aim.h> + +struct aim_rxcblist_s { +	guint16 family; +	guint16 type; +	aim_rxcallback_t handler; +	u_short flags; +	struct aim_rxcblist_s *next; +}; + +aim_module_t *aim__findmodulebygroup(aim_session_t *sess, guint16 group) +{ +	aim_module_t *cur; + +	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { +		if (cur->family == group) +			return cur; +	} + +	return NULL; +} + +static aim_module_t *aim__findmodule(aim_session_t *sess, const char *name) +{ +	aim_module_t *cur; + +	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { +		if (strcmp(name, cur->name) == 0) +			return cur; +	} + +	return NULL; +} + +int aim__registermodule(aim_session_t *sess, int (*modfirst)(aim_session_t *, aim_module_t *)) +{ +	aim_module_t *mod; + +	if (!sess || !modfirst) +		return -1; + +	if (!(mod = g_new0(aim_module_t,1))) +		return -1; + +	if (modfirst(sess, mod) == -1) { +		g_free(mod); +		return -1; +	} + +	if (aim__findmodule(sess, mod->name)) { +		if (mod->shutdown) +			mod->shutdown(sess, mod); +		g_free(mod); +		return -1; +	} + +	mod->next = (aim_module_t *)sess->modlistv; +	sess->modlistv = mod; + + +	return 0; +} + +void aim__shutdownmodules(aim_session_t *sess) +{ +	aim_module_t *cur; + +	for (cur = (aim_module_t *)sess->modlistv; cur; ) { +		aim_module_t *tmp; + +		tmp = cur->next; + +		if (cur->shutdown) +			cur->shutdown(sess, cur); + +		g_free(cur); + +		cur = tmp; +	} + +	sess->modlistv = NULL; + +	return; +} + +static int consumesnac(aim_session_t *sess, aim_frame_t *rx) +{ +	aim_module_t *cur; +	aim_modsnac_t snac; + +	if (aim_bstream_empty(&rx->data) < 10) +		return 0; + +	snac.family = aimbs_get16(&rx->data); +	snac.subtype = aimbs_get16(&rx->data); +	snac.flags = aimbs_get16(&rx->data); +	snac.id = aimbs_get32(&rx->data); + +	/* Contains TLV(s) in the FNAC header */ +	if(snac.flags & 0x8000) { +		aim_bstream_advance(&rx->data, aimbs_get16(&rx->data)); +	} else if(snac.flags & 0x0001) { +		/* Following SNAC will be related */ +	} + +	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { + +		if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) &&  +				(cur->family != snac.family)) +			continue; + +		if (cur->snachandler(sess, cur, rx, &snac, &rx->data)) +			return 1; + +	} + +	return 0; +} + +static int consumenonsnac(aim_session_t *sess, aim_frame_t *rx, guint16 family, guint16 subtype) +{ +	aim_module_t *cur; +	aim_modsnac_t snac; + +	snac.family = family; +	snac.subtype = subtype; +	snac.flags = snac.id = 0; + +	for (cur = (aim_module_t *)sess->modlistv; cur; cur = cur->next) { + +		if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) &&  +				(cur->family != snac.family)) +			continue; + +		if (cur->snachandler(sess, cur, rx, &snac, &rx->data)) +			return 1; + +	} + +	return 0; +} + +static int negchan_middle(aim_session_t *sess, aim_frame_t *fr) +{ +	aim_tlvlist_t *tlvlist; +	char *msg = NULL; +	guint16 code = 0; +	aim_rxcallback_t userfunc; +	int ret = 1; + +	if (aim_bstream_empty(&fr->data) == 0) { +		/* XXX should do something with this */ +		return 1; +	} + +	/* Used only by the older login protocol */ +	/* XXX remove this special case? */ +	if (fr->conn->type == AIM_CONN_TYPE_AUTH) +		return consumenonsnac(sess, fr, 0x0017, 0x0003); + +	tlvlist = aim_readtlvchain(&fr->data); + +	if (aim_gettlv(tlvlist, 0x0009, 1)) +		code = aim_gettlv16(tlvlist, 0x0009, 1); + +	if (aim_gettlv(tlvlist, 0x000b, 1)) +		msg = aim_gettlv_str(tlvlist, 0x000b, 1); + +	if ((userfunc = aim_callhandler(sess, fr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR)))  +		ret = userfunc(sess, fr, code, msg); + +	aim_freetlvchain(&tlvlist); + +	g_free(msg); + +	return ret; +} + +/* + * Some SNACs we do not allow to be hooked, for good reason. + */ +static int checkdisallowed(guint16 group, guint16 type) +{ +	static const struct { +		guint16 group; +		guint16 type; +	} dontuse[] = { +		{0x0001, 0x0002}, +		{0x0001, 0x0003}, +		{0x0001, 0x0006}, +		{0x0001, 0x0007}, +		{0x0001, 0x0008}, +		{0x0001, 0x0017}, +		{0x0001, 0x0018}, +		{0x0000, 0x0000} +	}; +	int i; + +	for (i = 0; dontuse[i].group != 0x0000; i++) { +		if ((dontuse[i].group == group) && (dontuse[i].type == type)) +			return 1; +	} + +	return 0; +} + +int aim_conn_addhandler(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 type, aim_rxcallback_t newhandler, guint16 flags) +{ +	struct aim_rxcblist_s *newcb; + +	if (!conn) +		return -1; + +	if (checkdisallowed(family, type)) { +		g_assert(0); +		return -1; +	} + +	if (!(newcb = (struct aim_rxcblist_s *)g_new0(struct aim_rxcblist_s, 1))) +		return -1; + +	newcb->family = family; +	newcb->type = type; +	newcb->flags = flags; +	newcb->handler = newhandler; +	newcb->next = NULL; + +	if (!conn->handlerlist) +		conn->handlerlist = (void *)newcb; +	else { +		struct aim_rxcblist_s *cur; + +		for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur->next; cur = cur->next) +			; +		cur->next = newcb; +	} + +	return 0; +} + +int aim_clearhandlers(aim_conn_t *conn) +{ +	struct aim_rxcblist_s *cur; + +	if (!conn) +		return -1; + +	for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; ) { +		struct aim_rxcblist_s *tmp; + +		tmp = cur->next; +		g_free(cur); +		cur = tmp; +	} +	conn->handlerlist = NULL; + +	return 0; +} + +aim_rxcallback_t aim_callhandler(aim_session_t *sess, aim_conn_t *conn, guint16 family, guint16 type) +{ +	struct aim_rxcblist_s *cur; + +	if (!conn) +		return NULL; + +	for (cur = (struct aim_rxcblist_s *)conn->handlerlist; cur; cur = cur->next) { +		if ((cur->family == family) && (cur->type == type)) +			return cur->handler; +	} + +	if (type == AIM_CB_SPECIAL_DEFAULT) { +		return NULL; /* prevent infinite recursion */ +	} + +	return aim_callhandler(sess, conn, family, AIM_CB_SPECIAL_DEFAULT); +} + +void aim_clonehandlers(aim_session_t *sess, aim_conn_t *dest, aim_conn_t *src) +{ +	struct aim_rxcblist_s *cur; + +	for (cur = (struct aim_rxcblist_s *)src->handlerlist; cur; cur = cur->next) { +		aim_conn_addhandler(sess, dest, cur->family, cur->type,  +						cur->handler, cur->flags); +	} + +	return; +} + +static int aim_callhandler_noparam(aim_session_t *sess, aim_conn_t *conn,guint16 family, guint16 type, aim_frame_t *ptr) +{ +	aim_rxcallback_t userfunc; + +	if ((userfunc = aim_callhandler(sess, conn, family, type))) +		return userfunc(sess, ptr); + +	return 1; /* XXX */ +} + +/* + * aim_rxdispatch() + * + * Basically, heres what this should do: + *   1) Determine correct packet handler for this packet + *   2) Mark the packet handled (so it can be dequeued in purge_queue()) + *   3) Send the packet to the packet handler + *   4) Go to next packet in the queue and start over + *   5) When done, run purge_queue() to purge handled commands + * + * TODO: Clean up. + * TODO: More support for mid-level handlers. + * TODO: Allow for NULL handlers. + * + */ +void aim_rxdispatch(aim_session_t *sess) +{ +	int i; +	aim_frame_t *cur; + +	for (cur = sess->queue_incoming, i = 0; cur; cur = cur->next, i++) { + +		/* +		 * XXX: This is still fairly ugly. +		 */ + +		if (cur->handled) +			continue; + +		if (cur->hdr.flap.type == 0x01) { +			 +			cur->handled = aim_callhandler_noparam(sess, cur->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, cur); /* XXX use consumenonsnac */ +			 +			continue; +			 +		} else if (cur->hdr.flap.type == 0x02) { + +			if ((cur->handled = consumesnac(sess, cur))) +				continue; + +		} else if (cur->hdr.flap.type == 0x04) { + +			cur->handled = negchan_middle(sess, cur); +			continue; + +		} else if (cur->hdr.flap.type == 0x05) +			; +		 +		if (!cur->handled) { +			consumenonsnac(sess, cur, 0xffff, 0xffff); /* last chance! */ +			cur->handled = 1; +		} +	} + +	/*  +	 * This doesn't have to be called here.  It could easily be done +	 * by a seperate thread or something. It's an administrative operation, +	 * and can take a while. Though the less you call it the less memory +	 * you'll have :) +	 */ +	aim_purge_rxqueue(sess); + +	return; +} diff --git a/protocols/oscar/rxqueue.c b/protocols/oscar/rxqueue.c new file mode 100644 index 00000000..34f389af --- /dev/null +++ b/protocols/oscar/rxqueue.c @@ -0,0 +1,500 @@ +/* + *  aim_rxqueue.c + * + * This file contains the management routines for the receive + * (incoming packet) queue.  The actual packet handlers are in + * aim_rxhandlers.c. + */ + +#include <aim.h>  + +#ifndef _WIN32 +#include <sys/socket.h> +#endif + +/* + * + */ +int aim_recv(int fd, void *buf, size_t count) +{ +	int left, cur;  + +	for (cur = 0, left = count; left; ) { +		int ret; +		 +		ret = recv(fd, ((unsigned char *)buf)+cur, left, 0); + +		/* Of course EOF is an error, only morons disagree with that. */ +		if (ret <= 0) +			return -1; + +		cur += ret; +		left -= ret; +	} + +	return cur; +} + +/* + * Read into a byte stream.  Will not read more than count, but may read + * less if there is not enough room in the stream buffer. + */ +static int aim_bstream_recv(aim_bstream_t *bs, int fd, size_t count) +{ +	int red = 0; + +	if (!bs || (fd < 0) || (count < 0)) +		return -1; +	 +	if (count > (bs->len - bs->offset)) +		count = bs->len - bs->offset; /* truncate to remaining space */ + +	if (count) { + +		red = aim_recv(fd, bs->data + bs->offset, count); + +		if (red <= 0) +			return -1; +	} + +	bs->offset += red; + +	return red; +} + +int aim_bstream_init(aim_bstream_t *bs, guint8 *data, int len) +{ +	 +	if (!bs) +		return -1; + +	bs->data = data; +	bs->len = len; +	bs->offset = 0; + +	return 0; +} + +int aim_bstream_empty(aim_bstream_t *bs) +{ +	return bs->len - bs->offset; +} + +int aim_bstream_curpos(aim_bstream_t *bs) +{ +	return bs->offset; +} + +int aim_bstream_setpos(aim_bstream_t *bs, int off) +{ + +	if (off > bs->len) +		return -1; + +	bs->offset = off; + +	return off; +} + +void aim_bstream_rewind(aim_bstream_t *bs) +{ + +	aim_bstream_setpos(bs, 0); + +	return; +} + +int aim_bstream_advance(aim_bstream_t *bs, int n) +{ + +	if (aim_bstream_empty(bs) < n) +		return 0; /* XXX throw an exception */ + +	bs->offset += n; + +	return n; +} + +guint8 aimbs_get8(aim_bstream_t *bs) +{ +	 +	if (aim_bstream_empty(bs) < 1) +		return 0; /* XXX throw an exception */ +	 +	bs->offset++; +	 +	return aimutil_get8(bs->data + bs->offset - 1); +} + +guint16 aimbs_get16(aim_bstream_t *bs) +{ +	 +	if (aim_bstream_empty(bs) < 2) +		return 0; /* XXX throw an exception */ +	 +	bs->offset += 2; +	 +	return aimutil_get16(bs->data + bs->offset - 2); +} + +guint32 aimbs_get32(aim_bstream_t *bs) +{ +	 +	if (aim_bstream_empty(bs) < 4) +		return 0; /* XXX throw an exception */ +	 +	bs->offset += 4; +	 +	return aimutil_get32(bs->data + bs->offset - 4); +} + +guint8 aimbs_getle8(aim_bstream_t *bs) +{ +	 +	if (aim_bstream_empty(bs) < 1) +		return 0; /* XXX throw an exception */ +	 +	bs->offset++; +	 +	return aimutil_getle8(bs->data + bs->offset - 1); +} + +guint16 aimbs_getle16(aim_bstream_t *bs) +{ +	 +	if (aim_bstream_empty(bs) < 2) +		return 0; /* XXX throw an exception */ +	 +	bs->offset += 2; +	 +	return aimutil_getle16(bs->data + bs->offset - 2); +} + +guint32 aimbs_getle32(aim_bstream_t *bs) +{ +	 +	if (aim_bstream_empty(bs) < 4) +		return 0; /* XXX throw an exception */ +	 +	bs->offset += 4; +	 +	return aimutil_getle32(bs->data + bs->offset - 4); +} + +int aimbs_put8(aim_bstream_t *bs, guint8 v) +{ + +	if (aim_bstream_empty(bs) < 1) +		return 0; /* XXX throw an exception */ + +	bs->offset += aimutil_put8(bs->data + bs->offset, v); + +	return 1; +} + +int aimbs_put16(aim_bstream_t *bs, guint16 v) +{ + +	if (aim_bstream_empty(bs) < 2) +		return 0; /* XXX throw an exception */ + +	bs->offset += aimutil_put16(bs->data + bs->offset, v); + +	return 2; +} + +int aimbs_put32(aim_bstream_t *bs, guint32 v) +{ + +	if (aim_bstream_empty(bs) < 4) +		return 0; /* XXX throw an exception */ + +	bs->offset += aimutil_put32(bs->data + bs->offset, v); + +	return 1; +} + +int aimbs_putle8(aim_bstream_t *bs, guint8 v) +{ + +	if (aim_bstream_empty(bs) < 1) +		return 0; /* XXX throw an exception */ + +	bs->offset += aimutil_putle8(bs->data + bs->offset, v); + +	return 1; +} + +int aimbs_putle16(aim_bstream_t *bs, guint16 v) +{ + +	if (aim_bstream_empty(bs) < 2) +		return 0; /* XXX throw an exception */ + +	bs->offset += aimutil_putle16(bs->data + bs->offset, v); + +	return 2; +} + +int aimbs_putle32(aim_bstream_t *bs, guint32 v) +{ + +	if (aim_bstream_empty(bs) < 4) +		return 0; /* XXX throw an exception */ + +	bs->offset += aimutil_putle32(bs->data + bs->offset, v); + +	return 1; +} + +int aimbs_getrawbuf(aim_bstream_t *bs, guint8 *buf, int len) +{ + +	if (aim_bstream_empty(bs) < len) +		return 0; + +	memcpy(buf, bs->data + bs->offset, len); +	bs->offset += len; + +	return len; +} + +guint8 *aimbs_getraw(aim_bstream_t *bs, int len) +{ +	guint8 *ob; + +	if (!(ob = g_malloc(len))) +		return NULL; + +	if (aimbs_getrawbuf(bs, ob, len) < len) { +		g_free(ob); +		return NULL; +	} + +	return ob; +} + +char *aimbs_getstr(aim_bstream_t *bs, int len) +{ +	guint8 *ob; + +	if (!(ob = g_malloc(len+1))) +		return NULL; + +	if (aimbs_getrawbuf(bs, ob, len) < len) { +		g_free(ob); +		return NULL; +	} +	 +	ob[len] = '\0'; + +	return (char *)ob; +} + +int aimbs_putraw(aim_bstream_t *bs, const guint8 *v, int len) +{ + +	if (aim_bstream_empty(bs) < len) +		return 0; /* XXX throw an exception */ + +	memcpy(bs->data + bs->offset, v, len); +	bs->offset += len; + +	return len; +} + +int aimbs_putbs(aim_bstream_t *bs, aim_bstream_t *srcbs, int len) +{ + +	if (aim_bstream_empty(srcbs) < len) +		return 0; /* XXX throw exception (underrun) */ + +	if (aim_bstream_empty(bs) < len) +		return 0; /* XXX throw exception (overflow) */ + +	memcpy(bs->data + bs->offset, srcbs->data + srcbs->offset, len); +	bs->offset += len; +	srcbs->offset += len; + +	return len; +} + +/** + * aim_frame_destroy - free aim_frame_t  + * @frame: the frame to free   + * + * returns -1 on error; 0 on success.   + * + */ +void aim_frame_destroy(aim_frame_t *frame) +{ + +	g_free(frame->data.data); /* XXX aim_bstream_free */ + +	g_free(frame); +}  + + +/* + * Grab a single command sequence off the socket, and enqueue + * it in the incoming event queue in a seperate struct. + */ +int aim_get_command(aim_session_t *sess, aim_conn_t *conn) +{ +	guint8 flaphdr_raw[6]; +	aim_bstream_t flaphdr; +	aim_frame_t *newrx; +	guint16 payloadlen; +	 +	if (!sess || !conn) +		return 0; + +	if (conn->fd == -1) +		return -1; /* its a aim_conn_close()'d connection */ + +	/* KIDS, THIS IS WHAT HAPPENS IF YOU USE CODE WRITTEN FOR GUIS IN A DAEMON! +	    +	   And wouldn't it make sense to return something that prevents this function +	   from being called again IMMEDIATELY (and making the program suck up all +	   CPU time)?... +	    +	if (conn->fd < 3) +		return 0; +	*/ + +	if (conn->status & AIM_CONN_STATUS_INPROGRESS) +		return aim_conn_completeconnect(sess, conn); + +	aim_bstream_init(&flaphdr, flaphdr_raw, sizeof(flaphdr_raw)); + +	/* +	 * Read FLAP header.  Six bytes: +	 *     +	 *   0 char  -- Always 0x2a +	 *   1 char  -- Channel ID.  Usually 2 -- 1 and 4 are used during login. +	 *   2 short -- Sequence number  +	 *   4 short -- Number of data bytes that follow. +	 */ +	if (aim_bstream_recv(&flaphdr, conn->fd, 6) < 6) { +		aim_conn_close(conn); +		return -1; +	} + +	aim_bstream_rewind(&flaphdr); + +	/* +	 * This shouldn't happen unless the socket breaks, the server breaks, +	 * or we break.  We must handle it just in case. +	 */ +	if (aimbs_get8(&flaphdr) != 0x2a) { +		guint8 start; + +		aim_bstream_rewind(&flaphdr); +		start = aimbs_get8(&flaphdr); +		imcb_error(sess->aux_data, "FLAP framing disrupted"); +		aim_conn_close(conn); +		return -1; +	}	 + +	/* allocate a new struct */ +	if (!(newrx = (aim_frame_t *)g_new0(aim_frame_t,1))) +		return -1; + +	/* we're doing FLAP if we're here */ +	newrx->hdrtype = AIM_FRAMETYPE_FLAP; +	 +	newrx->hdr.flap.type = aimbs_get8(&flaphdr); +	newrx->hdr.flap.seqnum = aimbs_get16(&flaphdr); +	payloadlen = aimbs_get16(&flaphdr); + +	newrx->nofree = 0; /* free by default */ + +	if (payloadlen) { +		guint8 *payload = NULL; + +		if (!(payload = (guint8 *) g_malloc(payloadlen))) { +			aim_frame_destroy(newrx); +			return -1; +		} + +		aim_bstream_init(&newrx->data, payload, payloadlen); + +		/* read the payload */ +		if (aim_bstream_recv(&newrx->data, conn->fd, payloadlen) < payloadlen) { +			aim_frame_destroy(newrx); /* free's payload */ +			aim_conn_close(conn); +			return -1; +		} +	} else +		aim_bstream_init(&newrx->data, NULL, 0); + + +	aim_bstream_rewind(&newrx->data); + +	newrx->conn = conn; + +	newrx->next = NULL;  /* this will always be at the bottom */ + +	if (!sess->queue_incoming) +		sess->queue_incoming = newrx; +	else { +		aim_frame_t *cur; + +		for (cur = sess->queue_incoming; cur->next; cur = cur->next) +			; +		cur->next = newrx; +	} + +	newrx->conn->lastactivity = time(NULL); + +	return 0;   +} + +/* + * Purge recieve queue of all handled commands (->handled==1).  Also + * allows for selective freeing using ->nofree so that the client can + * keep the data for various purposes.   + * + * If ->nofree is nonzero, the frame will be delinked from the global list,  + * but will not be free'ed.  The client _must_ keep a pointer to the + * data -- libfaim will not!  If the client marks ->nofree but + * does not keep a pointer, it's lost forever. + * + */ +void aim_purge_rxqueue(aim_session_t *sess) +{ +	aim_frame_t *cur, **prev; + +	for (prev = &sess->queue_incoming; (cur = *prev); ) { +		if (cur->handled) { + +			*prev = cur->next; +			 +			if (!cur->nofree) +				aim_frame_destroy(cur); + +		} else +			prev = &cur->next; +	} + +	return; +} + +/* + * Since aim_get_command will aim_conn_kill dead connections, we need + * to clean up the rxqueue of unprocessed connections on that socket. + * + * XXX: this is something that was handled better in the old connection + * handling method, but eh. + */ +void aim_rxqueue_cleanbyconn(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_frame_t *currx; + +	for (currx = sess->queue_incoming; currx; currx = currx->next) { +		if ((!currx->handled) && (currx->conn == conn)) +			currx->handled = 1; +	}	 +	return; +} + diff --git a/protocols/oscar/search.c b/protocols/oscar/search.c new file mode 100644 index 00000000..3570e4df --- /dev/null +++ b/protocols/oscar/search.c @@ -0,0 +1,121 @@ + +/* + * aim_search.c + * + * TODO: Add aim_usersearch_name() + * + */ + +#include <aim.h> + +int aim_usersearch_address(aim_session_t *sess, aim_conn_t *conn, const char *address) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !conn || !address) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+strlen(address)))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x000a, 0x0002, 0x0000, g_strdup(address), strlen(address)+1); +	aim_putsnac(&fr->data, 0x000a, 0x0002, 0x0000, snacid); +	 +	aimbs_putraw(&fr->data, (guint8 *)address, strlen(address));  + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* XXX can this be integrated with the rest of the error handling? */ +static int error(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	aim_rxcallback_t userfunc; +	aim_snac_t *snac2; + +	/* XXX the modules interface should have already retrieved this for us */ +	if (!(snac2 = aim_remsnac(sess, snac->id))) { +		imcb_error(sess->aux_data, "couldn't get snac"); +		return 0; +	} + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, snac2->data /* address */); + +	/* XXX freesnac()? */ +	if (snac2) +		g_free(snac2->data); +	g_free(snac2); + +	return ret; +} + +static int reply(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int j = 0, m, ret = 0; +	aim_tlvlist_t *tlvlist; +	char *cur = NULL, *buf = NULL; +	aim_rxcallback_t userfunc; +	aim_snac_t *snac2; +	char *searchaddr = NULL; + +	if ((snac2 = aim_remsnac(sess, snac->id))) +		searchaddr = (char *)snac2->data; + +	tlvlist = aim_readtlvchain(bs); +	m = aim_counttlvchain(&tlvlist); + +	/* XXX uhm. */ +	while ((cur = aim_gettlv_str(tlvlist, 0x0001, j+1)) && j < m) { +		buf = g_realloc(buf, (j+1) * (MAXSNLEN+1)); + +		strncpy(&buf[j * (MAXSNLEN+1)], cur, MAXSNLEN); +		g_free(cur); + +		j++;  +	} + +	aim_freetlvchain(&tlvlist); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, searchaddr, j, buf); + +	/* XXX freesnac()? */ +	if (snac2) +		g_free(snac2->data); +	g_free(snac2); + +	g_free(buf); + +	return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0001) +		return error(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0003) +		return reply(sess, mod, rx, snac, bs); + +	return 0; +} + +int search_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x000a; +	mod->version = 0x0001; +	mod->toolid = 0x0110; +	mod->toolversion = 0x0629; +	mod->flags = 0; +	strncpy(mod->name, "search", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} + + diff --git a/protocols/oscar/search.h b/protocols/oscar/search.h new file mode 100644 index 00000000..77eeb265 --- /dev/null +++ b/protocols/oscar/search.h @@ -0,0 +1,6 @@ +#ifndef __OSCAR_SEARCH_H__ +#define __OSCAR_SEARCH_H__ + +int aim_usersearch_address(aim_session_t *, aim_conn_t *, const char *); + +#endif /* __OSCAR_SEARCH_H__ */ diff --git a/protocols/oscar/service.c b/protocols/oscar/service.c new file mode 100644 index 00000000..acd09150 --- /dev/null +++ b/protocols/oscar/service.c @@ -0,0 +1,949 @@ +/* + * Group 1.  This is a very special group.  All connections support + * this group, as it does some particularly good things (like rate limiting). + */ + +#include <aim.h> + +#include "md5.h" + +/* Client Online (group 1, subtype 2) */ +int aim_clientready(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; +	struct snacgroup *sg; +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!ins) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0001, 0x0002, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0001, 0x0002, 0x0000, snacid); + +	/* +	 * Send only the tool versions that the server cares about (that it +	 * marked as supporting in the server ready SNAC).   +	 */ +	for (sg = ins->groups; sg; sg = sg->next) { +		aim_module_t *mod; + +		if ((mod = aim__findmodulebygroup(sess, sg->group))) { +			aimbs_put16(&fr->data, mod->family); +			aimbs_put16(&fr->data, mod->version); +			aimbs_put16(&fr->data, mod->toolid); +			aimbs_put16(&fr->data, mod->toolversion); +		}  +	} + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Host Online (group 1, type 3) + *  + * See comments in conn.c about how the group associations are supposed + * to work, and how they really work. + * + * This info probably doesn't even need to make it to the client. + * + * We don't actually call the client here.  This starts off the connection + * initialization routine required by all AIM connections.  The next time + * the client is called is the CONNINITDONE callback, which should be  + * shortly after the rate information is acknowledged. + *  + */ +static int hostonline(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	guint16 *families; +	int famcount; + + +	if (!(families = g_malloc(aim_bstream_empty(bs)))) +		return 0; + +	for (famcount = 0; aim_bstream_empty(bs); famcount++) { +		families[famcount] = aimbs_get16(bs); +		aim_conn_addgroup(rx->conn, families[famcount]); +	} + +	g_free(families); + + +	/* +	 * Next step is in the Host Versions handler. +	 * +	 * Note that we must send this before we request rates, since +	 * the format of the rate information depends on the versions we +	 * give it. +	 * +	 */ +	aim_setversions(sess, rx->conn); + +	return 1;  +} + +/* Service request (group 1, type 4) */ +int aim_reqservice(aim_session_t *sess, aim_conn_t *conn, guint16 serviceid) +{ +	return aim_genericreq_s(sess, conn, 0x0001, 0x0004, &serviceid); +} + +/* Redirect (group 1, type 5) */ +static int redirect(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	struct aim_redirect_data redir; +	aim_rxcallback_t userfunc; +	aim_tlvlist_t *tlvlist; +	aim_snac_t *origsnac = NULL; +	int ret = 0; + +	memset(&redir, 0, sizeof(redir)); + +	tlvlist = aim_readtlvchain(bs); + +	if (!aim_gettlv(tlvlist, 0x000d, 1) || +			!aim_gettlv(tlvlist, 0x0005, 1) || +			!aim_gettlv(tlvlist, 0x0006, 1)) { +		aim_freetlvchain(&tlvlist); +		return 0; +	} + +	redir.group = aim_gettlv16(tlvlist, 0x000d, 1); +	redir.ip = aim_gettlv_str(tlvlist, 0x0005, 1); +	redir.cookie = (guint8 *)aim_gettlv_str(tlvlist, 0x0006, 1); + +	/* Fetch original SNAC so we can get csi if needed */ +	origsnac = aim_remsnac(sess, snac->id); + +	if ((redir.group == AIM_CONN_TYPE_CHAT) && origsnac) { +		struct chatsnacinfo *csi = (struct chatsnacinfo *)origsnac->data; + +		redir.chat.exchange = csi->exchange; +		redir.chat.room = csi->name; +		redir.chat.instance = csi->instance; +	} + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, &redir); + +	g_free((void *)redir.ip); +	g_free((void *)redir.cookie); + +	if (origsnac) +		g_free(origsnac->data); +	g_free(origsnac); + +	aim_freetlvchain(&tlvlist); + +	return ret; +} + +/* Request Rate Information. (group 1, type 6) */ +int aim_reqrates(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, 0x0001, 0x0006); +} + +/* + * OSCAR defines several 'rate classes'.  Each class has seperate + * rate limiting properties (limit level, alert level, disconnect + * level, etc), and a set of SNAC family/type pairs associated with + * it.  The rate classes, their limiting properties, and the definitions + * of which SNACs are belong to which class, are defined in the + * Rate Response packet at login to each host.   + * + * Logically, all rate offenses within one class count against further + * offenses for other SNACs in the same class (ie, sending messages + * too fast will limit the number of user info requests you can send, + * since those two SNACs are in the same rate class). + * + * Since the rate classes are defined dynamically at login, the values + * below may change. But they seem to be fairly constant. + * + * Currently, BOS defines five rate classes, with the commonly used + * members as follows... + * + *  Rate class 0x0001: + *  	- Everything thats not in any of the other classes + * + *  Rate class 0x0002: + * 	- Buddy list add/remove + *	- Permit list add/remove + *	- Deny list add/remove + * + *  Rate class 0x0003: + *	- User information requests + *	- Outgoing ICBMs + * + *  Rate class 0x0004: + *	- A few unknowns: 2/9, 2/b, and f/2 + * + *  Rate class 0x0005: + *	- Chat room create + *	- Outgoing chat ICBMs + * + * The only other thing of note is that class 5 (chat) has slightly looser + * limiting properties than class 3 (normal messages).  But thats just a  + * small bit of trivia for you. + * + * The last thing that needs to be learned about the rate limiting + * system is how the actual numbers relate to the passing of time.  This + * seems to be a big mystery. + *  + */ + +static void rc_addclass(struct rateclass **head, struct rateclass *inrc) +{ +	struct rateclass *rc, *rc2; + +	if (!(rc = g_malloc(sizeof(struct rateclass)))) +		return; + +	memcpy(rc, inrc, sizeof(struct rateclass)); +	rc->next = NULL; + +	for (rc2 = *head; rc2 && rc2->next; rc2 = rc2->next) +		; + +	if (!rc2) +		*head = rc; +	else +		rc2->next = rc; + +	return; +} + +static struct rateclass *rc_findclass(struct rateclass **head, guint16 id) +{ +	struct rateclass *rc; + +	for (rc = *head; rc; rc = rc->next) { +		if (rc->classid == id) +			return rc; +	} + +	return NULL; +} + +static void rc_addpair(struct rateclass *rc, guint16 group, guint16 type) +{ +	struct snacpair *sp, *sp2; + +	if (!(sp = g_new0(struct snacpair, 1))) +		return; + +	sp->group = group; +	sp->subtype = type; +	sp->next = NULL; + +	for (sp2 = rc->members; sp2 && sp2->next; sp2 = sp2->next) +		; + +	if (!sp2) +		rc->members = sp; +	else +		sp2->next = sp; + +	return; +} + +/* Rate Parameters (group 1, type 7) */ +static int rateresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_conn_inside_t *ins = (aim_conn_inside_t *)rx->conn->inside; +	guint16 numclasses, i; +	aim_rxcallback_t userfunc; + + +	/* +	 * First are the parameters for each rate class. +	 */ +	numclasses = aimbs_get16(bs); +	for (i = 0; i < numclasses; i++) { +		struct rateclass rc; + +		memset(&rc, 0, sizeof(struct rateclass)); + +		rc.classid = aimbs_get16(bs); +		rc.windowsize = aimbs_get32(bs); +		rc.clear = aimbs_get32(bs); +		rc.alert = aimbs_get32(bs); +		rc.limit = aimbs_get32(bs); +		rc.disconnect = aimbs_get32(bs); +		rc.current = aimbs_get32(bs); +		rc.max = aimbs_get32(bs); + +		/* +		 * The server will send an extra five bytes of parameters +		 * depending on the version we advertised in 1/17.  If we +		 * didn't send 1/17 (evil!), then this will crash and you +		 * die, as it will default to the old version but we have  +		 * the new version hardcoded here.  +		 */ +		if (mod->version >= 3) +			aimbs_getrawbuf(bs, rc.unknown, sizeof(rc.unknown)); + +		rc_addclass(&ins->rates, &rc); +	} + +	/* +	 * Then the members of each class. +	 */ +	for (i = 0; i < numclasses; i++) { +		guint16 classid, count; +		struct rateclass *rc; +		int j; + +		classid = aimbs_get16(bs); +		count = aimbs_get16(bs); + +		rc = rc_findclass(&ins->rates, classid); + +		for (j = 0; j < count; j++) { +			guint16 group, subtype; + +			group = aimbs_get16(bs); +			subtype = aimbs_get16(bs); + +			if (rc) +				rc_addpair(rc, group, subtype); +		} +	} + +	/* +	 * We don't pass the rate information up to the client, as it really +	 * doesn't care.  The information is stored in the connection, however +	 * so that we can do more fun stuff later (not really). +	 */ + +	/* +	 * Last step in the conn init procedure is to acknowledge that we +	 * agree to these draconian limitations. +	 */ +	aim_rates_addparam(sess, rx->conn); + +	/* +	 * Finally, tell the client it's ready to go... +	 */ +	if ((userfunc = aim_callhandler(sess, rx->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE))) +		userfunc(sess, rx); + + +	return 1; +} + +/* Add Rate Parameter (group 1, type 8) */ +int aim_rates_addparam(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; +	aim_frame_t *fr;	 +	aim_snacid_t snacid; +	struct rateclass *rc; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) +		return -ENOMEM;  + +	snacid = aim_cachesnac(sess, 0x0001, 0x0008, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0001, 0x0008, 0x0000, snacid); + +	for (rc = ins->rates; rc; rc = rc->next) +		aimbs_put16(&fr->data, rc->classid); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* Delete Rate Parameter (group 1, type 9) */ +int aim_rates_delparam(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; +	aim_frame_t *fr;	 +	aim_snacid_t snacid; +	struct rateclass *rc; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512))) +		return -ENOMEM;  + +	snacid = aim_cachesnac(sess, 0x0001, 0x0009, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0001, 0x0009, 0x0000, snacid); + +	for (rc = ins->rates; rc; rc = rc->next) +		aimbs_put16(&fr->data, rc->classid); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* Rate Change (group 1, type 0x0a) */ +static int ratechange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	guint16 code, rateclass; +	guint32 currentavg, maxavg, windowsize, clear, alert, limit, disconnect; + +	code = aimbs_get16(bs); +	rateclass = aimbs_get16(bs); +	 +	windowsize = aimbs_get32(bs); +	clear = aimbs_get32(bs); +	alert = aimbs_get32(bs); +	limit = aimbs_get32(bs); +	disconnect = aimbs_get32(bs); +	currentavg = aimbs_get32(bs); +	maxavg = aimbs_get32(bs); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx, code, rateclass, windowsize, clear, alert, limit, disconnect, currentavg, maxavg); + +	return 0; +} + +/* + * How Migrations work.   + * + * The server sends a Server Pause message, which the client should respond to  + * with a Server Pause Ack, which contains the families it needs on this  + * connection. The server will send a Migration Notice with an IP address, and  + * then disconnect. Next the client should open the connection and send the  + * cookie.  Repeat the normal login process and pretend this never happened. + * + * The Server Pause contains no data. + * + */ + +/* Service Pause (group 1, type 0x0b) */ +static int serverpause(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx); + +	return 0; +} + +/* + * Service Pause Acknowledgement (group 1, type 0x0c) + * + * It is rather important that aim_sendpauseack() gets called for the exact + * same connection that the Server Pause callback was called for, since + * libfaim extracts the data for the SNAC from the connection structure. + * + * Of course, if you don't do that, more bad things happen than just what + * libfaim can cause. + * + */ +int aim_sendpauseack(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; +	struct snacgroup *sg; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1024))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0001, 0x000c, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0001, 0x000c, 0x0000, snacid); + +	/* +	 * This list should have all the groups that the original  +	 * Host Online / Server Ready said this host supports.  And  +	 * we want them all back after the migration. +	 */ +	for (sg = ins->groups; sg; sg = sg->next) +		aimbs_put16(&fr->data, sg->group); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* Service Resume (group 1, type 0x0d) */ +static int serverresume(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx); + +	return 0; +} + +/* Request self-info (group 1, type 0x0e) */ +int aim_reqpersonalinfo(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, 0x0001, 0x000e); +} + +/* Self User Info (group 1, type 0x0f) */ +static int selfinfo(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	aim_userinfo_t userinfo; + +	aim_extractuserinfo(sess, bs, &userinfo); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx, &userinfo); + +	return 0; +} + +/* Evil Notification (group 1, type 0x10) */ +static int evilnotify(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	guint16 newevil; +	aim_userinfo_t userinfo; + +	memset(&userinfo, 0, sizeof(aim_userinfo_t)); +	 +	newevil = aimbs_get16(bs); + +	if (aim_bstream_empty(bs)) +		aim_extractuserinfo(sess, bs, &userinfo); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx, newevil, &userinfo); + +	return 0; +} + +/* + * Idle Notification (group 1, type 0x11) + * + * Should set your current idle time in seconds.  Note that this should + * never be called consecutively with a non-zero idle time.  That makes + * OSCAR do funny things.  Instead, just set it once you go idle, and then + * call it again with zero when you're back. + * + */ +int aim_bos_setidle(aim_session_t *sess, aim_conn_t *conn, guint32 idletime) +{ +	return aim_genericreq_l(sess, conn, 0x0001, 0x0011, &idletime); +} + +/* + * Service Migrate (group 1, type 0x12) + * + * This is the final SNAC sent on the original connection during a migration. + * It contains the IP and cookie used to connect to the new server, and  + * optionally a list of the SNAC groups being migrated. + * + */ +static int migrate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	int ret = 0; +	guint16 groupcount, i; +	aim_tlvlist_t *tl; +	char *ip = NULL; +	aim_tlv_t *cktlv; + +	/* +	 * Apparently there's some fun stuff that can happen right here. The +	 * migration can actually be quite selective about what groups it +	 * moves to the new server.  When not all the groups for a connection +	 * are migrated, or they are all migrated but some groups are moved +	 * to a different server than others, it is called a bifurcated  +	 * migration. +	 * +	 * Let's play dumb and not support that. +	 * +	 */ +	groupcount = aimbs_get16(bs); +	for (i = 0; i < groupcount; i++) { +		guint16 group; + +		group = aimbs_get16(bs); + +		imcb_error(sess->aux_data, "bifurcated migration unsupported"); +	} + +	tl = aim_readtlvchain(bs); + +	if (aim_gettlv(tl, 0x0005, 1)) +		ip = aim_gettlv_str(tl, 0x0005, 1); + +	cktlv = aim_gettlv(tl, 0x0006, 1); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, ip, cktlv ? cktlv->value : NULL); + +	aim_freetlvchain(&tl); +	g_free(ip); + +	return ret; +} + +/* Message of the Day (group 1, type 0x13) */ +static int motd(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	char *msg = NULL; +	int ret = 0; +	aim_tlvlist_t *tlvlist; +	guint16 id; + +	/* +	 * Code. +	 * +	 * Valid values: +	 *   1 Mandatory upgrade +	 *   2 Advisory upgrade +	 *   3 System bulletin +	 *   4 Nothing's wrong ("top o the world" -- normal) +	 *   5 Lets-break-something.  +	 * +	 */ +	id = aimbs_get16(bs); + +	/*  +	 * TLVs follow  +	 */ +	tlvlist = aim_readtlvchain(bs); + +	msg = aim_gettlv_str(tlvlist, 0x000b, 1); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, id, msg); + +	g_free(msg); + +	aim_freetlvchain(&tlvlist); + +	return ret; +} + +/*  + * Set privacy flags (group 1, type 0x14) + * + * Normally 0x03. + * + *  Bit 1:  Allows other AIM users to see how long you've been idle. + *  Bit 2:  Allows other AIM users to see how long you've been a member. + * + */ +int aim_bos_setprivacyflags(aim_session_t *sess, aim_conn_t *conn, guint32 flags) +{ +	return aim_genericreq_l(sess, conn, 0x0001, 0x0014, &flags); +} + +/* + * No-op (group 1, type 0x16) + * + * WinAIM sends these every 4min or so to keep the connection alive.  Its not  + * real necessary. + * + */ +int aim_nop(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, 0x0001, 0x0016); +} + +/*  + * Set client versions (group 1, subtype 0x17)  + * + * If you've seen the clientonline/clientready SNAC you're probably  + * wondering what the point of this one is.  And that point seems to be + * that the versions in the client online SNAC are sent too late for the + * server to be able to use them to change the protocol for the earlier + * login packets (client versions are sent right after Host Online is  + * received, but client online versions aren't sent until quite a bit later). + * We can see them already making use of this by changing the format of + * the rate information based on what version of group 1 we advertise here. + * + */ +int aim_setversions(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside; +	struct snacgroup *sg; +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!ins) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0001, 0x0017, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0001, 0x0017, 0x0000, snacid); + +	/* +	 * Send only the versions that the server cares about (that it +	 * marked as supporting in the server ready SNAC).   +	 */ +	for (sg = ins->groups; sg; sg = sg->next) { +		aim_module_t *mod; + +		if ((mod = aim__findmodulebygroup(sess, sg->group))) { +			aimbs_put16(&fr->data, mod->family); +			aimbs_put16(&fr->data, mod->version); +		} +	} + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* Host versions (group 1, subtype 0x18) */ +static int hostversions(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int vercount; +	guint8 *versions; + +	/* This is frivolous. (Thank you SmarterChild.) */ +	vercount = aim_bstream_empty(bs)/4; +	versions = aimbs_getraw(bs, aim_bstream_empty(bs)); +	g_free(versions); + +	/* +	 * Now request rates. +	 */ +	aim_reqrates(sess, rx->conn); + +	return 1; +} + +/*  + * Subtype 0x001e - Extended Status + * + * Sets your ICQ status (available, away, do not disturb, etc.) + * + * These are the same TLVs seen in user info.  You can  + * also set 0x0008 and 0x000c. + */ +int aim_setextstatus(aim_session_t *sess, aim_conn_t *conn, guint32 status) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	aim_tlvlist_t *tl = NULL; +	guint32 data; +	int tlvlen; +	struct im_connection *ic = sess ? sess->aux_data : NULL; + +	data = AIM_ICQ_STATE_HIDEIP | status; /* yay for error checking ;^) */ +	 +	if (ic && set_getbool(&ic->acc->set, "web_aware")) +		data |= AIM_ICQ_STATE_WEBAWARE; + +	tlvlen = aim_addtlvtochain32(&tl, 0x0006, data); + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10 + 8))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0001, 0x001e, 0x0000, NULL, 0); +	aim_putsnac(&fr->data, 0x0001, 0x001e, 0x0000, snacid); +	 +	aim_writetlvchain(&fr->data, &tl); +	aim_freetlvchain(&tl); +	 +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * Starting this past week (26 Mar 2001, say), AOL has started sending + * this nice little extra SNAC.  AFAIK, it has never been used until now. + * + * The request contains eight bytes.  The first four are an offset, the + * second four are a length. + * + * The offset is an offset into aim.exe when it is mapped during execution + * on Win32.  So far, AOL has only been requesting bytes in static regions + * of memory.  (I won't put it past them to start requesting data in + * less static regions -- regions that are initialized at run time, but still + * before the client recieves this request.) + * + * When the client recieves the request, it adds it to the current ds + * (0x00400000) and dereferences it, copying the data into a buffer which + * it then runs directly through the MD5 hasher.  The 16 byte output of + * the hash is then sent back to the server. + * + * If the client does not send any data back, or the data does not match + * the data that the specific client should have, the client will get the + * following message from "AOL Instant Messenger": + *    "You have been disconnected from the AOL Instant Message Service (SM)  + *     for accessing the AOL network using unauthorized software.  You can + *     download a FREE, fully featured, and authorized client, here  + *     http://www.aol.com/aim/download2.html" + * The connection is then closed, recieving disconnect code 1, URL + * http://www.aim.aol.com/errors/USER_LOGGED_OFF_NEW_LOGIN.html.   + * + * Note, however, that numerous inconsistencies can cause the above error,  + * not just sending back a bad hash.  Do not immediatly suspect this code + * if you get disconnected.  AOL and the open/free software community have + * played this game for a couple years now, generating the above message + * on numerous ocassions. + * + * Anyway, neener.  We win again. + * + */ +/* Client verification (group 1, subtype 0x1f) */ +static int memrequest(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	aim_rxcallback_t userfunc; +	guint32 offset, len; +	aim_tlvlist_t *list; +	char *modname; + +	offset = aimbs_get32(bs); +	len = aimbs_get32(bs); +	list = aim_readtlvchain(bs); + +	modname = aim_gettlv_str(list, 0x0001, 1); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx, offset, len, modname); + +	g_free(modname); +	aim_freetlvchain(&list); + +	return 0; +} + +/* Client verification reply (group 1, subtype 0x20) */ +int aim_sendmemblock(aim_session_t *sess, aim_conn_t *conn, guint32 offset, guint32 len, const guint8 *buf, guint8 flag) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !conn) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+2+16))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, 0x0001, 0x0020, 0x0000, NULL, 0); + +	aim_putsnac(&fr->data, 0x0001, 0x0020, 0x0000, snacid); +	aimbs_put16(&fr->data, 0x0010); /* md5 is always 16 bytes */ + +	if ((flag == AIM_SENDMEMBLOCK_FLAG_ISHASH) && buf && (len == 0x10)) { /* we're getting a hash */ + +		aimbs_putraw(&fr->data, buf, 0x10);  + +	} else if (buf && (len > 0)) { /* use input buffer */ +		md5_state_t state; +		md5_byte_t digest[0x10]; + +		md5_init(&state);	 +		md5_append(&state, (const md5_byte_t *)buf, len); +		md5_finish(&state, digest); + +		aimbs_putraw(&fr->data, (guint8 *)digest, 0x10); + +	} else if (len == 0) { /* no length, just hash NULL (buf is optional) */ +		md5_state_t state; +		guint8 nil = '\0'; +		md5_byte_t digest[0x10]; + +		/* +		 * These MD5 routines are stupid in that you have to have +		 * at least one append.  So thats why this doesn't look  +		 * real logical. +		 */ +		md5_init(&state); +		md5_append(&state, (const md5_byte_t *)&nil, 0); +		md5_finish(&state, digest); + +		aimbs_putraw(&fr->data, (guint8 *)digest, 0x10); + +	} else { + +		/*  +		 * This data is correct for AIM 3.5.1670. +		 * +		 * Using these blocks is as close to "legal" as you can get +		 * without using an AIM binary. +		 * +		 */ +		if ((offset == 0x03ffffff) && (len == 0x03ffffff)) { + +#if 1 /* with "AnrbnrAqhfzcd" */ +			aimbs_put32(&fr->data, 0x44a95d26); +			aimbs_put32(&fr->data, 0xd2490423); +			aimbs_put32(&fr->data, 0x93b8821f); +			aimbs_put32(&fr->data, 0x51c54b01); +#else /* no filename */ +			aimbs_put32(&fr->data, 0x1df8cbae); +			aimbs_put32(&fr->data, 0x5523b839); +			aimbs_put32(&fr->data, 0xa0e10db3); +			aimbs_put32(&fr->data, 0xa46d3b39); +#endif + +/* len can't be 0 here anyway... +		} else if ((offset == 0x00001000) && (len == 0x00000000)) { + +			aimbs_put32(&fr->data, 0xd41d8cd9); +			aimbs_put32(&fr->data, 0x8f00b204); +			aimbs_put32(&fr->data, 0xe9800998); +			aimbs_put32(&fr->data, 0xecf8427e); +*/ +		} else +			imcb_error(sess->aux_data, "Warning: unknown hash request"); + +	} + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0003) +		return hostonline(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0005) +		return redirect(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0007) +		return rateresp(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x000a) +		return ratechange(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x000b) +		return serverpause(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x000d) +		return serverresume(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x000f) +		return selfinfo(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0010) +		return evilnotify(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0012) +		return migrate(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0013) +		return motd(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x0018) +		return hostversions(sess, mod, rx, snac, bs); +	else if (snac->subtype == 0x001f) +		return memrequest(sess, mod, rx, snac, bs); + +	return 0; +} + +int general_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x0001; +	mod->version = 0x0003; +	mod->toolid = 0x0110; +	mod->toolversion = 0x0629; +	mod->flags = 0; +	strncpy(mod->name, "general", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} + diff --git a/protocols/oscar/snac.c b/protocols/oscar/snac.c new file mode 100644 index 00000000..8a75b2a0 --- /dev/null +++ b/protocols/oscar/snac.c @@ -0,0 +1,147 @@ +/* + * + * Various SNAC-related dodads...  + * + * outstanding_snacs is a list of aim_snac_t structs.  A SNAC should be added + * whenever a new SNAC is sent and it should remain in the list until the + * response for it has been receieved.   + * + * cleansnacs() should be called periodically by the client in order + * to facilitate the aging out of unreplied-to SNACs. This can and does + * happen, so it should be handled. + * + */ + +#include <aim.h> + +static aim_snacid_t aim_newsnac(aim_session_t *sess, aim_snac_t *newsnac); + +/* + * Called from aim_session_init() to initialize the hash. + */ +void aim_initsnachash(aim_session_t *sess) +{ +	int i; + +	for (i = 0; i < AIM_SNAC_HASH_SIZE; i++) +		sess->snac_hash[i] = NULL; + +	return; +} + +aim_snacid_t aim_cachesnac(aim_session_t *sess, const guint16 family, const guint16 type, const guint16 flags, const void *data, const int datalen) +{ +	aim_snac_t snac; + +	snac.id = sess->snacid_next++; +	snac.family = family; +	snac.type = type; +	snac.flags = flags; + +	if (datalen) { +		if (!(snac.data = g_malloc(datalen))) +			return 0; /* er... */ +		memcpy(snac.data, data, datalen); +	} else +		snac.data = NULL; + +	return aim_newsnac(sess, &snac); +} + +/* + * Clones the passed snac structure and caches it in the + * list/hash. + */ +static aim_snacid_t aim_newsnac(aim_session_t *sess, aim_snac_t *newsnac) +{ +	aim_snac_t *snac; +	int index; + +	if (!newsnac) +		return 0; + +	if (!(snac = g_malloc(sizeof(aim_snac_t)))) +		return 0; +	memcpy(snac, newsnac, sizeof(aim_snac_t)); +	snac->issuetime = time(NULL); + +	index = snac->id % AIM_SNAC_HASH_SIZE; + +	snac->next = (aim_snac_t *)sess->snac_hash[index]; +	sess->snac_hash[index] = (void *)snac; + +	return snac->id; +} + +/* + * Finds a snac structure with the passed SNAC ID,  + * removes it from the list/hash, and returns a pointer to it. + * + * The returned structure must be freed by the caller. + * + */ +aim_snac_t *aim_remsnac(aim_session_t *sess, aim_snacid_t id)  +{ +	aim_snac_t *cur, **prev; +	int index; + +	index = id % AIM_SNAC_HASH_SIZE; + +	for (prev = (aim_snac_t **)&sess->snac_hash[index]; (cur = *prev); ) { +		if (cur->id == id) { +			*prev = cur->next; +			return cur; +		} else +			prev = &cur->next; +	} + +	return cur; +} + +/* + * This is for cleaning up old SNACs that either don't get replies or + * a reply was never received for.  Garabage collection. Plain and simple. + * + * maxage is the _minimum_ age in seconds to keep SNACs. + * + */ +void aim_cleansnacs(aim_session_t *sess, int maxage) +{ +	int i; + +	for (i = 0; i < AIM_SNAC_HASH_SIZE; i++) { +		aim_snac_t *cur, **prev; +		time_t curtime; + +		if (!sess->snac_hash[i]) +			continue; + +		curtime = time(NULL); /* done here in case we waited for the lock */ + +		for (prev = (aim_snac_t **)&sess->snac_hash[i]; (cur = *prev); ) { +			if ((curtime - cur->issuetime) > maxage) { + +				*prev = cur->next; + +				/* XXX should we have destructors here? */ +				g_free(cur->data); +				g_free(cur); + +			} else +				prev = &cur->next; +		} +	} + +	return; +} + +int aim_putsnac(aim_bstream_t *bs, guint16 family, guint16 subtype, guint16 flags, aim_snacid_t snacid) +{ + +	aimbs_put16(bs, family); +	aimbs_put16(bs, subtype); +	aimbs_put16(bs, flags); +	aimbs_put32(bs, snacid); + +	return 10; +} diff --git a/protocols/oscar/ssi.c b/protocols/oscar/ssi.c new file mode 100644 index 00000000..f37d98e5 --- /dev/null +++ b/protocols/oscar/ssi.c @@ -0,0 +1,1523 @@ +/* + * Server-Side/Stored Information. + * + * Relatively new facility that allows storing of certain types of information, + * such as a users buddy list, permit/deny list, and permit/deny preferences,  + * to be stored on the server, so that they can be accessed from any client. + * + * We keep a copy of the ssi data in sess->ssi, because the data needs to be  + * accessed for various reasons.  So all the "aim_ssi_itemlist_bleh" functions  + * near the top just manage the local data. + * + * The SNAC sending and receiving functions are lower down in the file, and  + * they're simpler.  They are in the order of the subtypes they deal with,  + * starting with the request rights function (subtype 0x0002), then parse  + * rights (subtype 0x0003), then--well, you get the idea. + * + * This is entirely too complicated. + * You don't know the half of it. + * + * XXX - Test for memory leaks + * XXX - Better parsing of rights, and use the rights info to limit adds + * + */ + +#include <aim.h> +#include "ssi.h" + +/** + * Locally add a new item to the given item list. + * + * @param list A pointer to a pointer to the current list of items. + * @param parent A pointer to the parent group, or NULL if the item should have no  + *        parent group (ie. the group ID# should be 0). + * @param name A null terminated string of the name of the new item, or NULL if the  + *        item should have no name. + * @param type The type of the item, 0x0001 for a contact, 0x0002 for a group, etc. + * @return The newly created item. + */ +static struct aim_ssi_item *aim_ssi_itemlist_add(struct aim_ssi_item **list, struct aim_ssi_item *parent, char *name, guint16 type) +{ +	int i; +	struct aim_ssi_item *cur, *newitem; + +	if (!(newitem = g_new0(struct aim_ssi_item, 1))) +		return NULL; + +	/* Set the name */ +	if (name) { +		if (!(newitem->name = (char *)g_malloc((strlen(name)+1)*sizeof(char)))) { +			g_free(newitem); +			return NULL; +		} +		strcpy(newitem->name, name); +	} else +		newitem->name = NULL; + +	/* Set the group ID# and the buddy ID# */ +	newitem->gid = 0x0000; +	newitem->bid = 0x0000; +	if (type == AIM_SSI_TYPE_GROUP) { +		if (name) +			do { +				newitem->gid += 0x0001; +				for (cur=*list, i=0; ((cur) && (!i)); cur=cur->next) +					if ((cur->gid == newitem->gid) && (cur->gid == newitem->gid)) +						i=1; +			} while (i); +	} else { +		if (parent) +			newitem->gid = parent->gid; +		do { +			newitem->bid += 0x0001; +			for (cur=*list, i=0; ((cur) && (!i)); cur=cur->next) +				if ((cur->bid == newitem->bid) && (cur->gid == newitem->gid)) +					i=1; +		} while (i); +	} + +	/* Set the rest */ +	newitem->type = type; +	newitem->data = NULL; +	newitem->next = *list; +	*list = newitem; + +	return newitem; +} + +/** + * Locally rebuild the 0x00c8 TLV in the additional data of the given group. + * + * @param list A pointer to a pointer to the current list of items. + * @param parentgroup A pointer to the group who's additional data you want to rebuild. + * @return Return 0 if no errors, otherwise return the error number. + */ +static int aim_ssi_itemlist_rebuildgroup(struct aim_ssi_item **list, struct aim_ssi_item *parentgroup) +{ +	int newlen; //, i; +	struct aim_ssi_item *cur; + +	/* Free the old additional data */ +	if (parentgroup->data) { +		aim_freetlvchain((aim_tlvlist_t **)&parentgroup->data); +		parentgroup->data = NULL; +	} + +	/* Find the length for the new additional data */ +	newlen = 0; +	if (parentgroup->gid == 0x0000) { +		for (cur=*list; cur; cur=cur->next) +			if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP)) +				newlen += 2; +	} else { +		for (cur=*list; cur; cur=cur->next) +			if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY)) +				newlen += 2; +	} + +	/* Rebuild the additional data */ +	if (newlen>0) { +		guint8 *newdata; + +		if (!(newdata = (guint8 *)g_malloc((newlen)*sizeof(guint8)))) +			return -ENOMEM; +		newlen = 0; +		if (parentgroup->gid == 0x0000) { +			for (cur=*list; cur; cur=cur->next) +				if ((cur->gid != 0x0000) && (cur->type == AIM_SSI_TYPE_GROUP)) +						newlen += aimutil_put16(newdata+newlen, cur->gid); +		} else { +			for (cur=*list; cur; cur=cur->next) +				if ((cur->gid == parentgroup->gid) && (cur->type == AIM_SSI_TYPE_BUDDY)) +						newlen += aimutil_put16(newdata+newlen, cur->bid); +		} +		aim_addtlvtochain_raw((aim_tlvlist_t **)&(parentgroup->data), 0x00c8, newlen, newdata); + +		g_free(newdata); +	} + +	return 0; +} + +/** + * Locally free all of the stored buddy list information. + * + * @param sess The oscar session. + * @return Return 0 if no errors, otherwise return the error number. + */ +static int aim_ssi_freelist(aim_session_t *sess) +{ +	struct aim_ssi_item *cur, *delitem; + +	cur = sess->ssi.items; +	while (cur) { +		if (cur->name)  g_free(cur->name); +		if (cur->data)  aim_freetlvchain((aim_tlvlist_t **)&cur->data); +		delitem = cur; +		cur = cur->next; +		g_free(delitem); +	} + +	sess->ssi.items = NULL; +	sess->ssi.revision = 0; +	sess->ssi.timestamp = (time_t)0; + +	return 0; +} + +/** + * Locally find an item given a group ID# and a buddy ID#. + * + * @param list A pointer to the current list of items. + * @param gid The group ID# of the desired item. + * @param bid The buddy ID# of the desired item. + * @return Return a pointer to the item if found, else return NULL; + */ +struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid) +{ +	struct aim_ssi_item *cur; +	for (cur=list; cur; cur=cur->next) +		if ((cur->gid == gid) && (cur->bid == bid)) +			return cur; +	return NULL; +} + +/** + * Locally find an item given a group name, screen name, and type.  If group name  + * and screen name are null, then just return the first item of the given type. + * + * @param list A pointer to the current list of items. + * @param gn The group name of the desired item. + * @param bn The buddy name of the desired item. + * @param type The type of the desired item. + * @return Return a pointer to the item if found, else return NULL; + */ +struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, char *gn, char *sn, guint16 type) +{ +	struct aim_ssi_item *cur; +	if (!list) +		return NULL; + +	if (gn && sn) { /* For finding buddies in groups */ +		for (cur=list; cur; cur=cur->next) +			if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn))) { +				struct aim_ssi_item *curg; +				for (curg=list; curg; curg=curg->next) +					if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid) && (curg->name) && !(aim_sncmp(curg->name, gn))) +						return cur; +			} + +	} else if (sn) { /* For finding groups, permits, denies, and ignores */ +		for (cur=list; cur; cur=cur->next) +			if ((cur->type == type) && (cur->name) && !(aim_sncmp(cur->name, sn))) +				return cur; + +	/* For stuff without names--permit deny setting, visibility mask, etc. */ +	} else for (cur=list; cur; cur=cur->next) { +		if (cur->type == type) +			return cur; +	} + +	return NULL; +} + +/** + * Locally find the parent item of the given buddy name. + * + * @param list A pointer to the current list of items. + * @param bn The buddy name of the desired item. + * @return Return a pointer to the item if found, else return NULL; + */ +struct aim_ssi_item *aim_ssi_itemlist_findparent(struct aim_ssi_item *list, char *sn) +{ +	struct aim_ssi_item *cur, *curg; +	if (!list || !sn) +		return NULL; +	if (!(cur = aim_ssi_itemlist_finditem(list, NULL, sn, AIM_SSI_TYPE_BUDDY))) +		return NULL; +	for (curg=list; curg; curg=curg->next) +		if ((curg->type == AIM_SSI_TYPE_GROUP) && (curg->gid == cur->gid)) +			return curg; +	return NULL; +} + +/** + * Locally find the permit/deny setting item, and return the setting. + * + * @param list A pointer to the current list of items. + * @return Return the current SSI permit deny setting, or 0 if no setting was found. + */ +int aim_ssi_getpermdeny(struct aim_ssi_item *list) +{ +	struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PDINFO); +	if (cur) { +		aim_tlvlist_t *tlvlist = cur->data; +		if (tlvlist) { +			aim_tlv_t *tlv = aim_gettlv(tlvlist, 0x00ca, 1); +			if (tlv && tlv->value) +				return aimutil_get8(tlv->value); +		} +	} +	return 0; +} + +/** + * Locally find the presence flag item, and return the setting.  The returned setting is a  + * bitmask of the user flags that you are visible to.  See the AIM_FLAG_* #defines  + * in aim.h + * + * @param list A pointer to the current list of items. + * @return Return the current visibility mask. + */ +guint32 aim_ssi_getpresence(struct aim_ssi_item *list) +{ +	struct aim_ssi_item *cur = aim_ssi_itemlist_finditem(list, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS); +	if (cur) { +		aim_tlvlist_t *tlvlist = cur->data; +		if (tlvlist) { +			aim_tlv_t *tlv = aim_gettlv(tlvlist, 0x00c9, 1); +			if (tlv && tlv->length) +				return aimutil_get32(tlv->value); +		} +	} +	return 0xFFFFFFFF; +} + +/** + * Add the given packet to the holding queue.  We totally need to send SSI SNACs one at  + * a time, so we have a local queue where packets get put before they are sent, and  + * then we send stuff one at a time, nice and orderly-like. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param fr The newly created SNAC that you want to send. + * @return Return 0 if no errors, otherwise return the error number. + */ +static int aim_ssi_enqueue(aim_session_t *sess, aim_conn_t *conn, aim_frame_t *fr) +{ +	aim_frame_t *cur; + +	if (!sess || !conn || !fr) +		return -EINVAL; + +	fr->next = NULL; +	if (sess->ssi.holding_queue == NULL) { +		sess->ssi.holding_queue = fr; +		if (!sess->ssi.waiting_for_ack) +			aim_ssi_modbegin(sess, conn); +	} else { +		for (cur = sess->ssi.holding_queue; cur->next; cur = cur->next) ; +		cur->next = fr; +	} + +	return 0; +} + +/** + * Send the next SNAC from the holding queue.  This is called  + * automatically when an ack from an add, mod, or del is received.   + * If the queue is empty, it sends the modend SNAC. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +static int aim_ssi_dispatch(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_frame_t *cur; + +	if (!sess || !conn) +		return -EINVAL; + +	if (!sess->ssi.waiting_for_ack) { +		if (sess->ssi.holding_queue) { +			sess->ssi.waiting_for_ack = 1; +			cur = sess->ssi.holding_queue->next; +			sess->ssi.holding_queue->next = NULL; +			aim_tx_enqueue(sess, sess->ssi.holding_queue); +			sess->ssi.holding_queue = cur; +		} else +			aim_ssi_modend(sess, conn); +	} + +	return 0; +} + +/** + * Send SNACs necessary to remove all SSI data from the server list,  + * and then free the local copy as well. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_deletelist(aim_session_t *sess, aim_conn_t *conn) +{ +	int num; +	struct aim_ssi_item *cur, **items; + +	for (cur=sess->ssi.items, num=0; cur; cur=cur->next) +		num++; + +	if (!(items = g_new0(struct aim_ssi_item *, num))) +		return -ENOMEM; + +	for (cur=sess->ssi.items, num=0; cur; cur=cur->next) { +		items[num] = cur; +		num++; +	} + +	aim_ssi_addmoddel(sess, conn, items, num, AIM_CB_SSI_DEL); +	g_free(items); +	aim_ssi_dispatch(sess, conn); +	aim_ssi_freelist(sess); + +	return 0; +} + +/** + * This "cleans" the ssi list.  It does a few things, with the intent of making  + * sure there ain't nothin' wrong with your SSI. + *   -Make sure all buddies are in a group, and all groups have the correct  + *     additional data. + *   -Make sure there are no empty groups in the list.  While there is nothing  + *     wrong empty groups in the SSI, it's wiser to not have them. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_cleanlist(aim_session_t *sess, aim_conn_t *conn) +{ +	unsigned int i; +	struct aim_ssi_item *cur, *parentgroup; + +	/* Make sure we actually need to clean out the list */ +	for (cur=sess->ssi.items, i=0; cur && !i; cur=cur->next) +		/* Any buddies directly in the master group */ +		if ((cur->type == AIM_SSI_TYPE_BUDDY) && (cur->gid == 0x0000)) +			i++; +	if (!i) +		return 0; + +	/* Remove all the additional data from all groups */ +	for (cur=sess->ssi.items; cur; cur=cur->next) +		if ((cur->data) && (cur->type == AIM_SSI_TYPE_GROUP)) { +			aim_freetlvchain((aim_tlvlist_t **)&cur->data); +			cur->data = NULL; +		} + +	/* If there are buddies directly in the master group, make sure  */ +	/* there is a group to put them in.  Any group, any group at all. */ +	for (cur=sess->ssi.items; ((cur) && ((cur->type != AIM_SSI_TYPE_BUDDY) || (cur->gid != 0x0000))); cur=cur->next); +	if (!cur) { +		for (parentgroup=sess->ssi.items; ((parentgroup) && (parentgroup->type!=AIM_SSI_TYPE_GROUP) && (parentgroup->gid==0x0000)); parentgroup=parentgroup->next); +		if (!parentgroup) { +			char *newgroup; +			newgroup = (char*)g_malloc(strlen("Unknown")+1); +			strcpy(newgroup, "Unknown"); +			aim_ssi_addgroups(sess, conn, &newgroup, 1); +		} +	} + +	/* Set parentgroup equal to any arbitray group */ +	for (parentgroup=sess->ssi.items; parentgroup->gid==0x0000 || parentgroup->type!=AIM_SSI_TYPE_GROUP; parentgroup=parentgroup->next); + +	/* If there are any buddies directly in the master group, put them in a real group */ +	for (cur=sess->ssi.items; cur; cur=cur->next) +		if ((cur->type == AIM_SSI_TYPE_BUDDY) && (cur->gid == 0x0000)) { +			aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_DEL); +			cur->gid = parentgroup->gid; +			aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_ADD); +		} + +	/* Rebuild additional data for all groups */ +	for (parentgroup=sess->ssi.items; parentgroup; parentgroup=parentgroup->next) +		if (parentgroup->type == AIM_SSI_TYPE_GROUP) +			aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup); + +	/* Send a mod snac for all groups */ +	i = 0; +	for (cur=sess->ssi.items; cur; cur=cur->next) +		if (cur->type == AIM_SSI_TYPE_GROUP) +			i++; +	if (i > 0) { +		/* Allocate an array of pointers to each of the groups */ +		struct aim_ssi_item **groups; +		if (!(groups = g_new0(struct aim_ssi_item *, i))) +			return -ENOMEM; + +		for (cur=sess->ssi.items, i=0; cur; cur=cur->next) +			if (cur->type == AIM_SSI_TYPE_GROUP) +				groups[i] = cur; + +		aim_ssi_addmoddel(sess, conn, groups, i, AIM_CB_SSI_MOD); +		g_free(groups); +	} + +	/* Send a del snac for any empty groups */ +	i = 0; +	for (cur=sess->ssi.items; cur; cur=cur->next) +		if ((cur->type == AIM_SSI_TYPE_GROUP) && !(cur->data)) +			i++; +	if (i > 0) { +		/* Allocate an array of pointers to each of the groups */ +		struct aim_ssi_item **groups; +		if (!(groups = g_new0(struct aim_ssi_item *, i))) +			return -ENOMEM; + +		for (cur=sess->ssi.items, i=0; cur; cur=cur->next) +			if ((cur->type == AIM_SSI_TYPE_GROUP) && !(cur->data)) +				groups[i] = cur; + +		aim_ssi_addmoddel(sess, conn, groups, i, AIM_CB_SSI_DEL); +		g_free(groups); +	} + +	/* Begin sending SSI SNACs */ +	aim_ssi_dispatch(sess, conn); + +	return 0; +} + +/** + * Add an array of screen names to the given group. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param gn The name of the group to which you want to add these names. + * @param sn An array of null terminated strings of the names you want to add. + * @param num The number of screen names you are adding (size of the sn array). + * @param flags 1 - Add with TLV(0x66) + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num, unsigned int flags) +{ +	struct aim_ssi_item *parentgroup, **newitems; +	guint16 i; + +	if (!sess || !conn || !gn || !sn || !num) +		return -EINVAL; + +	/* Look up the parent group */ +	if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) { +		aim_ssi_addgroups(sess, conn, &gn, 1); +		if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) +			return -ENOMEM; +	} + +	/* Allocate an array of pointers to each of the new items */ +	if (!(newitems = g_new0(struct aim_ssi_item *, num))) +		return -ENOMEM; + +	/* Add items to the local list, and index them in the array */ +	for (i=0; i<num; i++) +		if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, parentgroup, sn[i], AIM_SSI_TYPE_BUDDY))) { +			g_free(newitems); +			return -ENOMEM; +		} else if (flags & 1) { +			aim_tlvlist_t *tl = NULL; +			aim_addtlvtochain_noval(&tl, 0x66); +			newitems[i]->data = tl; +		} + +	/* Send the add item SNAC */ +	if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) { +		g_free(newitems); +		return -i; +	} + +	/* Free the array of pointers to each of the new items */ +	g_free(newitems); + +	/* Rebuild the additional data in the parent group */ +	if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup))) +		return i; + +	/* Send the mod item SNAC */ +	if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD ))) +		return i; + +	/* Begin sending SSI SNACs */ +	if (!(i = aim_ssi_dispatch(sess, conn))) +		return i; + +	return 0; +} + +/** + * Add the master group (the group containing all groups).  This is called by  + * aim_ssi_addgroups, if necessary. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn) +{ +	struct aim_ssi_item *newitem; + +	if (!sess || !conn) +		return -EINVAL; + +	/* Add the item to the local list, and keep a pointer to it */ +	if (!(newitem = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_GROUP))) +		return -ENOMEM; + +	/* If there are any existing groups (technically there shouldn't be, but */ +	/* just in case) then add their group ID#'s to the additional data */ +	aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, newitem); + +	/* Send the add item SNAC */ +	aim_ssi_addmoddel(sess, conn, &newitem, 1, AIM_CB_SSI_ADD); + +	/* Begin sending SSI SNACs */ +	aim_ssi_dispatch(sess, conn); + +	return 0; +} + +/** + * Add an array of groups to the list. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param gn An array of null terminated strings of the names you want to add. + * @param num The number of groups names you are adding (size of the sn array). + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num) +{ +	struct aim_ssi_item *parentgroup, **newitems; +	guint16 i; + +	if (!sess || !conn || !gn || !num) +		return -EINVAL; + +	/* Look up the parent group */ +	if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) { +		aim_ssi_addmastergroup(sess, conn); +		if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) +			return -ENOMEM; +	} + +	/* Allocate an array of pointers to each of the new items */ +	if (!(newitems = g_new0(struct aim_ssi_item *, num))) +		return -ENOMEM; + +	/* Add items to the local list, and index them in the array */ +	for (i=0; i<num; i++) +		if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, parentgroup, gn[i], AIM_SSI_TYPE_GROUP))) { +			g_free(newitems); +			return -ENOMEM; +		} + +	/* Send the add item SNAC */ +	if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) { +		g_free(newitems); +		return -i; +	} + +	/* Free the array of pointers to each of the new items */ +	g_free(newitems); + +	/* Rebuild the additional data in the parent group */ +	if ((i = aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup))) +		return i; + +	/* Send the mod item SNAC */ +	if ((i = aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD))) +		return i; + +	/* Begin sending SSI SNACs */ +	if (!(i = aim_ssi_dispatch(sess, conn))) +		return i; + +	return 0; +} + +/** + * Add an array of a certain type of item to the list.  This can be used for  + * permit buddies, deny buddies, ICQ's ignore buddies, and probably other  + * types, also. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param sn An array of null terminated strings of the names you want to add. + * @param num The number of groups names you are adding (size of the sn array). + * @param type The type of item you want to add.  See the AIM_SSI_TYPE_BLEH  + *        #defines in aim.h. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type) +{ +	struct aim_ssi_item **newitems; +	guint16 i; + +	if (!sess || !conn || !sn || !num) +		return -EINVAL; + +	/* Allocate an array of pointers to each of the new items */ +	if (!(newitems = g_new0(struct aim_ssi_item *, num))) +		return -ENOMEM; + +	/* Add items to the local list, and index them in the array */ +	for (i=0; i<num; i++) +		if (!(newitems[i] = aim_ssi_itemlist_add(&sess->ssi.items, NULL, sn[i], type))) { +			g_free(newitems); +			return -ENOMEM; +		} + +	/* Send the add item SNAC */ +	if ((i = aim_ssi_addmoddel(sess, conn, newitems, num, AIM_CB_SSI_ADD))) { +		g_free(newitems); +		return -i; +	} + +	/* Free the array of pointers to each of the new items */ +	g_free(newitems); + +	/* Begin sending SSI SNACs */ +	if (!(i = aim_ssi_dispatch(sess, conn))) +		return i; + +	return 0; +} + +/** + * Move a buddy from one group to another group.  This basically just deletes the  + * buddy and re-adds it. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param oldgn The group that the buddy is currently in. + * @param newgn The group that the buddy should be moved in to. + * @param sn The name of the buddy to be moved. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn) +{ +	struct aim_ssi_item **groups, *buddy, *cur; +	guint16 i; + +	if (!sess || !conn || !oldgn || !newgn || !sn) +		return -EINVAL; + +	/* Look up the buddy */ +	if (!(buddy = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn, AIM_SSI_TYPE_BUDDY))) +		return -ENOMEM; + +	/* Allocate an array of pointers to the two groups */ +	if (!(groups = g_new0(struct aim_ssi_item *, 2))) +		return -ENOMEM; + +	/* Look up the old parent group */ +	if (!(groups[0] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, oldgn, AIM_SSI_TYPE_GROUP))) { +		g_free(groups); +		return -ENOMEM; +	} + +	/* Look up the new parent group */ +	if (!(groups[1] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, newgn, AIM_SSI_TYPE_GROUP))) { +		g_free(groups); +		return -ENOMEM; +	} + +	/* Send the delete item SNAC */ +	aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_DEL); + +	/* Put the buddy in the new group */ +	buddy->gid = groups[1]->gid; + +	/* Assign a new buddy ID#, because the new group might already have a buddy with this ID# */ +	buddy->bid = 0; +	do { +		buddy->bid += 0x0001; +		for (cur=sess->ssi.items, i=0; ((cur) && (!i)); cur=cur->next) +			if ((cur->bid == buddy->bid) && (cur->gid == buddy->gid) && (cur->type == AIM_SSI_TYPE_BUDDY) && (cur->name) && aim_sncmp(cur->name, buddy->name)) +				i=1; +	} while (i); + +	/* Rebuild the additional data in the two parent groups */ +	aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[0]); +	aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, groups[1]); + +	/* Send the add item SNAC */ +	aim_ssi_addmoddel(sess, conn, &buddy, 1, AIM_CB_SSI_ADD); + +	/* Send the mod item SNAC */ +	aim_ssi_addmoddel(sess, conn, groups, 2, AIM_CB_SSI_MOD); + +	/* Free the temporary array */ +	g_free(groups); + +	/* Begin sending SSI SNACs */ +	aim_ssi_dispatch(sess, conn); + +	return 0; +} + +/** + * Delete an array of screen names from the given group. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param gn The name of the group from which you want to delete these names. + * @param sn An array of null terminated strings of the names you want to delete. + * @param num The number of screen names you are deleting (size of the sn array). + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_delbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num) +{ +	struct aim_ssi_item *cur, *parentgroup, **delitems; +	int i; + +	if (!sess || !conn || !gn || !sn || !num) +		return -EINVAL; + +	/* Look up the parent group */ +	if (!(parentgroup = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn, AIM_SSI_TYPE_GROUP))) +		return -EINVAL; + +	/* Allocate an array of pointers to each of the items to be deleted */ +	delitems = g_new0(struct aim_ssi_item *, num); + +	/* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */ +	for (i=0; i<num; i++) { +		if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn[i], AIM_SSI_TYPE_BUDDY))) { +			g_free(delitems); +			return -EINVAL; +		} + +		/* Remove the delitems from the item list */ +		if (sess->ssi.items == delitems[i]) { +			sess->ssi.items = sess->ssi.items->next; +		} else { +			for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next); +			if (cur->next) +				cur->next = cur->next->next; +		} +	} + +	/* Send the del item SNAC */ +	aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL); + +	/* Free the items */ +	for (i=0; i<num; i++) { +		if (delitems[i]->name) +			g_free(delitems[i]->name); +		if (delitems[i]->data) +			aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data); +		g_free(delitems[i]); +	} +	g_free(delitems); + +	/* Rebuild the additional data in the parent group */ +	aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup); + +	/* Send the mod item SNAC */ +	aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD); + +	/* Delete the group, but only if it's empty */ +	if (!parentgroup->data) +		aim_ssi_delgroups(sess, conn, &parentgroup->name, 1); + +	/* Begin sending SSI SNACs */ +	aim_ssi_dispatch(sess, conn); + +	return 0; +} + +/** + * Delete the master group from the item list.  There can be only one. + * Er, so just find the one master group and delete it. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_delmastergroup(aim_session_t *sess, aim_conn_t *conn) +{ +	struct aim_ssi_item *cur, *delitem; + +	if (!sess || !conn) +		return -EINVAL; + +	/* Make delitem a pointer to the aim_ssi_item to be deleted */ +	if (!(delitem = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) +		return -EINVAL; + +	/* Remove delitem from the item list */ +	if (sess->ssi.items == delitem) { +		sess->ssi.items = sess->ssi.items->next; +	} else { +		for (cur=sess->ssi.items; (cur->next && (cur->next!=delitem)); cur=cur->next); +		if (cur->next) +			cur->next = cur->next->next; +	} + +	/* Send the del item SNAC */ +	aim_ssi_addmoddel(sess, conn, &delitem, 1, AIM_CB_SSI_DEL); + +	/* Free the item */ +	if (delitem->name) +		g_free(delitem->name); +	if (delitem->data) +		aim_freetlvchain((aim_tlvlist_t **)&delitem->data); +	g_free(delitem); + +	/* Begin sending SSI SNACs */ +	aim_ssi_dispatch(sess, conn); + +	return 0; +} + +/** + * Delete an array of groups. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param gn An array of null terminated strings of the groups you want to delete. + * @param num The number of groups you are deleting (size of the gn array). + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_delgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num) { +	struct aim_ssi_item *cur, *parentgroup, **delitems; +	int i; + +	if (!sess || !conn || !gn || !num) +		return -EINVAL; + +	/* Look up the parent group */ +	if (!(parentgroup = aim_ssi_itemlist_find(sess->ssi.items, 0, 0))) +		return -EINVAL; + +	/* Allocate an array of pointers to each of the items to be deleted */ +	delitems = g_new0(struct aim_ssi_item *, num); + +	/* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */ +	for (i=0; i<num; i++) { +		if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, gn[i], AIM_SSI_TYPE_GROUP))) { +			g_free(delitems); +			return -EINVAL; +		} + +		/* Remove the delitems from the item list */ +		if (sess->ssi.items == delitems[i]) { +			sess->ssi.items = sess->ssi.items->next; +		} else { +			for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next); +			if (cur->next) +				cur->next = cur->next->next; +		} +	} + +	/* Send the del item SNAC */ +	aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL); + +	/* Free the items */ +	for (i=0; i<num; i++) { +		if (delitems[i]->name) +			g_free(delitems[i]->name); +		if (delitems[i]->data) +			aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data); +		g_free(delitems[i]); +	} +	g_free(delitems); + +	/* Rebuild the additional data in the parent group */ +	aim_ssi_itemlist_rebuildgroup(&sess->ssi.items, parentgroup); + +	/* Send the mod item SNAC */ +	aim_ssi_addmoddel(sess, conn, &parentgroup, 1, AIM_CB_SSI_MOD); + +	/* Delete the group, but only if it's empty */ +	if (!parentgroup->data) +		aim_ssi_delmastergroup(sess, conn); + +	/* Begin sending SSI SNACs */ +	aim_ssi_dispatch(sess, conn); + +	return 0; +} + +/** + * Delete an array of a certain type of item from the list.  This can be  + * used for permit buddies, deny buddies, ICQ's ignore buddies, and  + * probably other types, also. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param sn An array of null terminated strings of the items you want to delete. + * @param num The number of items you are deleting (size of the sn array). + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type) { +	struct aim_ssi_item *cur, **delitems; +	int i; + +	if (!sess || !conn || !sn || !num || (type!=AIM_SSI_TYPE_PERMIT && type!=AIM_SSI_TYPE_DENY)) +		return -EINVAL; + +	/* Allocate an array of pointers to each of the items to be deleted */ +	delitems = g_new0(struct aim_ssi_item *, num); + +	/* Make the delitems array a pointer to the aim_ssi_item structs to be deleted */ +	for (i=0; i<num; i++) { +		if (!(delitems[i] = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, sn[i], type))) { +			g_free(delitems); +			return -EINVAL; +		} + +		/* Remove the delitems from the item list */ +		if (sess->ssi.items == delitems[i]) { +			sess->ssi.items = sess->ssi.items->next; +		} else { +			for (cur=sess->ssi.items; (cur->next && (cur->next!=delitems[i])); cur=cur->next); +			if (cur->next) +				cur->next = cur->next->next; +		} +	} + +	/* Send the del item SNAC */ +	aim_ssi_addmoddel(sess, conn, delitems, num, AIM_CB_SSI_DEL); + +	/* Free the items */ +	for (i=0; i<num; i++) { +		if (delitems[i]->name) +			g_free(delitems[i]->name); +		if (delitems[i]->data) +			aim_freetlvchain((aim_tlvlist_t **)&delitems[i]->data); +		g_free(delitems[i]); +	} +	g_free(delitems); + +	/* Begin sending SSI SNACs */ +	aim_ssi_dispatch(sess, conn); + +	return 0; +} + +/** + * Stores your permit/deny setting on the server, and starts using it. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param permdeny Your permit/deny setting.  Can be one of the following: + *        1 - Allow all users + *        2 - Block all users + *        3 - Allow only the users below + *        4 - Block only the users below + *        5 - Allow only users on my buddy list + * @param vismask A bitmask of the class of users to whom you want to be  + *        visible.  See the AIM_FLAG_BLEH #defines in aim.h + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_setpermdeny(aim_session_t *sess, aim_conn_t *conn, guint8 permdeny, guint32 vismask) { +	struct aim_ssi_item *cur; //, *tmp; +//	guint16 j; +	aim_tlv_t *tlv; + +	if (!sess || !conn) +		return -EINVAL; + +	/* Look up the permit/deny settings item */ +	cur = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PDINFO); + +	if (cur) { +		/* The permit/deny item exists */ +		if (cur->data && (tlv = aim_gettlv(cur->data, 0x00ca, 1))) { +			/* Just change the value of the x00ca TLV */ +			if (tlv->length != 1) { +				tlv->length = 1; +				g_free(tlv->value); +				tlv->value = (guint8 *)g_malloc(sizeof(guint8)); +			} +			tlv->value[0] = permdeny; +		} else { +			/* Need to add the x00ca TLV to the TLV chain */ +			aim_addtlvtochain8((aim_tlvlist_t**)&cur->data, 0x00ca, permdeny); +		} + +		if (cur->data && (tlv = aim_gettlv(cur->data, 0x00cb, 1))) { +			/* Just change the value of the x00cb TLV */ +			if (tlv->length != 4) { +				tlv->length = 4; +				g_free(tlv->value); +				tlv->value = (guint8 *)g_malloc(4*sizeof(guint8)); +			} +			aimutil_put32(tlv->value, vismask); +		} else { +			/* Need to add the x00cb TLV to the TLV chain */ +			aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00cb, vismask); +		} + +		/* Send the mod item SNAC */ +		aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_MOD); +	} else { +		/* Need to add the permit/deny item */ +		if (!(cur = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PDINFO))) +			return -ENOMEM; +		aim_addtlvtochain8((aim_tlvlist_t**)&cur->data, 0x00ca, permdeny); +		aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00cb, vismask); +		aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_ADD); +	} + +	/* Begin sending SSI SNACs */ +	aim_ssi_dispatch(sess, conn); + +	return 0; +} + +/** + * Stores your setting for whether you should show up as idle or not. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param presence I think it's a bitmask, but I only know what one of the bits is: + *        0x00000400 - Allow others to see your idle time + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_setpresence(aim_session_t *sess, aim_conn_t *conn, guint32 presence) { +	struct aim_ssi_item *cur; //, *tmp; +//	guint16 j; +	aim_tlv_t *tlv; + +	if (!sess || !conn) +		return -EINVAL; + +	/* Look up the item */ +	cur = aim_ssi_itemlist_finditem(sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS); + +	if (cur) { +		/* The item exists */ +		if (cur->data && (tlv = aim_gettlv(cur->data, 0x00c9, 1))) { +			/* Just change the value of the x00c9 TLV */ +			if (tlv->length != 4) { +				tlv->length = 4; +				g_free(tlv->value); +				tlv->value = (guint8 *)g_malloc(4*sizeof(guint8)); +			} +			aimutil_put32(tlv->value, presence); +		} else { +			/* Need to add the x00c9 TLV to the TLV chain */ +			aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00c9, presence); +		} + +		/* Send the mod item SNAC */ +		aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_MOD); +	} else { +		/* Need to add the item */ +		if (!(cur = aim_ssi_itemlist_add(&sess->ssi.items, NULL, NULL, AIM_SSI_TYPE_PRESENCEPREFS))) +			return -ENOMEM; +		aim_addtlvtochain32((aim_tlvlist_t**)&cur->data, 0x00c9, presence); +		aim_ssi_addmoddel(sess, conn, &cur, 1, AIM_CB_SSI_ADD); +	} + +	/* Begin sending SSI SNACs */ +	aim_ssi_dispatch(sess, conn); + +	return 0; +} + +/* + * Request SSI Rights. + */ +int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_REQRIGHTS); +} + +/* + * SSI Rights Information. + */ +static int parserights(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	aim_rxcallback_t userfunc; + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx); + +	return ret; +} + +/* + * Request SSI Data. + * + * The data will only be sent if it is newer than the posted local + * timestamp and revision. + *  + * Note that the client should never increment the revision, only the server. + *  + */ +int aim_ssi_reqdata(aim_session_t *sess, aim_conn_t *conn, time_t localstamp, guint16 localrev) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !conn) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10+4+2))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_REQLIST, 0x0000, NULL, 0); + +	aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_REQLIST, 0x0000, snacid); +	aimbs_put32(&fr->data, localstamp); +	aimbs_put16(&fr->data, localrev); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +int aim_ssi_reqalldata(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; + +	if (!sess || !conn) +		return -EINVAL; + +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 10))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, NULL, 0); + +	aim_putsnac(&fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_REQFULLLIST, 0x0000, snacid); + +	aim_tx_enqueue(sess, fr); + +	return 0; +} + +/* + * SSI Data. + */ +static int parsedata(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	aim_rxcallback_t userfunc; +	struct aim_ssi_item *cur = NULL; +	guint8 fmtver; /* guess */ +	guint16 revision; +	guint32 timestamp; + +	/* When you set the version for the SSI family to 2-4, the beginning of this changes. +	 * Instead of the version and then the revision, there is "0x0006" and then a type  +	 * 0x0001 TLV containing the 2 byte SSI family version that you sent earlier.  Also,  +	 * the SNAC flags go from 0x0000 to 0x8000.  I guess the 0x0006 is the length of the  +	 * TLV(s) that follow.  The rights SNAC does the same thing, with the differing flag  +	 * and everything. +	 */ + +	fmtver = aimbs_get8(bs); /* Version of ssi data.  Should be 0x00 */ +	revision = aimbs_get16(bs); /* # of times ssi data has been modified */ +	if (revision != 0) +		sess->ssi.revision = revision; + +	for (cur = sess->ssi.items; cur && cur->next; cur=cur->next) ; + +	while (aim_bstream_empty(bs) > 4) { /* last four bytes are stamp */ +		guint16 namelen, tbslen; + +		if (!sess->ssi.items) { +			if (!(sess->ssi.items = g_new0(struct aim_ssi_item, 1))) +				return -ENOMEM; +			cur = sess->ssi.items; +		} else { +			if (!(cur->next = g_new0(struct aim_ssi_item, 1))) +				return -ENOMEM; +			cur = cur->next; +		} + +		if ((namelen = aimbs_get16(bs))) +			cur->name = aimbs_getstr(bs, namelen); +		cur->gid = aimbs_get16(bs); +		cur->bid = aimbs_get16(bs); +		cur->type = aimbs_get16(bs); + +		if ((tbslen = aimbs_get16(bs))) { +			aim_bstream_t tbs; + +			aim_bstream_init(&tbs, bs->data + bs->offset /* XXX */, tbslen); +			cur->data = (void *)aim_readtlvchain(&tbs); +			aim_bstream_advance(bs, tbslen); +		} +	} + +	timestamp = aimbs_get32(bs); +	if (timestamp != 0) +		sess->ssi.timestamp = timestamp; +	sess->ssi.received_data = 1; + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, fmtver, sess->ssi.revision, sess->ssi.timestamp, sess->ssi.items); + +	return ret; +} + +/* + * SSI Data Enable Presence. + * + * Should be sent after receiving 13/6 or 13/f to tell the server you + * are ready to begin using the list.  It will promptly give you the + * presence information for everyone in your list and put your permit/deny + * settings into effect. + *  + */ +int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, 0x0007); +} + +/* + * Stuff for SSI authorizations. The code used to work with the old im_ch4 + * messages, but those are supposed to be obsolete. This is probably + * ICQ-specific. + */ + +/** + * Request authorization to add someone to the server-side buddy list. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param uin The contact's ICQ UIN. + * @param reason The reason string to send with the request. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_auth_request( aim_session_t *sess, aim_conn_t *conn, char *uin, char *reason ) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	int snaclen; +	 +	snaclen = 10 + 1 + strlen( uin ) + 2 + strlen( reason ) + 2; +	 +	if( !( fr = aim_tx_new( sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen ) ) ) +		return -ENOMEM; + +	snacid = aim_cachesnac( sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, NULL, 0 ); +	aim_putsnac( &fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREQ, 0x0000, snacid ); +	 +	aimbs_put8( &fr->data, strlen( uin ) ); +	aimbs_putraw( &fr->data, (guint8 *)uin, strlen( uin ) ); +	aimbs_put16( &fr->data, strlen( reason ) ); +	aimbs_putraw( &fr->data, (guint8 *)reason, strlen( reason ) ); +	aimbs_put16( &fr->data, 0 ); +	 +	aim_tx_enqueue( sess, fr ); +	 +	return( 0 ); +} + +/** + * Reply to an authorization request to add someone to the server-side buddy list. + * + * @param sess The oscar session. + * @param conn The bos connection for this session. + * @param uin The contact's ICQ UIN. + * @param yesno 1 == Permit, 0 == Deny + * @param reason The reason string to send with the request. + * @return Return 0 if no errors, otherwise return the error number. + */ +int aim_ssi_auth_reply( aim_session_t *sess, aim_conn_t *conn, char *uin, int yesno, char *reason ) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	int snaclen; +	 +	snaclen = 10 + 1 + strlen( uin ) + 3 + strlen( reason ); +	 +	if( !( fr = aim_tx_new( sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen ) ) ) +		return -ENOMEM; +	 +	snacid = aim_cachesnac( sess, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, NULL, 0 ); +	aim_putsnac( &fr->data, AIM_CB_FAM_SSI, AIM_CB_SSI_SENDAUTHREP, 0x0000, snacid ); +	 +	aimbs_put8( &fr->data, strlen( uin ) ); +	aimbs_putraw( &fr->data, (guint8 *)uin, strlen( uin ) ); +	aimbs_put8( &fr->data, yesno ); +	aimbs_put16( &fr->data, strlen( reason ) ); +	aimbs_putraw( &fr->data, (guint8 *)reason, strlen( reason ) ); +	 +	aim_tx_enqueue( sess, fr ); +	 +	return( 0 ); +} + + +/* + * SSI Add/Mod/Del Item(s). + * + * Sends the SNAC to add, modify, or delete an item from the server-stored + * information.  These 3 SNACs all have an identical structure.  The only + * difference is the subtype that is set for the SNAC. + *  + */ +int aim_ssi_addmoddel(aim_session_t *sess, aim_conn_t *conn, struct aim_ssi_item **items, unsigned int num, guint16 subtype) +{ +	aim_frame_t *fr; +	aim_snacid_t snacid; +	int i, snaclen, listlen; +	char *list = NULL; + +	if (!sess || !conn || !items || !num) +		return -EINVAL; + +	snaclen = 10; /* For family, subtype, flags, and SNAC ID */ +	listlen = 0; +	for (i=0; i<num; i++) { +		snaclen += 10; /* For length, GID, BID, type, and length */ +		if (items[i]->name) { +			snaclen += strlen(items[i]->name); +			 +			if (subtype == AIM_CB_SSI_ADD) { +				list = g_realloc(list, listlen + strlen(items[i]->name) + 1); +				strcpy(list + listlen, items[i]->name); +				listlen += strlen(items[i]->name) + 1; +			} +		} else { +			if (subtype == AIM_CB_SSI_ADD) { +				list = g_realloc(list, listlen + 1); +				list[listlen] = '\0'; +				listlen ++; +			} +		} +		if (items[i]->data) +			snaclen += aim_sizetlvchain((aim_tlvlist_t **)&items[i]->data); +	} +	 +	if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, snaclen))) +		return -ENOMEM; + +	snacid = aim_cachesnac(sess, AIM_CB_FAM_SSI, subtype, 0x0000, list, list ? listlen : 0); +	aim_putsnac(&fr->data, AIM_CB_FAM_SSI, subtype, 0x0000, snacid); +	 +	g_free(list); + +	for (i=0; i<num; i++) { +		aimbs_put16(&fr->data, items[i]->name ? strlen(items[i]->name) : 0); +		if (items[i]->name) +			aimbs_putraw(&fr->data, (guint8 *)items[i]->name, strlen(items[i]->name)); +		aimbs_put16(&fr->data, items[i]->gid); +		aimbs_put16(&fr->data, items[i]->bid); +		aimbs_put16(&fr->data, items[i]->type); +		aimbs_put16(&fr->data, items[i]->data ? aim_sizetlvchain((aim_tlvlist_t **)&items[i]->data) : 0); +		if (items[i]->data) +			aim_writetlvchain(&fr->data, (aim_tlvlist_t **)&items[i]->data); +	} + +	aim_ssi_enqueue(sess, conn, fr); + +	return 0; +} + +/* + * SSI Add/Mod/Del Ack. + * + * Response to add, modify, or delete SNAC (sent with aim_ssi_addmoddel). + * + */ +static int parseack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	aim_rxcallback_t userfunc; +	aim_snac_t *origsnac; + +	sess->ssi.waiting_for_ack = 0; +	aim_ssi_dispatch(sess, rx->conn); +	 +	origsnac = aim_remsnac(sess, snac->id); +	 +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx, origsnac); +	 +	if (origsnac) { +		g_free(origsnac->data); +		g_free(origsnac); +	} +	 +	return ret; +} + +/* + * SSI Begin Data Modification. + * + * Tells the server you're going to start modifying data. + *  + */ +int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTART); +} + +/* + * SSI End Data Modification. + * + * Tells the server you're done modifying data. + * + */ +int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn) +{ +	return aim_genericreq_n(sess, conn, AIM_CB_FAM_SSI, AIM_CB_SSI_EDITSTOP); +} + +/* + * SSI Data Unchanged. + * + * Response to aim_ssi_reqdata() if the server-side data is not newer than + * posted local stamp/revision. + * + */ +static int parsedataunchanged(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	int ret = 0; +	aim_rxcallback_t userfunc; + +	sess->ssi.received_data = 1; + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		ret = userfunc(sess, rx); + +	return ret; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == AIM_CB_SSI_RIGHTSINFO) +		return parserights(sess, mod, rx, snac, bs); +	else if (snac->subtype == AIM_CB_SSI_LIST) +		return parsedata(sess, mod, rx, snac, bs); +	else if (snac->subtype == AIM_CB_SSI_SRVACK) +		return parseack(sess, mod, rx, snac, bs); +	else if (snac->subtype == AIM_CB_SSI_NOLIST) +		return parsedataunchanged(sess, mod, rx, snac, bs); + +	return 0; +} + +static void ssi_shutdown(aim_session_t *sess, aim_module_t *mod) +{ +	aim_ssi_freelist(sess); + +	return; +} + +int ssi_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = AIM_CB_FAM_SSI; +	mod->version = 0x0003; +	mod->toolid = 0x0110; +	mod->toolversion = 0x0629; +	mod->flags = 0; +	strncpy(mod->name, "ssi", sizeof(mod->name)); +	mod->snachandler = snachandler; +	mod->shutdown = ssi_shutdown; + +	return 0; +} diff --git a/protocols/oscar/ssi.h b/protocols/oscar/ssi.h new file mode 100644 index 00000000..94b18d60 --- /dev/null +++ b/protocols/oscar/ssi.h @@ -0,0 +1,78 @@ +#ifndef __OSCAR_SSI_H__ +#define __OSCAR_SSI_H__ + +#define AIM_CB_FAM_SSI 0x0013 /* Server stored information */ + +/* + * SNAC Family: Server-Stored Buddy Lists + */ +#define AIM_CB_SSI_ERROR 0x0001 +#define AIM_CB_SSI_REQRIGHTS 0x0002 +#define AIM_CB_SSI_RIGHTSINFO 0x0003 +#define AIM_CB_SSI_REQFULLLIST 0x0004 +#define AIM_CB_SSI_REQLIST 0x0005 +#define AIM_CB_SSI_LIST 0x0006 +#define AIM_CB_SSI_ACTIVATE 0x0007 +#define AIM_CB_SSI_ADD 0x0008 +#define AIM_CB_SSI_MOD 0x0009 +#define AIM_CB_SSI_DEL 0x000A +#define AIM_CB_SSI_SRVACK 0x000E +#define AIM_CB_SSI_NOLIST 0x000F +#define AIM_CB_SSI_EDITSTART 0x0011 +#define AIM_CB_SSI_EDITSTOP 0x0012 +#define AIM_CB_SSI_SENDAUTHREQ 0x0018 +#define AIM_CB_SSI_SERVAUTHREQ 0x0019 +#define AIM_CB_SSI_SENDAUTHREP 0x001A +#define AIM_CB_SSI_SERVAUTHREP 0x001B + + +#define AIM_SSI_TYPE_BUDDY         0x0000 +#define AIM_SSI_TYPE_GROUP         0x0001 +#define AIM_SSI_TYPE_PERMIT        0x0002 +#define AIM_SSI_TYPE_DENY          0x0003 +#define AIM_SSI_TYPE_PDINFO        0x0004 +#define AIM_SSI_TYPE_PRESENCEPREFS 0x0005 + +struct aim_ssi_item { +	char *name; +	guint16 gid; +	guint16 bid; +	guint16 type; +	void *data; +	struct aim_ssi_item *next; +}; + +/* These build the actual SNACs and queue them to be sent */ +int aim_ssi_reqrights(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_reqdata(aim_session_t *sess, aim_conn_t *conn, time_t localstamp, guint16 localrev); +int aim_ssi_reqalldata(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_enable(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_addmoddel(aim_session_t *sess, aim_conn_t *conn, struct aim_ssi_item **items, unsigned int num, guint16 subtype); +int aim_ssi_modbegin(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_modend(aim_session_t *sess, aim_conn_t *conn); + +/* These handle the local variables */ +struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid); +struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, char *gn, char *sn, guint16 type); +struct aim_ssi_item *aim_ssi_itemlist_findparent(struct aim_ssi_item *list, char *sn); +int aim_ssi_getpermdeny(struct aim_ssi_item *list); +guint32 aim_ssi_getpresence(struct aim_ssi_item *list); + +/* Send packets */ +int aim_ssi_cleanlist(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_addbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num, unsigned int flags); +int aim_ssi_addmastergroup(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_addgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num); +int aim_ssi_addpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type); +int aim_ssi_movebuddy(aim_session_t *sess, aim_conn_t *conn, char *oldgn, char *newgn, char *sn); +int aim_ssi_delbuddies(aim_session_t *sess, aim_conn_t *conn, char *gn, char **sn, unsigned int num); +int aim_ssi_delmastergroup(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_delgroups(aim_session_t *sess, aim_conn_t *conn, char **gn, unsigned int num); +int aim_ssi_deletelist(aim_session_t *sess, aim_conn_t *conn); +int aim_ssi_delpord(aim_session_t *sess, aim_conn_t *conn, char **sn, unsigned int num, guint16 type); +int aim_ssi_setpermdeny(aim_session_t *sess, aim_conn_t *conn, guint8 permdeny, guint32 vismask); +int aim_ssi_setpresence(aim_session_t *sess, aim_conn_t *conn, guint32 presence); +int aim_ssi_auth_request(aim_session_t *sess, aim_conn_t *conn, char *uin, char *reason); +int aim_ssi_auth_reply(aim_session_t *sess, aim_conn_t *conn, char *uin, int yesno, char *reason); + +#endif /* __OSCAR_SSI_H__ */ diff --git a/protocols/oscar/stats.c b/protocols/oscar/stats.c new file mode 100644 index 00000000..affd82fe --- /dev/null +++ b/protocols/oscar/stats.c @@ -0,0 +1,38 @@ + +#include <aim.h> + +static int reportinterval(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ +	guint16 interval; +	aim_rxcallback_t userfunc; + +	interval = aimbs_get16(bs); + +	if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) +		return userfunc(sess, rx, interval); + +	return 0; +} + +static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) +{ + +	if (snac->subtype == 0x0002) +		return reportinterval(sess, mod, rx, snac, bs); + +	return 0; +} + +int stats_modfirst(aim_session_t *sess, aim_module_t *mod) +{ + +	mod->family = 0x000b; +	mod->version = 0x0001; +	mod->toolid = 0x0104; +	mod->toolversion = 0x0001; +	mod->flags = 0; +	strncpy(mod->name, "stats", sizeof(mod->name)); +	mod->snachandler = snachandler; + +	return 0; +} diff --git a/protocols/oscar/tlv.c b/protocols/oscar/tlv.c new file mode 100644 index 00000000..9d827caf --- /dev/null +++ b/protocols/oscar/tlv.c @@ -0,0 +1,599 @@ +#include <aim.h> + +static void freetlv(aim_tlv_t **oldtlv) +{ +	if (!oldtlv || !*oldtlv) +		return; +	 +	g_free((*oldtlv)->value); +	g_free(*oldtlv); +	*oldtlv = NULL; +} + +/** + * aim_readtlvchain - Read a TLV chain from a buffer. + * @buf: Input buffer + * @maxlen: Length of input buffer + * + * Reads and parses a series of TLV patterns from a data buffer; the + * returned structure is manipulatable with the rest of the TLV + * routines.  When done with a TLV chain, aim_freetlvchain() should + * be called to free the dynamic substructures. + * + * XXX There should be a flag setable here to have the tlvlist contain + * bstream references, so that at least the ->value portion of each  + * element doesn't need to be malloc/memcpy'd.  This could prove to be + * just as effecient as the in-place TLV parsing used in a couple places + * in libfaim. + * + */ +aim_tlvlist_t *aim_readtlvchain(aim_bstream_t *bs) +{ +	aim_tlvlist_t *list = NULL, *cur; +	guint16 type, length; + +	while (aim_bstream_empty(bs)) { + +		type = aimbs_get16(bs); +		length = aimbs_get16(bs); + +		cur = g_new0(aim_tlvlist_t, 1); + +		cur->tlv = g_new0(aim_tlv_t, 1); +		cur->tlv->type = type; +		if ((cur->tlv->length = length)) +			cur->tlv->value = aimbs_getraw(bs, length);	 + +		cur->next = list; +		list = cur; +	} + +	return list; +} + +/** + * aim_freetlvchain - Free a TLV chain structure + * @list: Chain to be freed + * + * Walks the list of TLVs in the passed TLV chain and + * frees each one. Note that any references to this data + * should be removed before calling this. + * + */ +void aim_freetlvchain(aim_tlvlist_t **list) +{ +	aim_tlvlist_t *cur; + +	if (!list || !*list) +		return; + +	for (cur = *list; cur; ) { +		aim_tlvlist_t *tmp; +		 +		freetlv(&cur->tlv); + +		tmp = cur->next; +		g_free(cur); +		cur = tmp; +	} + +	list = NULL; + +	return; +} + +/** + * aim_counttlvchain - Count the number of TLVs in a chain + * @list: Chain to be counted + * + * Returns the number of TLVs stored in the passed chain. + * + */ +int aim_counttlvchain(aim_tlvlist_t **list) +{ +	aim_tlvlist_t *cur; +	int count; + +	if (!list || !*list) +		return 0; + +	for (cur = *list, count = 0; cur; cur = cur->next) +		count++; + +	return count; +} + +/** + * aim_sizetlvchain - Count the number of bytes in a TLV chain + * @list: Chain to be sized + * + * Returns the number of bytes that would be needed to  + * write the passed TLV chain to a data buffer. + * + */ +int aim_sizetlvchain(aim_tlvlist_t **list) +{ +	aim_tlvlist_t *cur; +	int size; + +	if (!list || !*list) +		return 0; + +	for (cur = *list, size = 0; cur; cur = cur->next) +		size += (4 + cur->tlv->length); + +	return size; +} + +/** + * aim_addtlvtochain_str - Add a string to a TLV chain + * @list: Desination chain (%NULL pointer if empty) + * @type: TLV type + * @str: String to add + * @len: Length of string to add (not including %NULL) + * + * Adds the passed string as a TLV element of the passed type + * to the TLV chain. + * + */ +int aim_addtlvtochain_raw(aim_tlvlist_t **list, const guint16 t, const guint16 l, const guint8 *v) +{ +	aim_tlvlist_t *newtlv, *cur; + +	if (!list) +		return 0; + +	if (!(newtlv = g_new0(aim_tlvlist_t, 1))) +		return 0; + +	if (!(newtlv->tlv = g_new0(aim_tlv_t, 1))) { +		g_free(newtlv); +		return 0; +	} +	newtlv->tlv->type = t; +	if ((newtlv->tlv->length = l)) { +		newtlv->tlv->value = (guint8 *)g_malloc(newtlv->tlv->length); +		memcpy(newtlv->tlv->value, v, newtlv->tlv->length); +	} + +	if (!*list) +		*list = newtlv; +	else { +		for(cur = *list; cur->next; cur = cur->next) +			; +		cur->next = newtlv; +	} + +	return newtlv->tlv->length; +} + +/** + * aim_addtlvtochain8 - Add a 8bit integer to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @val: Value to add + * + * Adds a one-byte unsigned integer to a TLV chain. + * + */ +int aim_addtlvtochain8(aim_tlvlist_t **list, const guint16 t, const guint8 v) +{ +	guint8 v8[1]; + +	aimutil_put8(v8, v); + +	return aim_addtlvtochain_raw(list, t, 1, v8); +} + +/** + * aim_addtlvtochain16 - Add a 16bit integer to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @val: Value to add + * + * Adds a two-byte unsigned integer to a TLV chain. + * + */ +int aim_addtlvtochain16(aim_tlvlist_t **list, const guint16 t, const guint16 v) +{ +	guint8 v16[2]; + +	aimutil_put16(v16, v); + +	return aim_addtlvtochain_raw(list, t, 2, v16); +} + +/** + * aim_addtlvtochain32 - Add a 32bit integer to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @val: Value to add + * + * Adds a four-byte unsigned integer to a TLV chain. + * + */ +int aim_addtlvtochain32(aim_tlvlist_t **list, const guint16 t, const guint32 v) +{ +	guint8 v32[4]; + +	aimutil_put32(v32, v); + +	return aim_addtlvtochain_raw(list, t, 4, v32); +} + +/** + * aim_addtlvtochain_availmsg - Add a ICQ availability message to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @val: Value to add + * + * Adds a available message to a TLV chain + * + */ +int aim_addtlvtochain_availmsg(aim_tlvlist_t **list, const guint16 t, const char *msg) +{ +	int ret; +	guint16 unknown_data = 0x00; +	guint8 add_data_len = 4; +	guint16 msg_len = strlen(msg); +	guint8 total_len = strlen(msg) + add_data_len; +	guint8 *data, *cur; +	guint8 alloc_len = msg_len + (3*sizeof(guint16)) + (2*sizeof(guint8)); +	data = cur = g_malloc(alloc_len); +	 +	cur += aimutil_put16(cur, 2); +	cur += aimutil_put8(cur, add_data_len); +	cur += aimutil_put8(cur, total_len); +	cur += aimutil_put16(cur, msg_len); +	cur += aimutil_putstr(cur, msg, msg_len); +	cur += aimutil_put16(cur, unknown_data); + +	ret = aim_addtlvtochain_raw(list, t, alloc_len, data); +	g_free(data); + +	return ret; +} + +/** + * aim_addtlvtochain_caps - Add a capability block to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * @caps: Bitfield of capability flags to send + * + * Adds a block of capability blocks to a TLV chain. The bitfield + * passed in should be a bitwise %OR of any of the %AIM_CAPS constants: + * + */ +int aim_addtlvtochain_caps(aim_tlvlist_t **list, const guint16 t, const guint32 caps) +{ +	guint8 buf[16*16]; /* XXX icky fixed length buffer */ +	aim_bstream_t bs; + +	if (!caps) +		return 0; /* nothing there anyway */ + +	aim_bstream_init(&bs, buf, sizeof(buf)); + +	aim_putcap(&bs, caps); + +	return aim_addtlvtochain_raw(list, t, aim_bstream_curpos(&bs), buf); +} + +int aim_addtlvtochain_userinfo(aim_tlvlist_t **list, guint16 type, aim_userinfo_t *ui) +{ +	guint8 buf[1024]; /* bleh */ +	aim_bstream_t bs; + +	aim_bstream_init(&bs, buf, sizeof(buf)); + +	aim_putuserinfo(&bs, ui); + +	return aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf); +} + +/** + * aim_addtlvtochain_noval - Add a blank TLV to a TLV chain + * @list: Destination chain + * @type: TLV type to add + * + * Adds a TLV with a zero length to a TLV chain. + * + */ +int aim_addtlvtochain_noval(aim_tlvlist_t **list, const guint16 t) +{ +	return aim_addtlvtochain_raw(list, t, 0, NULL); +} + +/* + * Note that the inner TLV chain will not be modifiable as a tlvchain once + * it is written using this.  Or rather, it can be, but updates won't be + * made to this. + * + * XXX should probably support sublists for real. + *  + * This is so neat. + * + */ +int aim_addtlvtochain_frozentlvlist(aim_tlvlist_t **list, guint16 type, aim_tlvlist_t **tl) +{ +	guint8 *buf; +	int buflen; +	aim_bstream_t bs; + +	buflen = aim_sizetlvchain(tl); + +	if (buflen <= 0) +		return 0; + +	if (!(buf = g_malloc(buflen))) +		return 0; + +	aim_bstream_init(&bs, buf, buflen); + +	aim_writetlvchain(&bs, tl); + +	aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf); + +	g_free(buf); + +	return buflen; +} + +int aim_addtlvtochain_chatroom(aim_tlvlist_t **list, guint16 type, guint16 exchange, const char *roomname, guint16 instance) +{ +	guint8 *buf; +	int buflen; +	aim_bstream_t bs; + +	buflen = 2 + 1 + strlen(roomname) + 2; +	 +	if (!(buf = g_malloc(buflen))) +		return 0; + +	aim_bstream_init(&bs, buf, buflen); + +	aimbs_put16(&bs, exchange); +	aimbs_put8(&bs, strlen(roomname)); +	aimbs_putraw(&bs, (guint8 *)roomname, strlen(roomname)); +	aimbs_put16(&bs, instance); + +	aim_addtlvtochain_raw(list, type, aim_bstream_curpos(&bs), buf); + +	g_free(buf); + +	return 0; +} + +/** + * aim_writetlvchain - Write a TLV chain into a data buffer. + * @buf: Destination buffer + * @buflen: Maximum number of bytes that will be written to buffer + * @list: Source TLV chain + * + * Copies a TLV chain into a raw data buffer, writing only the number + * of bytes specified. This operation does not free the chain;  + * aim_freetlvchain() must still be called to free up the memory used + * by the chain structures. + * + * XXX clean this up, make better use of bstreams  + */ +int aim_writetlvchain(aim_bstream_t *bs, aim_tlvlist_t **list) +{ +	int goodbuflen; +	aim_tlvlist_t *cur; + +	/* do an initial run to test total length */ +	for (cur = *list, goodbuflen = 0; cur; cur = cur->next) { +		goodbuflen += 2 + 2; /* type + len */ +		goodbuflen += cur->tlv->length; +	} + +	if (goodbuflen > aim_bstream_empty(bs)) +		return 0; /* not enough buffer */ + +	/* do the real write-out */ +	for (cur = *list; cur; cur = cur->next) { +		aimbs_put16(bs, cur->tlv->type); +		aimbs_put16(bs, cur->tlv->length); +		if (cur->tlv->length) +			aimbs_putraw(bs, cur->tlv->value, cur->tlv->length); +	} + +	return 1; /* XXX this is a nonsensical return */ +} + + +/** + * aim_gettlv - Grab the Nth TLV of type type in the TLV list list. + * @list: Source chain + * @type: Requested TLV type + * @nth: Index of TLV of type to get + * + * Returns a pointer to an aim_tlv_t of the specified type;  + * %NULL on error.  The @nth parameter is specified starting at %1. + * In most cases, there will be no more than one TLV of any type + * in a chain. + * + */ +aim_tlv_t *aim_gettlv(aim_tlvlist_t *list, const guint16 t, const int n) +{ +	aim_tlvlist_t *cur; +	int i; + +	for (cur = list, i = 0; cur; cur = cur->next) { +		if (cur && cur->tlv) { +			if (cur->tlv->type == t) +				i++; +			if (i >= n) +				return cur->tlv; +		} +	} + +	return NULL; +} + +/** + * aim_gettlv_str - Retrieve the Nth TLV in chain as a string. + * @list: Source TLV chain + * @type: TLV type to search for + * @nth: Index of TLV to return + * + * Same as aim_gettlv(), except that the return value is a %NULL- + * terminated string instead of an aim_tlv_t.  This is a  + * dynamic buffer and must be freed by the caller. + * + */ +char *aim_gettlv_str(aim_tlvlist_t *list, const guint16 t, const int n) +{ +	aim_tlv_t *tlv; +	char *newstr; + +	if (!(tlv = aim_gettlv(list, t, n))) +		return NULL; + +	newstr = (char *) g_malloc(tlv->length + 1); +	memcpy(newstr, tlv->value, tlv->length); +	*(newstr + tlv->length) = '\0'; + +	return newstr; +} + +/** + * aim_gettlv8 - Retrieve the Nth TLV in chain as a 8bit integer. + * @list: Source TLV chain + * @type: TLV type to search for + * @nth: Index of TLV to return + * + * Same as aim_gettlv(), except that the return value is a  + * 8bit integer instead of an aim_tlv_t.  + * + */ +guint8 aim_gettlv8(aim_tlvlist_t *list, const guint16 t, const int n) +{ +	aim_tlv_t *tlv; + +	if (!(tlv = aim_gettlv(list, t, n))) +		return 0; /* erm */ +	return aimutil_get8(tlv->value); +} + +/** + * aim_gettlv16 - Retrieve the Nth TLV in chain as a 16bit integer. + * @list: Source TLV chain + * @type: TLV type to search for + * @nth: Index of TLV to return + * + * Same as aim_gettlv(), except that the return value is a  + * 16bit integer instead of an aim_tlv_t.  + * + */ +guint16 aim_gettlv16(aim_tlvlist_t *list, const guint16 t, const int n) +{ +	aim_tlv_t *tlv; + +	if (!(tlv = aim_gettlv(list, t, n))) +		return 0; /* erm */ +	return aimutil_get16(tlv->value); +} + +/** + * aim_gettlv32 - Retrieve the Nth TLV in chain as a 32bit integer. + * @list: Source TLV chain + * @type: TLV type to search for + * @nth: Index of TLV to return + * + * Same as aim_gettlv(), except that the return value is a  + * 32bit integer instead of an aim_tlv_t.  + * + */ +guint32 aim_gettlv32(aim_tlvlist_t *list, const guint16 t, const int n) +{ +	aim_tlv_t *tlv; + +	if (!(tlv = aim_gettlv(list, t, n))) +		return 0; /* erm */ +	return aimutil_get32(tlv->value); +} + +#if 0 +/** + * aim_puttlv_8 - Write a one-byte TLV. + * @buf: Destination buffer + * @t: TLV type + * @v: Value + * + * Writes a TLV with a one-byte integer value portion. + * + */ +int aim_puttlv_8(guint8 *buf, const guint16 t, const guint8 v) +{ +	guint8 v8[1]; + +	aimutil_put8(v8, v); + +	return aim_puttlv_raw(buf, t, 1, v8); +} + +/** + * aim_puttlv_16 - Write a two-byte TLV. + * @buf: Destination buffer + * @t: TLV type + * @v: Value + * + * Writes a TLV with a two-byte integer value portion. + * + */ +int aim_puttlv_16(guint8 *buf, const guint16 t, const guint16 v) +{ +	guint8 v16[2]; + +	aimutil_put16(v16, v); + +	return aim_puttlv_raw(buf, t, 2, v16); +} + + +/** + * aim_puttlv_32 - Write a four-byte TLV. + * @buf: Destination buffer + * @t: TLV type + * @v: Value + * + * Writes a TLV with a four-byte integer value portion. + * + */ +int aim_puttlv_32(guint8 *buf, const guint16 t, const guint32 v) +{ +	guint8 v32[4]; + +	aimutil_put32(v32, v); + +	return aim_puttlv_raw(buf, t, 4, v32); +} + +/** + * aim_puttlv_raw - Write a raw TLV. + * @buf: Destination buffer + * @t: TLV type + * @l: Length of string + * @v: String to write + * + * Writes a TLV with a raw value portion.  (Only the first @l + * bytes of the passed buffer will be written, which should not + * include a terminating NULL.) + * + */ +int aim_puttlv_raw(guint8 *buf, const guint16 t, const guint16 l, const guint8 *v) +{ +	int i; + +	i = aimutil_put16(buf, t); +	i += aimutil_put16(buf+i, l); +	if (l) +		memcpy(buf+i, v, l); +	i += l; + +	return i; +} +#endif + diff --git a/protocols/oscar/txqueue.c b/protocols/oscar/txqueue.c new file mode 100644 index 00000000..d38986d0 --- /dev/null +++ b/protocols/oscar/txqueue.c @@ -0,0 +1,358 @@ +/* + *  aim_txqueue.c + * + * Herein lies all the mangement routines for the transmit (Tx) queue. + * + */ + +#include <aim.h> +#include "im.h" + +#ifndef _WIN32 +#include <sys/socket.h> +#endif + +/* + * Allocate a new tx frame. + * + * This is more for looks than anything else. + * + * Right now, that is.  If/when we implement a pool of transmit + * frames, this will become the request-an-unused-frame part. + * + * framing = AIM_FRAMETYPE_OFT/FLAP + * chan = channel for FLAP, hdrtype for OFT + * + */ +aim_frame_t *aim_tx_new(aim_session_t *sess, aim_conn_t *conn, guint8 framing, guint8 chan, int datalen) +{ +	aim_frame_t *fr; + +	if (!conn) { +		imcb_error(sess->aux_data, "no connection specified"); +		return NULL; +	} + +	if (!(fr = (aim_frame_t *)g_new0(aim_frame_t,1))) +		return NULL; + +	fr->conn = conn;  + +	fr->hdrtype = framing; + +	if (fr->hdrtype == AIM_FRAMETYPE_FLAP) { + +		fr->hdr.flap.type = chan; + +	} else  +		imcb_error(sess->aux_data, "unknown framing"); + +	if (datalen > 0) { +		guint8 *data; + +		if (!(data = (unsigned char *)g_malloc(datalen))) { +			aim_frame_destroy(fr); +			return NULL; +		} + +		aim_bstream_init(&fr->data, data, datalen); +	} + +	return fr; +} + +/* + * aim_tx_enqeue__queuebased() + * + * The overall purpose here is to enqueue the passed in command struct + * into the outgoing (tx) queue.  Basically... + *   1) Make a scope-irrelevent copy of the struct + *   3) Mark as not-sent-yet + *   4) Enqueue the struct into the list + *   6) Return + * + * Note that this is only used when doing queue-based transmitting; + * that is, when sess->tx_enqueue is set to &aim_tx_enqueue__queuebased. + * + */ +static int aim_tx_enqueue__queuebased(aim_session_t *sess, aim_frame_t *fr) +{ + +	if (!fr->conn) { +		imcb_error(sess->aux_data, "Warning: enqueueing packet with no connection"); +		fr->conn = aim_getconn_type(sess, AIM_CONN_TYPE_BOS); +	} + +	if (fr->hdrtype == AIM_FRAMETYPE_FLAP) { +		/* assign seqnum -- XXX should really not assign until hardxmit */ +		fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn); +	} + +	fr->handled = 0; /* not sent yet */ + +	/* see overhead note in aim_rxqueue counterpart */ +	if (!sess->queue_outgoing) +		sess->queue_outgoing = fr; +	else { +		aim_frame_t *cur; + +		for (cur = sess->queue_outgoing; cur->next; cur = cur->next) +			; +		cur->next = fr; +	} + +	return 0; +} + +/* + * aim_tx_enqueue__immediate() + * + * Parallel to aim_tx_enqueue__queuebased, however, this bypasses + * the whole queue mess when you want immediate writes to happen. + * + * Basically the same as its __queuebased couterpart, however + * instead of doing a list append, it just calls aim_tx_sendframe() + * right here.  + *  + */ +static int aim_tx_enqueue__immediate(aim_session_t *sess, aim_frame_t *fr) +{ + +	if (!fr->conn) { +		imcb_error(sess->aux_data, "packet has no connection"); +		aim_frame_destroy(fr); +		return 0; +	} + +	if (fr->hdrtype == AIM_FRAMETYPE_FLAP) +		fr->hdr.flap.seqnum = aim_get_next_txseqnum(fr->conn); + +	fr->handled = 0; /* not sent yet */ + +	aim_tx_sendframe(sess, fr); + +	aim_frame_destroy(fr); + +	return 0; +} + +int aim_tx_setenqueue(aim_session_t *sess, int what, int (*func)(aim_session_t *, aim_frame_t *)) +{ +	 +	if (what == AIM_TX_QUEUED) +		sess->tx_enqueue = &aim_tx_enqueue__queuebased; +	else if (what == AIM_TX_IMMEDIATE)  +		sess->tx_enqueue = &aim_tx_enqueue__immediate; +	else if (what == AIM_TX_USER) { +		if (!func) +			return -EINVAL; +		sess->tx_enqueue = func; +	} else +		return -EINVAL; /* unknown action */ + +	return 0; +} + +int aim_tx_enqueue(aim_session_t *sess, aim_frame_t *fr) +{ +	 +	/* +	 * If we want to send a connection thats inprogress, we have to force +	 * them to use the queue based version. Otherwise, use whatever they +	 * want. +	 */ +	if (fr && fr->conn &&  +			(fr->conn->status & AIM_CONN_STATUS_INPROGRESS)) { +		return aim_tx_enqueue__queuebased(sess, fr); +	} + +	return (*sess->tx_enqueue)(sess, fr); +} + +/*  + *  aim_get_next_txseqnum() + * + *   This increments the tx command count, and returns the seqnum + *   that should be stamped on the next FLAP packet sent.  This is + *   normally called during the final step of packet preparation + *   before enqueuement (in aim_tx_enqueue()). + * + */ +flap_seqnum_t aim_get_next_txseqnum(aim_conn_t *conn) +{ +	flap_seqnum_t ret; +	 +	ret = ++conn->seqnum; + +	return ret; +} + +static int aim_send(int fd, const void *buf, size_t count) +{ +	int left, cur; + +	for (cur = 0, left = count; left; ) { +		int ret; + +		ret = send(fd, ((unsigned char *)buf)+cur, left, 0); +		if (ret == -1) +			return -1; +		else if (ret == 0) +			return cur; + +		cur += ret; +		left -= ret; +	} + +	return cur; +} + +static int aim_bstream_send(aim_bstream_t *bs, aim_conn_t *conn, size_t count) +{ +	int wrote = 0; +	if (!bs || !conn || (count < 0)) +		return -EINVAL; + +	if (count > aim_bstream_empty(bs)) +		count = aim_bstream_empty(bs); /* truncate to remaining space */ + +	if (count) { +		if (count - wrote) { +			wrote = wrote + aim_send(conn->fd, bs->data + bs->offset + wrote, count - wrote); +		} +		 +	} +	 +	bs->offset += wrote; + +	return wrote;	 +} + +static int sendframe_flap(aim_session_t *sess, aim_frame_t *fr) +{ +	aim_bstream_t obs; +	guint8 *obs_raw; +	int payloadlen, err = 0, obslen; + +	payloadlen = aim_bstream_curpos(&fr->data); + +	if (!(obs_raw = g_malloc(6 + payloadlen))) +		return -ENOMEM; + +	aim_bstream_init(&obs, obs_raw, 6 + payloadlen); + +	/* FLAP header */ +	aimbs_put8(&obs, 0x2a); +	aimbs_put8(&obs, fr->hdr.flap.type); +	aimbs_put16(&obs, fr->hdr.flap.seqnum); +	aimbs_put16(&obs, payloadlen); + +	/* payload */ +	aim_bstream_rewind(&fr->data); +	aimbs_putbs(&obs, &fr->data, payloadlen); + +	obslen = aim_bstream_curpos(&obs); +	aim_bstream_rewind(&obs); +	if (aim_bstream_send(&obs, fr->conn, obslen) != obslen) +		err = -errno; +	 +	g_free(obs_raw); /* XXX aim_bstream_free */ + +	fr->handled = 1; +	fr->conn->lastactivity = time(NULL); + +	return err; +} + +int aim_tx_sendframe(aim_session_t *sess, aim_frame_t *fr) +{ +	if (fr->hdrtype == AIM_FRAMETYPE_FLAP) +		return sendframe_flap(sess, fr); +	return -1; +} + +int aim_tx_flushqueue(aim_session_t *sess) +{ +	aim_frame_t *cur; + +	for (cur = sess->queue_outgoing; cur; cur = cur->next) { + +		if (cur->handled) +	       		continue; /* already been sent */ + +		if (cur->conn && (cur->conn->status & AIM_CONN_STATUS_INPROGRESS)) +			continue; + +		/* +		 * And now for the meager attempt to force transmit +		 * latency and avoid missed messages. +		 */ +		if ((cur->conn->lastactivity + cur->conn->forcedlatency) >= time(NULL)) { +			/*  +			 * XXX should be a break! we dont want to block the  +			 * upper layers +			 * +			 * XXX or better, just do this right. +			 * +			 */ +			sleep((cur->conn->lastactivity + cur->conn->forcedlatency) - time(NULL)); +		} + +		/* XXX this should call the custom "queuing" function!! */ +		aim_tx_sendframe(sess, cur); +	} + +	/* purge sent commands from queue */ +	aim_tx_purgequeue(sess); + +	return 0; +} + +/* + *  aim_tx_purgequeue() + *   + *  This is responsable for removing sent commands from the transmit  + *  queue. This is not a required operation, but it of course helps + *  reduce memory footprint at run time!   + * + */ +void aim_tx_purgequeue(aim_session_t *sess) +{ +	aim_frame_t *cur, **prev; + +	for (prev = &sess->queue_outgoing; (cur = *prev); ) { + +		if (cur->handled) { +			*prev = cur->next; + +			aim_frame_destroy(cur); + +		} else +			prev = &cur->next; +	} + +	return; +} + +/** + * aim_tx_cleanqueue - get rid of packets waiting for tx on a dying conn + * @sess: session + * @conn: connection that's dying + * + * for now this simply marks all packets as sent and lets them + * disappear without warning. + * + */ +void aim_tx_cleanqueue(aim_session_t *sess, aim_conn_t *conn) +{ +	aim_frame_t *cur; + +	for (cur = sess->queue_outgoing; cur; cur = cur->next) { +		if (cur->conn == conn) +			cur->handled = 1; +	} + +	return; +} + +  | 
