aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/oscar
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/oscar')
-rw-r--r--protocols/oscar/AUTHORS31
-rw-r--r--protocols/oscar/COPYING504
-rw-r--r--protocols/oscar/Makefile37
-rw-r--r--protocols/oscar/admin.c196
-rw-r--r--protocols/oscar/admin.h13
-rw-r--r--protocols/oscar/aim.h921
-rw-r--r--protocols/oscar/aim_cbtypes.h0
-rw-r--r--protocols/oscar/aim_internal.h216
-rw-r--r--protocols/oscar/auth.c543
-rw-r--r--protocols/oscar/bos.c161
-rw-r--r--protocols/oscar/bos.h14
-rw-r--r--protocols/oscar/buddylist.c150
-rw-r--r--protocols/oscar/buddylist.h23
-rw-r--r--protocols/oscar/chat.c713
-rw-r--r--protocols/oscar/chat.h17
-rw-r--r--protocols/oscar/chatnav.c421
-rw-r--r--protocols/oscar/chatnav.h14
-rw-r--r--protocols/oscar/conn.c690
-rw-r--r--protocols/oscar/faimconfig.h0
-rw-r--r--protocols/oscar/ft.c2005
-rw-r--r--protocols/oscar/ft.h78
-rw-r--r--protocols/oscar/icq.c441
-rw-r--r--protocols/oscar/icq.h98
-rw-r--r--protocols/oscar/im.c2029
-rw-r--r--protocols/oscar/im.h198
-rw-r--r--protocols/oscar/info.c725
-rw-r--r--protocols/oscar/info.h44
-rw-r--r--protocols/oscar/misc.c396
-rw-r--r--protocols/oscar/msgcookie.c196
-rw-r--r--protocols/oscar/oscar.c2491
-rw-r--r--protocols/oscar/oscar_util.c167
-rw-r--r--protocols/oscar/rxhandlers.c408
-rw-r--r--protocols/oscar/rxqueue.c508
-rw-r--r--protocols/oscar/search.c121
-rw-r--r--protocols/oscar/search.h6
-rw-r--r--protocols/oscar/service.c946
-rw-r--r--protocols/oscar/snac.c145
-rw-r--r--protocols/oscar/ssi.c1523
-rw-r--r--protocols/oscar/ssi.h78
-rw-r--r--protocols/oscar/stats.c38
-rw-r--r--protocols/oscar/tlv.c600
-rw-r--r--protocols/oscar/txqueue.c440
42 files changed, 18345 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..acf4794a
--- /dev/null
+++ b/protocols/oscar/Makefile
@@ -0,0 +1,37 @@
+###########################
+## Makefile for BitlBee ##
+## ##
+## Copyright 2002 Lintux ##
+###########################
+
+### DEFINITIONS
+
+-include ../../Makefile.settings
+
+# [SH] Program variables
+objects = admin.o auth.o bos.o buddylist.o chat.o chatnav.o conn.o ft.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
+
+CFLAGS += -Wall
+LFLAGS += -r
+
+# [SH] Phony targets
+all: oscarr.o
+
+.PHONY: all clean distclean
+
+clean:
+ rm -f *.o core
+
+distclean: clean
+
+### MAIN PROGRAM
+
+$(objects): ../../Makefile.settings Makefile
+
+$(objects): %.o: %.c
+ @echo '*' Compiling $<
+ @$(CC) -c $(CFLAGS) $< -o $@
+
+oscarr.o: $(objects)
+ @echo '*' Linking oscarr.o
+ @$(LD) $(LFLAGS) $(objects) -o oscarr.o
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..2ba29c8b
--- /dev/null
+++ b/protocols/oscar/aim.h
@@ -0,0 +1,921 @@
+/*
+ * 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 "login.oscar.aol.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", \
+}
+
+/*
+ * 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_3_5_1670
+
+#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
+
+/* they start getting arbitrary in rendezvous stuff =) */
+#define AIM_CONN_TYPE_RENDEZVOUS 0x0101 /* these do not speak FLAP! */
+#define AIM_CONN_TYPE_RENDEZVOUS_OUT 0x0102 /* socket waiting for accept() */
+
+/*
+ * Subtypes, we need these for OFT stuff.
+ */
+#define AIM_CONN_SUBTYPE_OFT_DIRECTIM 0x0001
+#define AIM_CONN_SUBTYPE_OFT_GETFILE 0x0002
+#define AIM_CONN_SUBTYPE_OFT_SENDFILE 0x0003
+#define AIM_CONN_SUBTYPE_OFT_BUDDYICON 0x0004
+#define AIM_CONN_SUBTYPE_OFT_VOICE 0x0005
+
+/*
+ * 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
+#define AIM_FRAMETYPE_OFT 0x0001
+
+/*
+ * 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;
+ struct {
+ guint16 type;
+ guint8 magic[4]; /* ODC2 OFT2 */
+ guint16 hdr2len;
+ guint8 *hdr2; /* rest of bloated header */
+ } oft;
+ } 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_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;
+};
+
+
+#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
+/* we'll move OFT up a bit to give breathing room. not like it really
+ * matters. */
+#define AIM_COOKIETYPE_OFTIM 0x10
+#define AIM_COOKIETYPE_OFTGET 0x11
+#define AIM_COOKIETYPE_OFTSEND 0x12
+#define AIM_COOKIETYPE_OFTVOICE 0x13
+#define AIM_COOKIETYPE_OFTIMAGE 0x14
+#define AIM_COOKIETYPE_OFTICON 0x15
+
+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
+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_cbtypes.h b/protocols/oscar/aim_cbtypes.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/protocols/oscar/aim_cbtypes.h
diff --git a/protocols/oscar/aim_internal.h b/protocols/oscar/aim_internal.h
new file mode 100644
index 00000000..2e36c961
--- /dev/null
+++ b/protocols/oscar/aim_internal.h
@@ -0,0 +1,216 @@
+/*
+ * 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_newsnac(aim_session_t *, aim_snac_t *newsnac);
+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_msgcookie_gettype(int reqclass);
+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..c25a4604
--- /dev/null
+++ b/protocols/oscar/auth.c
@@ -0,0 +1,543 @@
+/*
+ * 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;
+
+ if (!sess || !conn || !sn)
+ return -EINVAL;
+
+ if ((sn[0] >= '0') && (sn[0] <= '9'))
+ 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[] = {
+#if 0 /* old v1 table */
+ 0xf3, 0xb3, 0x6c, 0x99,
+ 0x95, 0x3f, 0xac, 0xb6,
+ 0xc5, 0xfa, 0x6b, 0x63,
+ 0x69, 0x6c, 0xc3, 0x9f
+#else /* v2.1 table, also works for ICQ */
+ 0xf3, 0x26, 0x81, 0xc4,
+ 0x39, 0x86, 0xdb, 0x92,
+ 0x71, 0xa3, 0xb9, 0xe6,
+ 0x53, 0x7a, 0x95, 0x7c
+#endif
+ };
+ 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..60aabc79
--- /dev/null
+++ b/protocols/oscar/chat.c
@@ -0,0 +1,713 @@
+/*
+ * 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) {
+ do_error_dialog(sess->aux_data, "chat connection with no name!", "Gaim");
+ 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);
+
+ /*
+ * 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;
+}
+
+static 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;
+}
+
+/*
+ * 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) {
+ do_error_dialog(sess->aux_data, "Only detaillevel 0x2 is support at the moment", "Gaim");
+ 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) {
+ do_error_dialog(sess->aux_data, "unknown channel!", "Gaim");
+ 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..c7e11765
--- /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))) {
+ do_error_dialog(sess->aux_data, "no bigblock in top tlv in create room response", "Gaim");
+
+ 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) {
+ do_error_dialog(sess->aux_data, "unknown detaillevel in create room response", "Gaim");
+ 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))) {
+ do_error_dialog(sess->aux_data, "received response to unknown request!", "Gaim");
+ return 0;
+ }
+
+ if (snac2->family != 0x000d) {
+ do_error_dialog(sess->aux_data, "recieved response that maps to corrupt request!", "Gaim");
+ 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
+ do_error_dialog(sess->aux_data, "unknown request subtype", "Gaim");
+
+ 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..711e3458
--- /dev/null
+++ b/protocols/oscar/conn.c
@@ -0,0 +1,690 @@
+
+/*
+ * 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_RENDEZVOUS)
+ aim_conn_kill_rend(sess, *deadconn);
+ else 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);
+ if (deadconn->type == AIM_CONN_TYPE_RENDEZVOUS)
+ aim_conn_close_rend((aim_session_t *)deadconn->sessv, 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;
+ }
+
+#ifndef _WIN32
+ fcntl(conn->fd, F_SETFL, 0); /* XXX should restore original flags */
+#endif
+
+ 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/faimconfig.h b/protocols/oscar/faimconfig.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/protocols/oscar/faimconfig.h
diff --git a/protocols/oscar/ft.c b/protocols/oscar/ft.c
new file mode 100644
index 00000000..ddb64e7a
--- /dev/null
+++ b/protocols/oscar/ft.c
@@ -0,0 +1,2005 @@
+/*
+ * File transfer (OFT) and DirectIM (ODC).
+ * (OSCAR File Transfer, Oscar Direct Connect(ion?)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <aim.h>
+#include <glib.h>
+#include "ft.h"
+
+#ifndef _WIN32
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/utsname.h> /* for aim_directim_initiate */
+#include <arpa/inet.h> /* for inet_ntoa */
+#endif
+
+/* TODO:
+ o look for memory leaks.. there's going to be shitloads, i'm sure.
+*/
+
+struct aim_directim_intdata {
+ guint8 cookie[8];
+ char sn[MAXSNLEN+1];
+ char ip[22];
+};
+
+static int listenestablish(guint16 portnum);
+
+/**
+ * aim_handlerendconnect - call this to accept OFT connections and set up the required structures
+ * @sess: the session
+ * @cur: the conn the incoming connection is on
+ *
+ * call this when you get an outstanding read on a conn with subtype
+ * AIM_CONN_SUBTYPE_RENDEZVOUS_OUT, it will clone the current
+ * &aim_conn_t and tweak things as appropriate. the new conn and the
+ * listener conn are both returned to the client in the
+ * %AIM_CB_FAM_OFT, %AIM_CB_OFT_<CLASS>INITIATE callback.
+ */
+int aim_handlerendconnect(aim_session_t *sess, aim_conn_t *cur)
+{
+ int acceptfd = 0;
+ struct sockaddr cliaddr;
+ unsigned int clilen = sizeof(cliaddr);
+ int ret = 0;
+ aim_conn_t *newconn;
+
+ if ((acceptfd = accept(cur->fd, &cliaddr, &clilen)) == -1)
+ return 0; /* not an error */
+
+ if (cliaddr.sa_family != AF_INET) { /* just in case IPv6 really is happening */
+ closesocket(acceptfd);
+ aim_conn_close(cur);
+ return -1;
+ }
+
+ if (!(newconn = aim_cloneconn(sess, cur))) {
+ closesocket(acceptfd);
+ aim_conn_close(cur);
+ return -1;
+ }
+
+ newconn->type = AIM_CONN_TYPE_RENDEZVOUS;
+ newconn->fd = acceptfd;
+
+ if (newconn->subtype == AIM_CONN_SUBTYPE_OFT_DIRECTIM) {
+ struct aim_directim_intdata *priv;
+ aim_rxcallback_t userfunc;
+
+ priv = (struct aim_directim_intdata *)(newconn->internal = cur->internal);
+ cur->internal = NULL;
+
+ g_snprintf(priv->ip, sizeof(priv->ip), "%s:%u",
+ inet_ntoa(((struct sockaddr_in *)&cliaddr)->sin_addr),
+ ntohs(((struct sockaddr_in *)&cliaddr)->sin_port));
+
+ if ((userfunc = aim_callhandler(sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINITIATE)))
+ ret = userfunc(sess, NULL, newconn, cur);
+
+ } else if (newconn->subtype == AIM_CONN_SUBTYPE_OFT_GETFILE) {
+#if 0
+ struct aim_filetransfer_priv *priv;
+ aim_rxcallback_t userfunc;
+
+
+ newconn->priv = cur->priv;
+ cur->priv = NULL;
+ priv = (struct aim_filetransfer_priv *)newconn->priv;
+
+ g_snprintf(priv->ip, sizeof(priv->ip), "%s:%u", inet_ntoa(((struct sockaddr_in *)&cliaddr)->sin_addr), ntohs(((struct sockaddr_in *)&cliaddr)->sin_port));
+
+ if ((userfunc = aim_callhandler(sess, newconn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILEINITIATE)))
+ ret = userfunc(sess, NULL, newconn, cur);
+#endif
+ } else {
+ do_error_dialog(sess->aux_data, "Got a Connection on a listener that's not Rendezvous Closing conn.", "Gaim");
+ aim_conn_close(newconn);
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/**
+ * aim_send_typing - send client-to-client typing notification over established connection
+ * @sess: session to conn
+ * @conn: directim connection
+ * @typing: If true, notify user has started typing; if false, notify user has stopped.
+ *
+ * The connection must have been previously established.
+ */
+int aim_send_typing(aim_session_t *sess, aim_conn_t *conn, int typing)
+{
+
+struct aim_directim_intdata *intdata = (struct aim_directim_intdata *)conn->internal;
+ aim_frame_t *fr;
+ aim_bstream_t hdrbs; /* XXX this should be within aim_frame_t */
+
+ if (!sess || !conn || (conn->type != AIM_CONN_TYPE_RENDEZVOUS))
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x01, 0)))
+ return -ENOMEM;
+
+ memcpy(fr->hdr.oft.magic, "ODC2", 4);
+
+ fr->hdr.oft.hdr2len = 0x44;
+
+ if (!(fr->hdr.oft.hdr2 = g_malloc(fr->hdr.oft.hdr2len))) {
+ aim_frame_destroy(fr);
+ return -ENOMEM;
+ }
+ memset(fr->hdr.oft.hdr2, 0, fr->hdr.oft.hdr2len);
+
+ aim_bstream_init(&hdrbs, fr->hdr.oft.hdr2, fr->hdr.oft.hdr2len);
+
+ aimbs_put16(&hdrbs, 0x0006);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_putraw(&hdrbs, intdata->cookie, 8);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put32(&hdrbs, 0x00000000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+
+ /* flags -- 0x000e for "started typing", 0x0002 for "stopped typing */
+ aimbs_put16(&hdrbs, ( typing ? 0x000e : 0x0002));
+
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_putraw(&hdrbs, (guint8 *)sess->sn, strlen(sess->sn));
+
+ aim_bstream_setpos(&hdrbs, 52); /* bleeehh */
+
+ aimbs_put8(&hdrbs, 0x00);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+
+ /* end of hdr2 */
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+/**
+ * aim_send_im_direct - send IM client-to-client over established connection
+ * @sess: session to conn
+ * @conn: directim connection
+ * @msg: null-terminated string to send.
+ * len: The length of the message to send, including binary data.
+ *
+ * Call this just like you would aim_send_im, to send a directim. You
+ * _must_ have previously established the directim connection.
+ */
+int aim_send_im_direct(aim_session_t *sess, aim_conn_t *conn, const char *msg, int len)
+{
+ struct aim_directim_intdata *intdata = (struct aim_directim_intdata *)conn->internal;
+ aim_frame_t *fr;
+ aim_bstream_t hdrbs; /* XXX this should be within aim_frame_t */
+
+ if (!sess || !conn || !msg || (conn->type != AIM_CONN_TYPE_RENDEZVOUS))
+ return -EINVAL;
+
+ if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x01, len)))
+ return -ENOMEM;
+
+ memcpy(fr->hdr.oft.magic, "ODC2", 4);
+
+ fr->hdr.oft.hdr2len = 0x44;
+
+ if (!(fr->hdr.oft.hdr2 = g_malloc(fr->hdr.oft.hdr2len))) {
+ aim_frame_destroy(fr);
+ return -ENOMEM;
+ }
+ memset(fr->hdr.oft.hdr2, 0, fr->hdr.oft.hdr2len);
+
+ aim_bstream_init(&hdrbs, fr->hdr.oft.hdr2, fr->hdr.oft.hdr2len);
+
+ aimbs_put16(&hdrbs, 0x0006);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_putraw(&hdrbs, intdata->cookie, 8);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put32(&hdrbs, len);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+
+ /* flags -- 0x000e for "started typing", 0x0002 for "stopped typing, 0x0000 for message */
+ aimbs_put16(&hdrbs, 0x0000);
+
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_putraw(&hdrbs, (guint8 *)sess->sn, strlen(sess->sn));
+
+ aim_bstream_setpos(&hdrbs, 52); /* bleeehh */
+
+ aimbs_put8(&hdrbs, 0x00);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+ aimbs_put16(&hdrbs, 0x0000);
+
+ /* end of hdr2 */
+
+#if 0 /* XXX this is how you send buddy icon info... */
+ i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0008);
+ i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x000c);
+ i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0000);
+ i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x1466);
+ i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x0001);
+ i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x2e0f);
+ i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0x393e);
+ i += aimutil_put16(newpacket->hdr.oft.hdr2+i, 0xcac8);
+#endif
+ aimbs_putraw(&fr->data, (guint8 *)msg, len);
+
+ aim_tx_enqueue(sess, fr);
+
+ return 0;
+}
+
+static int getlocalip(guint8 *ip)
+{
+ struct hostent *hptr;
+ char localhost[129];
+
+ /* XXX if available, use getaddrinfo() */
+ /* XXX allow client to specify which IP to use for multihomed boxes */
+
+ if (gethostname(localhost, 128) < 0)
+ return -1;
+
+ if (!(hptr = gethostbyname(localhost)))
+ return -1;
+
+ memcpy(ip, hptr->h_addr_list[0], 4);
+
+ return 0;
+}
+
+/**
+ * aim_directim_intitiate - For those times when we want to open up the directim channel ourselves.
+ * @sess: your session,
+ * @conn: the BOS conn,
+ * @priv: a dummy priv value (we'll let it get filled in later) (if you pass a %NULL, we alloc one)
+ * @destsn: the SN to connect to.
+ *
+ */
+aim_conn_t *aim_directim_initiate(aim_session_t *sess, const char *destsn)
+{
+ aim_conn_t *newconn;
+ aim_msgcookie_t *cookie;
+ struct aim_directim_intdata *priv;
+ int listenfd;
+ guint16 port = 4443;
+ guint8 localip[4];
+ guint8 ck[8];
+
+ if (getlocalip(localip) == -1)
+ return NULL;
+
+ if ((listenfd = listenestablish(port)) == -1)
+ return NULL;
+
+ aim_request_directim(sess, destsn, localip, port, ck);
+
+ cookie = (aim_msgcookie_t *)g_new0(aim_msgcookie_t,1);
+ memcpy(cookie->cookie, ck, 8);
+ cookie->type = AIM_COOKIETYPE_OFTIM;
+
+ /* this one is for the cookie */
+ priv = (struct aim_directim_intdata *)g_new0(struct aim_directim_intdata,1);
+
+ memcpy(priv->cookie, ck, 8);
+ strncpy(priv->sn, destsn, sizeof(priv->sn));
+ cookie->data = priv;
+ aim_cachecookie(sess, cookie);
+
+ /* XXX switch to aim_cloneconn()? */
+ if (!(newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS_OUT, NULL))) {
+ closesocket(listenfd);
+ return NULL;
+ }
+
+ /* this one is for the conn */
+ priv = (struct aim_directim_intdata *)g_new0(struct aim_directim_intdata, 1);
+
+ memcpy(priv->cookie, ck, 8);
+ strncpy(priv->sn, destsn, sizeof(priv->sn));
+
+ newconn->fd = listenfd;
+ newconn->subtype = AIM_CONN_SUBTYPE_OFT_DIRECTIM;
+ newconn->internal = priv;
+ newconn->lastactivity = time(NULL);
+
+ return newconn;
+}
+
+/**
+ * aim_sendfile_intitiate - For those times when we want to send the file ourselves.
+ * @sess: your session,
+ * @conn: the BOS conn,
+ * @destsn: the SN to connect to.
+ * @filename: the name of the files you want to send
+ *
+ */
+aim_conn_t *aim_sendfile_initiate(aim_session_t *sess, const char *destsn, const char *filename, guint16 numfiles, guint32 totsize)
+{
+ aim_conn_t *newconn;
+ aim_msgcookie_t *cookie;
+ struct aim_directim_intdata *priv;
+ int listenfd;
+ guint16 port = 4443;
+ guint8 localip[4];
+ guint8 ck[8];
+
+ if (getlocalip(localip) == -1)
+ return NULL;
+
+ if ((listenfd = listenestablish(port)) == -1)
+ return NULL;
+
+ aim_request_sendfile(sess, destsn, filename, numfiles, totsize, localip, port, ck);
+
+ cookie = (aim_msgcookie_t *)g_new0(aim_msgcookie_t,1);
+ memcpy(cookie->cookie, ck, 8);
+ cookie->type = AIM_COOKIETYPE_OFTIM;
+
+ /* this one is for the cookie */
+ priv = (struct aim_directim_intdata *)g_new0(struct aim_directim_intdata,1);
+
+ memcpy(priv->cookie, ck, 8);
+ strncpy(priv->sn, destsn, sizeof(priv->sn));
+ cookie->data = priv;
+ aim_cachecookie(sess, cookie);
+
+ /* XXX switch to aim_cloneconn()? */
+ if (!(newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS_OUT, NULL))) {
+ closesocket(listenfd);
+ return NULL;
+ }
+
+ /* this one is for the conn */
+ priv = (struct aim_directim_intdata *)g_new0(struct aim_directim_intdata,1);
+
+ memcpy(priv->cookie, ck, 8);
+ strncpy(priv->sn, destsn, sizeof(priv->sn));
+
+ newconn->fd = listenfd;
+ newconn->subtype = AIM_CONN_SUBTYPE_OFT_SENDFILE;
+ newconn->internal = priv;
+ newconn->lastactivity = time(NULL);
+
+ return newconn;
+}
+
+#if 0
+/**
+ * unsigned int aim_oft_listener_clean - close up old listeners
+ * @sess: session to clean up in
+ * @age: maximum age in seconds
+ *
+ * returns number closed, -1 on error.
+ */
+unsigned int aim_oft_listener_clean(struct aim_session_t *sess, time_t age)
+{
+ struct aim_conn_t *cur;
+ time_t now;
+ unsigned int hit = 0;
+
+ if (!sess)
+ return -1;
+ now = time(NULL);
+ faim_mutex_lock(&sess->connlistlock);
+ for(cur = sess->connlist;cur; cur = cur->next)
+ if (cur->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) {
+ faim_mutex_lock(&cur->active);
+ if (cur->lastactivity < (now - age) ) {
+ faim_mutex_unlock(&cur->active);
+ aim_conn_close(cur);
+ hit++;
+ } else
+ faim_mutex_unlock(&cur->active);
+ }
+ faim_mutex_unlock(&sess->connlistlock);
+ return hit;
+}
+#endif
+
+const char *aim_directim_getsn(aim_conn_t *conn)
+{
+ struct aim_directim_intdata *intdata;
+
+ if (!conn)
+ return NULL;
+
+ if ((conn->type != AIM_CONN_TYPE_RENDEZVOUS) ||
+ (conn->subtype != AIM_CONN_SUBTYPE_OFT_DIRECTIM))
+ return NULL;
+
+ if (!conn->internal)
+ return NULL;
+
+ intdata = (struct aim_directim_intdata *)conn->internal;
+
+ return intdata->sn;
+}
+
+/**
+ * aim_directim_connect - connect to buddy for directim
+ * @sess: the session to append the conn to,
+ * @sn: the SN we're connecting to
+ * @addr: address to connect to
+ *
+ * This is a wrapper for aim_newconn.
+ *
+ * If addr is NULL, the socket is not created, but the connection is
+ * allocated and setup to connect.
+ *
+ */
+aim_conn_t *aim_directim_connect(aim_session_t *sess, const char *sn, const char *addr, const guint8 *cookie)
+{
+ aim_conn_t *newconn;
+ struct aim_directim_intdata *intdata;
+
+ if (!sess || !sn)
+ return NULL;
+
+ if (!(intdata = g_new0(struct aim_directim_intdata, 1)))
+ return NULL;
+
+ memcpy(intdata->cookie, cookie, 8);
+ strncpy(intdata->sn, sn, sizeof(intdata->sn));
+ if (addr)
+ strncpy(intdata->ip, addr, sizeof(intdata->ip));
+
+ /* XXX verify that non-blocking connects actually work */
+ if (!(newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS, addr))) {
+ g_free(intdata);
+ return NULL;
+ }
+
+ if (!newconn) {
+ g_free(intdata);
+ return newconn;
+ }
+
+ newconn->subtype = AIM_CONN_SUBTYPE_OFT_DIRECTIM;
+ newconn->internal = intdata;
+
+ return newconn;
+}
+
+/**
+ * aim_directim_getconn - find a directim conn for buddy name
+ * @sess: your session,
+ * @name: the name to get,
+ *
+ * returns conn for directim with name, %NULL if none found.
+ *
+ */
+aim_conn_t *aim_directim_getconn(aim_session_t *sess, const char *name)
+{
+ aim_conn_t *cur;
+
+ if (!sess || !name || !strlen(name))
+ return NULL;
+
+ for (cur = sess->connlist; cur; cur = cur->next) {
+ struct aim_directim_intdata *intdata;
+
+ if ((cur->type != AIM_CONN_TYPE_RENDEZVOUS) || (cur->subtype != AIM_CONN_SUBTYPE_OFT_DIRECTIM))
+ continue;
+
+ intdata = cur->internal;
+
+ if (aim_sncmp(intdata->sn, name) == 0)
+ break;
+ }
+
+ return cur;
+}
+
+/**
+ * aim_accepttransfer - accept a file transfer request
+ * @sess: the session,
+ * @conn: the BOS conn for the CAP reply
+ * @sn: the screenname to send it to,
+ * @cookie: the cookie used
+ * @ip: the ip to connect to
+ * @listingfiles: number of files to share
+ * @listingtotsize: total size of shared files
+ * @listingsize: length of the listing file(buffer)
+ * @listingchecksum: checksum of the listing
+ * @rendid: capability type (%AIM_CAPS_GETFILE or %AIM_CAPS_SENDFILE)
+ *
+ * Returns new connection or %NULL on error.
+ *
+ * XXX this should take a struct.
+ */
+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)
+{
+ return NULL;
+#if 0
+ struct command_tx_struct *newpacket, *newoft;
+ struct aim_conn_t *newconn;
+ struct aim_fileheader_t *fh;
+ struct aim_filetransfer_priv *priv;
+ struct aim_msgcookie_t *cachedcook;
+ int curbyte, i;
+
+ if (!sess || !conn || !sn || !cookie || !ip) {
+ return NULL;
+ }
+
+ newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS, ip);
+
+ if (!newconn || (newconn->fd == -1)) {
+ perror("aim_newconn");
+ faimdprintf(sess, 2, "could not connect to %s (fd: %i)\n", ip, newconn?newconn->fd:0);
+ return newconn;
+ } else {
+ priv = (struct aim_filetransfer_priv *)g_new0(struct aim_filetransfer_priv,1);
+
+ memcpy(priv->cookie, cookie, 8);
+ priv->state = 0;
+ strncpy(priv->sn, sn, MAXSNLEN);
+ strncpy(priv->ip, ip, sizeof(priv->ip));
+ newconn->priv = (void *)priv;
+
+ faimdprintf(sess, 2, "faim: connected to peer (fd = %d)\n", newconn->fd);
+ }
+
+ if (rendid == AIM_CAPS_GETFILE) {
+ newconn->subtype = AIM_CONN_SUBTYPE_OFT_GETFILE;
+
+ faimdprintf(sess, 2, "faim: getfile request accept\n");
+
+ if (!(newoft = aim_tx_new(sess, newconn, AIM_FRAMETYPE_OFT, 0x1108, 0))) {
+ faimdprintf(sess, 2, "faim: aim_accepttransfer: tx_new OFT failed\n");
+ /* XXX: conn leak here */
+ return NULL;
+ }
+
+ newoft->lock = 1;
+ memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+ newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+ if (!(fh = (struct aim_fileheader_t*)g_new0(struct aim_fileheader_t,1))) {
+ /* XXX: conn leak here */
+ perror("calloc");
+ return NULL;
+ }
+
+ fh->encrypt = 0x0000;
+ fh->compress = 0x0000;
+ fh->totfiles = listingfiles;
+ fh->filesleft = listingfiles; /* is this right -- total parts and parts left?*/
+ fh->totparts = 0x0001;
+ fh->partsleft = 0x0001;
+ fh->totsize = listingtotsize;
+ fh->size = listingsize; /* ls -l listing.txt */
+ fh->modtime = (int)time(NULL); /* we'll go with current time for now */
+ fh->checksum = listingchecksum;
+ fh->rfcsum = 0x00000000;
+ fh->rfsize = 0x00000000;
+ fh->cretime = 0x00000000;
+ fh->rfcsum = 0x00000000;
+ fh->nrecvd = 0x00000000;
+ fh->recvcsum = 0x00000000;
+ memset(fh->idstring, 0, sizeof(fh->idstring));
+ memcpy(fh->idstring, "OFT_Windows ICBMFT V1.1 32", sizeof(fh->idstring));
+ fh->flags = 0x02;
+ fh->lnameoffset = 0x1a;
+ fh->lsizeoffset = 0x10;
+ memset(fh->dummy, 0, sizeof(fh->dummy));
+ memset(fh->macfileinfo, 0, sizeof(fh->macfileinfo));
+
+ /* we need to figure out these encodings for filenames */
+ fh->nencode = 0x0000;
+ fh->nlanguage = 0x0000;
+ memset(fh->name, 0, sizeof(fh->name));
+ memcpy(fh->name, "listing.txt", sizeof(fh->name));
+
+ if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+ newoft->lock = 0;
+ aim_frame_destroy(newoft);
+ /* XXX: conn leak */
+ perror("calloc (1)");
+ return NULL;
+ }
+
+ memcpy(fh->bcookie, cookie, 8);
+
+ if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, fh)))
+ faimdprintf(sess, 1, "eek, bh fail!\n");
+
+ newoft->lock = 0;
+ aim_tx_enqueue(sess, newoft);
+
+ if (!(cachedcook = (struct aim_msgcookie_t *)calloc(1, sizeof(struct aim_msgcookie_t)))) {
+ faimdprintf(sess, 1, "faim: accepttransfer: couldn't calloc cachedcook. yeep!\n");
+ /* XXX: more cleanup, conn leak */
+ perror("calloc (2)");
+ return NULL;
+ }
+
+ memcpy(&(priv->fh), fh, sizeof(struct aim_fileheader_t));
+ memcpy(cachedcook->cookie, cookie, 8);
+
+ cachedcook->type = AIM_COOKIETYPE_OFTGET;
+ cachedcook->data = (void *)priv;
+
+ if (aim_cachecookie(sess, cachedcook) == -1)
+ faimdprintf(sess, 1, "faim: ERROR caching message cookie\n");
+
+ g_free(fh);
+
+ /* OSCAR CAP accept packet */
+
+ if (!(newpacket = aim_tx_new(sess, conn, AIM_FRAMETYPE_OSCAR, 0x0002, 10+8+2+1+strlen(sn)+4+2+8+16))) {
+ return NULL;
+ }
+ } else {
+ return NULL;
+ }
+
+ newpacket->lock = 1;
+ curbyte = aim_putsnac(newpacket->data, 0x0004, 0x0006, 0x0000, sess->snac_nextid);
+
+ for (i = 0; i < 8; i++)
+ curbyte += aimutil_put8(newpacket->data+curbyte, cookie[i]);
+
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0002);
+ curbyte += aimutil_put8(newpacket->data+curbyte, strlen(sn));
+ curbyte += aimutil_putstr(newpacket->data+curbyte, sn, strlen(sn));
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0005);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x001a);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0002 /* accept*/);
+
+ for (i = 0;i < 8; i++)
+ curbyte += aimutil_put8(newpacket->data+curbyte, cookie[i]);
+
+ curbyte += aim_putcap(newpacket->data+curbyte, 0x10, rendid);
+ newpacket->lock = 0;
+ aim_tx_enqueue(sess, newpacket);
+
+ return newconn;
+#endif
+}
+
+/**
+ * aim_getlisting(FILE *file) -- get an aim_fileheader_t for a given FILE*
+ * @file is an opened listing file
+ *
+ * returns a pointer to the filled-in fileheader_t
+ *
+ * Currently omits checksum. we'll fix this when AOL breaks us, i
+ * guess.
+ *
+ */
+struct aim_fileheader_t *aim_getlisting(aim_session_t *sess, FILE *file)
+{
+ return NULL;
+#if 0
+ struct aim_fileheader_t *fh;
+ u_long totsize = 0, size = 0, checksum = 0xffff0000;
+ short totfiles = 0;
+ char *linebuf, sizebuf[9];
+
+ int linelength = 1024;
+
+ /* XXX: if we have a line longer than 1024chars, God help us. */
+ if ( (linebuf = (char *)calloc(1, linelength)) == NULL ) {
+ faimdprintf(sess, 2, "linebuf calloc failed\n");
+ return NULL;
+ }
+
+ if (fseek(file, 0, SEEK_END) == -1) { /* use this for sanity check */
+ perror("getlisting END1 fseek:");
+ faimdprintf(sess, 2, "getlising fseek END1 error\n");
+ }
+
+ if ((size = ftell(file)) == -1) {
+ perror("getlisting END1 getpos:");
+ faimdprintf(sess, 2, "getlising getpos END1 error\n");
+ }
+
+ if (fseek(file, 0, SEEK_SET) != 0) {
+ perror("getlesting fseek(SET):");
+ faimdprintf(sess, 2, "faim: getlisting: couldn't seek to beginning of listing file\n");
+ }
+
+ memset(linebuf, 0, linelength);
+
+ size = 0;
+
+ while(fgets(linebuf, linelength, file)) {
+ totfiles++;
+ memset(sizebuf, 0, 9);
+
+ size += strlen(linebuf);
+
+ if (strlen(linebuf) < 23) {
+ faimdprintf(sess, 2, "line \"%s\" too short. skipping\n", linebuf);
+ continue;
+ }
+ if (linebuf[strlen(linebuf)-1] != '\n') {
+ faimdprintf(sess, 2, "faim: OFT: getlisting -- hit EOF or line too long!\n");
+ }
+
+ memcpy(sizebuf, linebuf+17, 8);
+
+ totsize += strtol(sizebuf, NULL, 10);
+ memset(linebuf, 0, linelength);
+ }
+
+ if (fseek(file, 0, SEEK_SET) == -1) {
+ perror("getlisting END2 fseek:");
+ faimdprintf(sess, 2, "getlising fseek END2 error\n");
+ }
+
+ g_free(linebuf);
+
+ /* we're going to ignore checksumming the data for now -- that
+ * requires walking the whole listing.txt. it should probably be
+ * done at register time and cached, but, eh. */
+
+ if (!(fh = (struct aim_fileheader_t*)calloc(1, sizeof(struct aim_fileheader_t))))
+ return NULL;
+
+ fh->encrypt = 0x0000;
+ fh->compress = 0x0000;
+ fh->totfiles = totfiles;
+ fh->filesleft = totfiles; /* is this right ?*/
+ fh->totparts = 0x0001;
+ fh->partsleft = 0x0001;
+ fh->totsize = totsize;
+ fh->size = size; /* ls -l listing.txt */
+ fh->modtime = (int)time(NULL); /* we'll go with current time for now */
+ fh->checksum = checksum; /* XXX: checksum ! */
+ fh->rfcsum = 0x00000000;
+ fh->rfsize = 0x00000000;
+ fh->cretime = 0x00000000;
+ fh->rfcsum = 0x00000000;
+ fh->nrecvd = 0x00000000;
+ fh->recvcsum = 0x00000000;
+
+ /* memset(fh->idstring, 0, sizeof(fh->idstring)); */
+ memcpy(fh->idstring, "OFT_Windows ICBMFT V1.1 32", sizeof(fh->idstring));
+ memset(fh->idstring+strlen(fh->idstring), 0, sizeof(fh->idstring)-strlen(fh->idstring));
+
+ fh->flags = 0x02;
+ fh->lnameoffset = 0x1a;
+ fh->lsizeoffset = 0x10;
+
+ /* memset(fh->dummy, 0, sizeof(fh->dummy)); */
+ memset(fh->macfileinfo, 0, sizeof(fh->macfileinfo));
+
+ fh->nencode = 0x0000; /* we need to figure out these encodings for filenames */
+ fh->nlanguage = 0x0000;
+
+ /* memset(fh->name, 0, sizeof(fh->name)); */
+ memcpy(fh->name, "listing.txt", sizeof(fh->name));
+ memset(fh->name+strlen(fh->name), 0, 64-strlen(fh->name));
+
+ faimdprintf(sess, 2, "faim: OFT: listing fh name %s / %s\n", fh->name, (fh->name+(strlen(fh->name))));
+ return fh;
+#endif
+}
+
+/**
+ * aim_listenestablish - create a listening socket on a port.
+ * @portnum: the port number to bind to.
+ *
+ * you need to call accept() when it's connected. returns your fd
+ *
+ * XXX: give the client author the responsibility of setting up a
+ * listener, then we no longer have a libfaim problem with broken
+ * solaris *innocent smile* -jbm
+ */
+static int listenestablish(guint16 portnum)
+{
+#if HAVE_GETADDRINFO
+ int listenfd;
+ const int on = 1;
+ struct addrinfo hints, *res, *ressave;
+ char serv[5];
+
+ g_snprintf(serv, sizeof(serv), "%d", portnum);
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ if (getaddrinfo(NULL /*any IP*/, serv, &hints, &res) != 0) {
+ perror("getaddrinfo");
+ return -1;
+ }
+ ressave = res;
+ do {
+ listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (listenfd < 0)
+ continue;
+ setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+ if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
+ break;
+ /* success */
+ closesocket(listenfd);
+ } while ( (res = res->ai_next) );
+
+ if (!res)
+ return -1;
+
+ if (listen(listenfd, 1024)!=0) {
+ perror("listen");
+ return -1;
+ }
+
+ freeaddrinfo(ressave);
+ return listenfd;
+#else
+ int listenfd;
+ const int on = 1;
+ struct sockaddr_in sockin;
+
+ if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket(listenfd)");
+ return -1;
+ }
+
+ if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) != 0) {
+ perror("setsockopt(listenfd)");
+ closesocket(listenfd);
+ return -1;
+ }
+
+ memset(&sockin, 0, sizeof(struct sockaddr_in));
+ sockin.sin_family = AF_INET;
+ sockin.sin_port = htons(portnum);
+
+ if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) {
+ perror("bind(listenfd)");
+ closesocket(listenfd);
+ return -1;
+ }
+ if (listen(listenfd, 4) != 0) {
+ perror("listen(listenfd)");
+ closesocket(listenfd);
+ return -1;
+ }
+ return listenfd;
+#endif
+}
+
+static int getcommand_getfile(aim_session_t *sess, aim_conn_t *conn)
+{
+#if 0
+ struct aim_filetransfer_priv *ft;
+ aim_rxcallback_t userfunc;
+
+ ft = conn->priv;
+ if (ft->state == 2) {
+ /* waiting on listing data */
+ int ret = 0;
+ char *listing;
+ struct command_tx_struct *newoft;
+
+ if (!(listing = g_malloc(ft->fh.size)))
+ return -1;
+
+ ft->state = 0;
+ if (aim_recv(conn->fd, listing, ft->fh.size) != ft->fh.size)
+ faimdprintf(sess, 2, "OFT get: file %s was short. (0x%lx)\n", ft->fh.name, ft->fh.size);
+
+ if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x120b, 0))) {
+ faimdprintf(sess, 2, "faim: aim_get_command_rendezvous: getfile listing: tx_new OFT failed\n");
+ faim_mutex_unlock(&conn->active);
+ g_free(listing);
+ aim_conn_close(conn);
+ return -1;
+ }
+
+ memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+ newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+ /* Protocol BS - set nrecvd to size of listing, recvcsum to listing checksum, flags to 0 */
+
+ ft->fh.nrecvd = ft->fh.size;
+ ft->fh.recvcsum = ft->fh.checksum;
+ ft->fh.flags = 0;
+
+ if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+ aim_frame_destroy(newoft);
+ g_free(listing);
+ return -1;
+ }
+
+ if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh))))
+ faimdprintf(sess, 2, "eek! bh fail listing\n");
+
+ /* send the 120b */
+ aim_tx_enqueue(sess, newoft);
+ if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILELISTING)) )
+ ret = userfunc(sess, NULL, conn, ft, listing);
+
+ g_free(listing);
+ return ret;
+ }
+
+ if (ft->state == 3) {
+ /* waiting on file data */
+ if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILERECEIVE)) )
+ return userfunc(sess, NULL, conn, ft);
+ return 0;
+ }
+
+ if (ft->state == 4) {
+ if( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILESTATE4)) )
+ return userfunc(sess, NULL, conn);
+ aim_conn_close(conn);
+ return 0;
+ }
+
+ return 0;
+#else
+ return -1;
+#endif
+}
+
+static void connclose_sendfile(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_msgcookie_t *cook;
+ struct aim_filetransfer_priv *priv = (struct aim_filetransfer_priv *)conn->priv;
+
+ cook = aim_uncachecookie(sess, (guint8 *)priv->cookie, AIM_COOKIETYPE_OFTSEND);
+ aim_cookie_free(sess, cook);
+
+ return;
+}
+
+static void connkill_sendfile(aim_session_t *sess, aim_conn_t *conn)
+{
+
+ g_free(conn->internal);
+
+ return;
+}
+
+static void connclose_getfile(aim_session_t *sess, aim_conn_t *conn)
+{
+ aim_msgcookie_t *cook;
+ struct aim_filetransfer_priv *priv = (struct aim_filetransfer_priv *)conn->priv;
+
+ cook = aim_uncachecookie(sess, (guint8 *)priv->cookie, AIM_COOKIETYPE_OFTGET);
+ aim_cookie_free(sess, cook);
+
+ return;
+}
+
+static void connkill_getfile(aim_session_t *sess, aim_conn_t *conn)
+{
+
+ g_free(conn->internal);
+
+ return;
+}
+
+static void connclose_directim(aim_session_t *sess, aim_conn_t *conn)
+{
+ struct aim_directim_intdata *intdata = (struct aim_directim_intdata *)conn->internal;
+ aim_msgcookie_t *cook;
+
+ cook = aim_uncachecookie(sess, intdata->cookie, AIM_COOKIETYPE_OFTIM);
+ aim_cookie_free(sess, cook);
+
+ return;
+}
+
+static void connkill_directim(aim_session_t *sess, aim_conn_t *conn)
+{
+
+ g_free(conn->internal);
+
+ return;
+}
+
+void aim_conn_close_rend(aim_session_t *sess, aim_conn_t *conn)
+{
+
+ if (conn->type != AIM_CONN_TYPE_RENDEZVOUS)
+ return;
+
+ if (conn->subtype == AIM_CONN_SUBTYPE_OFT_SENDFILE)
+ connclose_sendfile(sess, conn);
+ else if (conn->subtype == AIM_CONN_SUBTYPE_OFT_GETFILE)
+ connclose_getfile(sess, conn);
+ else if (conn->subtype == AIM_CONN_SUBTYPE_OFT_DIRECTIM)
+ connclose_directim(sess, conn);
+
+ return;
+}
+
+void aim_conn_kill_rend(aim_session_t *sess, aim_conn_t *conn)
+{
+
+ if (conn->type != AIM_CONN_TYPE_RENDEZVOUS)
+ return;
+
+ if (conn->subtype == AIM_CONN_SUBTYPE_OFT_SENDFILE)
+ connkill_sendfile(sess, conn);
+ else if (conn->subtype == AIM_CONN_SUBTYPE_OFT_GETFILE)
+ connkill_getfile(sess, conn);
+ else if (conn->subtype == AIM_CONN_SUBTYPE_OFT_DIRECTIM)
+ connkill_directim(sess, conn);
+
+ return;
+}
+
+static int handlehdr_directim(aim_session_t *sess, aim_conn_t *conn, guint8 *hdr)
+{
+ aim_frame_t fr;
+ aim_rxcallback_t userfunc;
+ guint32 payloadlength;
+ guint16 flags;
+ char *snptr = NULL;
+
+ fr.conn = conn;
+
+ payloadlength = aimutil_get32(hdr+22);
+ flags = aimutil_get16(hdr+32);
+ snptr = (char *)hdr+38;
+
+ if (flags == 0x000e) {
+ int ret = 0;
+
+ if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMTYPING)))
+ ret = userfunc(sess, &fr, snptr, 1);
+
+ return ret;
+
+ } else if (flags == 0x0002) {
+ int ret = 0;
+
+ if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMTYPING)))
+ ret = userfunc(sess, &fr, snptr, 0);
+
+ return ret;
+
+ } else if ((flags == 0x0000) && payloadlength) {
+ char *msg, *msg2;
+ int ret = 0;
+ int recvd = 0;
+ int i;
+
+ if (!(msg = g_malloc(payloadlength+1)))
+ return -1;
+ memset(msg, 0, payloadlength+1);
+ msg2 = msg;
+
+ while (payloadlength - recvd) {
+ if (payloadlength - recvd >= 1024)
+ i = aim_recv(conn->fd, msg2, 1024);
+ else
+ i = aim_recv(conn->fd, msg2, payloadlength - recvd);
+ if (i <= 0) {
+ g_free(msg);
+ return -1;
+ }
+ recvd = recvd + i;
+ msg2 = msg2 + i;
+ if ((userfunc=aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_IMAGETRANSFER)))
+ userfunc(sess, &fr, snptr, (double)recvd / payloadlength);
+ }
+
+ if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIMINCOMING)) )
+ ret = userfunc(sess, &fr, snptr, msg, payloadlength);
+
+ g_free(msg);
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static int handlehdr_getfile_listing(aim_session_t *sess, aim_conn_t *conn, guint8 *hdr)
+{
+#if 0
+ struct aim_filetransfer_priv *ft;
+ struct aim_fileheader_t *fh;
+ struct aim_msgcookie_t *cook;
+ struct command_tx_struct *newoft;
+ aim_rxcallback_t userfunc;
+
+ faimdprintf(sess, 2,"faim: rend: fileget 0x1108\n");
+ fh = aim_oft_getfh(hdr);
+
+ faim_mutex_unlock(&conn->active);
+
+ if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+ g_free(fh);
+ return -1;
+ }
+
+ ft = cook->data;
+
+ /* we're waaaaiiiting.. for listing.txt */
+ ft->state = 2;
+
+ memcpy(&(ft->fh), fh, sizeof(struct aim_fileheader_t));
+ g_free(fh);
+
+ if(aim_cachecookie(sess, cook) == -1) {
+ faimdprintf(sess, 1, "error caching cookie\n");
+ return -1;
+ }
+
+ if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x1209, 0))) {
+ aim_conn_close(conn);
+ return -1;
+ }
+
+ memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+ newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+ if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+ newoft->lock = 0;
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh)))) {
+ newoft->lock = 0;
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ newoft->lock = 0;
+ aim_tx_enqueue(sess, newoft);
+#endif
+ return -1;
+}
+
+static int handlehdr_getfile_listing2(aim_session_t *sess, aim_conn_t *conn, guint8 *hdr)
+{
+#if 0
+ struct aim_filetransfer_priv *ft;
+ struct aim_fileheader_t *fh;
+ struct aim_msgcookie_t *cook;
+ int ret = 0;
+ aim_rxcallback_t userfunc;
+
+ fh = aim_oft_getfh(hdr);
+
+ if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET)))
+ faimdprintf(sess, 2, "shit, no cookie in 0x1209. (%i/%s)going to crash..\n", AIM_COOKIETYPE_OFTGET, fh->bcookie);
+
+ ft = cook->data;
+
+ if (ft->fh.size != fh->size)
+ faimdprintf(sess, 2, "hrm. ft->fh.size (%ld) != fh->size (%ld). um. using ft->fh.size\n", ft->fh.size, fh->size);
+
+ if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILELISTINGREQ)))
+ ret = userfunc(sess, NULL, conn, fh);
+
+ faimdprintf(sess, 2, "faim: get_command_rendezvous: hit end of 1209\n");
+
+ g_free(fh);
+
+ return ret;
+#else
+ return -1;
+#endif
+}
+
+static int handlehdr_getfile_listing3(aim_session_t *sess, aim_conn_t *conn, guint8 *hdr)
+{
+#if 0
+ struct aim_filetransfer_priv *ft;
+ struct aim_msgcookie_t *cook;
+ struct aim_fileheader_t *fh;
+ aim_rxcallback_t userfunc;
+
+ fh = aim_oft_getfh(hdr);
+
+ if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+ g_free(fh);
+ return -1;
+ }
+
+ g_free(fh);
+
+ ft = cook->data;
+
+ if (aim_cachecookie(sess, cook) == -1)
+ return -1;
+
+ if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILELISTINGRXCONFIRM)))
+ return userfunc(sess, NULL, conn);
+#endif
+ return -1;
+}
+
+static int handlehdr_getfile_request(aim_session_t *sess, aim_conn_t *conn, guint8 *hdr)
+{
+#if 0
+ struct aim_filetransfer_priv *ft;
+ struct aim_msgcookie_t *cook;
+ struct aim_fileheader_t *fh;
+ struct command_tx_struct *newoft;
+ int i = 0;
+ aim_rxcallback_t userfunc;
+
+ fh = aim_oft_getfh(hdr);
+
+ if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+ g_free(fh);
+ return -1;
+ }
+
+ ft = cook->data;
+ memcpy(&(ft->fh), fh, sizeof(struct aim_fileheader_t));
+ g_free(fh);
+
+ aim_cachecookie(sess, cook);
+
+ faimdprintf(sess, 2, "faim: fileget: %s seems to want %s\n", ft->sn, ft->fh.name);
+
+ if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILEFILEREQ)) )
+ i = userfunc(sess, NULL, conn, &(ft->fh), cook->cookie);
+
+ if (i < 0)
+ return i;
+
+ if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x0101, 0))) {
+ faimdprintf(sess, 2, "faim: send_final_transfer: tx_new OFT failed\n");
+ return -1;
+ }
+
+ newoft->lock = 1;
+ memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+ newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+ if (!(newoft->hdr.oft.hdr2 = calloc(1,newoft->hdr.oft.hdr2len))) {
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ /* protocol BS: nrecvd, recvcsum to 0, flags to 0x20. */
+ ft->fh.nrecvd = 0;
+ ft->fh.recvcsum = 0;
+ ft->fh.flags = 0x20;
+
+ aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh));
+
+ newoft->lock = 0;
+ aim_tx_enqueue(sess, newoft);
+
+ faimdprintf(sess, 2, "faim: OFT: OFT file header enqueued.\n");
+
+ return i;
+#else
+ return -1;
+#endif
+}
+
+static int handlehdr_getfile_sending(aim_session_t *sess, aim_conn_t *conn, guint8 *hdr)
+{
+#if 0
+ struct aim_fileheader_t *fh;
+ struct aim_filetransfer_priv *ft;
+ struct aim_msgcookie_t *cook;
+ struct command_tx_struct *newoft;
+ aim_rxcallback_t userfunc;
+
+ fh = aim_oft_getfh(hdr);
+
+ if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+ g_free(fh);
+ return -1;
+ }
+
+ g_free(fh);
+
+ ft = cook->data;
+
+ ft->state = 3;
+
+ if (aim_cachecookie(sess, cook) == -1)
+ return -1;
+
+ faimdprintf(sess, 2, "faim: fileget: %s seems to want to send %s\n", ft->sn, ft->fh.name);
+
+ if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x0202, 0))) {
+ faimdprintf(sess, 2, "faim: send_final_transfer: tx_new OFT failed\n");
+ return -1;
+ }
+
+ newoft->lock = 1;
+ memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+
+ newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+ if (!(newoft->hdr.oft.hdr2 = calloc(1,newoft->hdr.oft.hdr2len))) {
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh));
+
+ newoft->lock = 0;
+ aim_tx_enqueue(sess, newoft);
+
+ faimdprintf(sess, 2, "faim: OFT: OFT 0x0202 enqueued.\n");
+
+ if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILEFILEREQ)) == NULL)
+ return 1;
+#else
+ return -1;
+#endif
+}
+
+static int handlehdr_getfile_recv(aim_session_t *sess, aim_conn_t *conn, guint8 *hdr)
+{
+#if 0
+ struct aim_fileheader_t *fh;
+ struct aim_filetransfer_priv *ft;
+ struct aim_msgcookie_t *cook;
+ int ret = 1;
+ aim_rxcallback_t userfunc;
+
+ fh = aim_oft_getfh(hdr);
+
+ if (!(cook = aim_checkcookie(sess, fh->bcookie, AIM_COOKIETYPE_OFTGET))) {
+ g_free(fh);
+ return -1;
+ }
+
+ ft = cook->data;
+
+ faimdprintf(sess, 2, "faim: get_rend: looks like we're ready to send data.(oft 0x0202)\n");
+
+ if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILEFILESEND)) )
+ ret = userfunc(sess, NULL, conn, fh);
+
+ g_free(fh);
+
+ return ret;
+#else
+ return -1;
+#endif
+}
+
+static int handlehdr_getfile_finish(aim_session_t *sess, aim_conn_t *conn, guint8 *hdr)
+{
+#if 0
+ struct aim_fileheader_t *fh;
+ aim_rxcallback_t userfunc;
+
+ fh = aim_oft_getfh(hdr);
+
+ faimdprintf(sess, 2, "faim: get_rend: looks like we're done with a transfer (oft 0x0204)\n");
+
+ if ( (userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_OFT, AIM_CB_OFT_GETFILECOMPLETE)) )
+ userfunc(sess, NULL, conn, fh);
+
+ g_free(fh);
+#endif
+
+ return -1;
+}
+
+/**
+ * aim_get_command_rendezvous - OFT equivalent of aim_get_command
+ * @sess: session to work on
+ * @conn: conn to pull data from
+ *
+ * this reads and handles data from conn->fd. currently a little rough
+ * around the edges
+ */
+int aim_get_command_rendezvous(aim_session_t *sess, aim_conn_t *conn)
+{
+ guint8 hdrbuf1[6];
+ guint8 *hdr = NULL;
+ int hdrlen, hdrtype;
+ int ret = -1;
+
+ if (!sess || !conn)
+ return -1;
+
+ memset(hdrbuf1, 0, sizeof(hdrbuf1));
+
+ /* I guess? I didn't understand any of that mess... */
+ if (conn->subtype == AIM_CONN_SUBTYPE_OFT_GETFILE)
+ return getcommand_getfile(sess, conn);
+
+ /* XXX fix all the error cases here */
+ if (aim_recv(conn->fd, hdrbuf1, 6) < 6) {
+
+ do_error_dialog(sess->aux_data, "read error", "Gaim");
+
+ aim_conn_close(conn);
+
+ return -1;
+ }
+
+ hdrlen = aimutil_get16(hdrbuf1+4);
+ hdrlen -= 6;
+
+ hdr = g_malloc(hdrlen);
+
+ if (aim_recv(conn->fd, hdr, hdrlen) < hdrlen) {
+ do_error_dialog(sess->aux_data, "read error", "Gaim");
+ g_free(hdr);
+ aim_conn_close(conn);
+ return -1;
+ }
+
+ hdrtype = aimutil_get16(hdr);
+
+ if (hdrtype == 0x0001)
+ ret = handlehdr_directim(sess, conn, hdr);
+ else if (hdrtype == 0x1108) /* getfile listing.txt incoming tx->rx */
+ ret = handlehdr_getfile_listing(sess, conn, hdr);
+ else if (hdrtype == 0x1209) /* get file listing ack rx->tx */
+ ret = handlehdr_getfile_listing2(sess, conn, hdr);
+ else if (hdrtype == 0x120b) /* get file listing rx confirm */
+ ret = handlehdr_getfile_listing3(sess, conn, hdr);
+ else if (hdrtype == 0x120c) /* getfile request */
+ ret = handlehdr_getfile_request(sess, conn, hdr);
+ else if (hdrtype == 0x0101) /* getfile sending data */
+ ret = handlehdr_getfile_sending(sess, conn, hdr);
+ else if (hdrtype == 0x0202) /* getfile recv data */
+ ret = handlehdr_getfile_recv(sess, conn, hdr);
+ else if (hdrtype == 0x0204) /* getfile finished */
+ ret = handlehdr_getfile_finish(sess, conn, hdr);
+ else {
+ ret = -1;
+ }
+
+ g_free(hdr);
+
+ if (ret == -1)
+ aim_conn_close(conn);
+
+ return ret;
+}
+
+#if 0
+/**
+ * aim_oft_getfh - extracts an &aim_fileheader_t from buffer hdr.
+ * @hdr: buffer to extract header from
+ *
+ * returns pointer to new struct on success; %NULL on error.
+ *
+ */
+static struct aim_fileheader_t *aim_oft_getfh(unsigned char *hdr)
+{
+ struct aim_fileheader_t *fh;
+ int i, j;
+ if (!(fh = calloc(1, sizeof(struct aim_fileheader_t))))
+ return NULL;
+
+ /* [0] and [1] are the type. we can ignore those here. */
+ i = 2;
+ for(j = 0; j < 8; j++, i++)
+ fh->bcookie[j] = hdr[i];
+ fh->encrypt = aimutil_get16(hdr+i);
+ i += 2;
+ fh->compress = aimutil_get16(hdr+i);
+ i += 2;
+ fh->totfiles = aimutil_get16(hdr+i);
+ i += 2;
+ fh->filesleft = aimutil_get16(hdr+i);
+ i += 2;
+ fh->totparts = aimutil_get16(hdr+i);
+ i += 2;
+ fh->partsleft = aimutil_get16(hdr+i);
+ i += 2;
+ fh->totsize = aimutil_get32(hdr+i);
+ i += 4;
+ fh->size = aimutil_get32(hdr+i);
+ i += 4;
+ fh->modtime = aimutil_get32(hdr+i);
+ i += 4;
+ fh->checksum = aimutil_get32(hdr+i);
+ i += 4;
+ fh->rfrcsum = aimutil_get32(hdr+i);
+ i += 4;
+ fh->rfsize = aimutil_get32(hdr+i);
+ i += 4;
+ fh->cretime = aimutil_get32(hdr+i);
+ i += 4;
+ fh->rfcsum = aimutil_get32(hdr+i);
+ i += 4;
+ fh->nrecvd = aimutil_get32(hdr+i);
+ i += 4;
+ fh->recvcsum = aimutil_get32(hdr+i);
+ i += 4;
+ memcpy(fh->idstring, hdr+i, 32);
+ i += 32;
+ fh->flags = aimutil_get8(hdr+i);
+ i += 1;
+ fh->lnameoffset = aimutil_get8(hdr+i);
+ i += 1;
+ fh->lsizeoffset = aimutil_get8(hdr+i);
+ i += 1;
+ memcpy(fh->dummy, hdr+i, 69);
+ i += 69;
+ memcpy(fh->macfileinfo, hdr+i, 16);
+ i += 16;
+ fh->nencode = aimutil_get16(hdr+i);
+ i += 2;
+ fh->nlanguage = aimutil_get16(hdr+i);
+ i += 2;
+ memcpy(fh->name, hdr+i, 64);
+ i += 64;
+ return fh;
+}
+#endif
+
+/**
+ * aim_oft_checksum - calculate oft checksum of buffer
+ * @buffer: buffer of data to checksum
+ * @bufsize: size of buffer
+ * @checksum: pointer to integer to place result in (pointer!)
+ *
+ *
+ * Note that checksum is a pointer. Checksum should be filled with
+ * 0xFFFF0000 for each new file; you can have this checksum chunks of
+ * files in series if you just call it repeatedly in a for(; ; ) loop
+ * and don't reset the checksum between each call. And you thought we
+ * didn't care about you and your pathetic client's meomry footprint
+ * ;^)
+ *
+ *
+ * Also, it's been said that this is incorrect as currently
+ * written. You were warned.
+ */
+guint32 aim_oft_checksum(aim_session_t *sess, const char *buffer, int bufsize, guint32 *checksum)
+{
+ return 0xdeadbeef;
+#if 0
+ guint16 check0, check1;
+ int i;
+
+ check0 = ((*checksum & 0xFF000000) >> 16);
+ check1 = ((*checksum & 0x00ff0000) >> 16);
+ for(i = 0; i < bufsize; i++) {
+ if (i % 2) { /* use check1 -- second byte */
+ if ( (short)buffer[i] > check1 ) { /* wrapping */
+ check1 += 0x100; /* this is a cheap way to wrap */
+
+ /* if we're wrapping, decrement the other one */
+ /* XXX: check this corner case */
+ if (check0 == 0)
+ check0 = 0x00ff;
+ else
+ check0--;
+ }
+ check1 -= buffer[i];
+ } else { /* use check0 -- first byte */
+ if ( (short)buffer[i] > check0 ) { /* wrapping */
+ check0 += 0x100; /* this is a cheap way to wrap */
+
+ /* if we're wrapping, decrement the other one */
+ /* XXX: check this corner case */
+ if (check1 == 0)
+ check1 = 0x00ff;
+ else
+ check1--;
+ }
+ check0 -= buffer[i];
+ }
+ }
+
+ if (check0 > 0xff || check1 > 0xff) {
+ /* they shouldn't be able to do this. error! */
+ faimdprintf(sess, 2, "check0 or check1 is too high: 0x%04x, 0x%04x\n", check0, check1);
+ return -1;
+ }
+
+ /* grab just the lowest byte; this should be clean, but just in
+ case */
+ check0 &= 0xff;
+ check1 &= 0xff;
+
+ *checksum = ((check0 * 0x1000000) + (check1 * 0x10000));
+ return *checksum;
+#endif
+}
+
+#if 0
+/**
+ * aim_oft_buildheader - fills a buffer with network-order fh data
+ * @dest: buffer to fill -- pre-alloced
+ * @fh: fh to get data from
+ *
+ * returns length written; -1 on error.
+ * DOES NOT DO BOUNDS CHECKING!
+ *
+ */
+static int oft_buildheader(unsigned char *dest, struct aim_fileheader_t *fh)
+{
+ int i, curbyte;
+ if (!dest || !fh)
+ return -1;
+ curbyte = 0;
+ for(i = 0; i < 8; i++)
+ curbyte += aimutil_put8(dest+curbyte, fh->bcookie[i]);
+ curbyte += aimutil_put16(dest+curbyte, fh->encrypt);
+ curbyte += aimutil_put16(dest+curbyte, fh->compress);
+ curbyte += aimutil_put16(dest+curbyte, fh->totfiles);
+ curbyte += aimutil_put16(dest+curbyte, fh->filesleft);
+ curbyte += aimutil_put16(dest+curbyte, fh->totparts);
+ curbyte += aimutil_put16(dest+curbyte, fh->partsleft);
+ curbyte += aimutil_put32(dest+curbyte, fh->totsize);
+ curbyte += aimutil_put32(dest+curbyte, fh->size);
+ curbyte += aimutil_put32(dest+curbyte, fh->modtime);
+ curbyte += aimutil_put32(dest+curbyte, fh->checksum);
+ curbyte += aimutil_put32(dest+curbyte, fh->rfrcsum);
+ curbyte += aimutil_put32(dest+curbyte, fh->rfsize);
+ curbyte += aimutil_put32(dest+curbyte, fh->cretime);
+ curbyte += aimutil_put32(dest+curbyte, fh->rfcsum);
+ curbyte += aimutil_put32(dest+curbyte, fh->nrecvd);
+ curbyte += aimutil_put32(dest+curbyte, fh->recvcsum);
+ memcpy(dest+curbyte, fh->idstring, 32);
+ curbyte += 32;
+ curbyte += aimutil_put8(dest+curbyte, fh->flags);
+ curbyte += aimutil_put8(dest+curbyte, fh->lnameoffset);
+ curbyte += aimutil_put8(dest+curbyte, fh->lsizeoffset);
+ memcpy(dest+curbyte, fh->dummy, 69);
+ curbyte += 69;
+ memcpy(dest+curbyte, fh->macfileinfo, 16);
+ curbyte += 16;
+ curbyte += aimutil_put16(dest+curbyte, fh->nencode);
+ curbyte += aimutil_put16(dest+curbyte, fh->nlanguage);
+ memset(dest+curbyte, 0x00, 64);
+ memcpy(dest+curbyte, fh->name, 64);
+
+ /* XXX: Filenames longer than 64B */
+ curbyte += 64;
+ return curbyte;
+}
+#endif
+
+/**
+ * aim_getfile_intitiate - Request an OFT getfile session
+ * @sess: your session,
+ * @conn: the BOS conn,
+ * @destsn is the SN to connect to.
+ *
+ * returns a new &aim_conn_t on success, %NULL on error
+ */
+aim_conn_t *aim_getfile_initiate(aim_session_t *sess, aim_conn_t *conn, const char *destsn)
+{
+ return NULL;
+#if 0
+ struct command_tx_struct *newpacket;
+ struct aim_conn_t *newconn;
+ struct aim_filetransfer_priv *priv;
+ struct aim_msgcookie_t *cookie;
+ int curbyte, i, listenfd;
+ short port = 4443;
+ struct hostent *hptr;
+ struct utsname myname;
+ char cap[16];
+ char d[4];
+
+ /* Open our socket */
+
+ if ( (listenfd = aim_listenestablish(port)) == -1)
+ return NULL;
+
+ /* get our local IP */
+
+ if (uname(&myname) < 0)
+ return NULL;
+ if ( (hptr = gethostbyname(myname.nodename)) == NULL)
+ return NULL;
+ memcpy(&d, hptr->h_addr_list[0], 4);
+
+ aim_putcap(cap, 16, AIM_CAPS_GETFILE);
+
+ /* create the OSCAR packet */
+
+ if (!(newpacket = aim_tx_new(sess, conn, AIM_FRAMETYPE_OSCAR, 0x0002, 10+8+2+1+strlen(destsn)+4+4+0x42)))
+ return NULL;
+ newpacket->lock = 1;
+
+ /* lock struct */
+ curbyte = 0;
+ curbyte += aim_putsnac(newpacket->data+curbyte, 0x0004, 0x0006, 0x0000, sess->snac_nextid);
+
+ /* XXX: check the cookie before commiting to using it */
+
+ /* Generate a random message cookie
+ * This cookie needs to be alphanumeric and NULL-terminated to be TOC-compatible. */
+ for (i=0; i<7; i++)
+ curbyte += aimutil_put8(newpacket->data+curbyte, 0x30 + ((u_char) random() % 10));
+
+ curbyte += aimutil_put8(newpacket->data+curbyte, 0x00);
+
+ /* grab all the data for cookie caching. */
+
+ if (!(cookie = (struct aim_msgcookie_t *)calloc(1, sizeof(struct aim_msgcookie_t))))
+ return NULL;
+ memcpy(cookie->cookie, newpacket->data+curbyte-8, 8);
+ cookie->type = AIM_COOKIETYPE_OFTGET;
+
+ if (!(priv = (struct aim_filetransfer_priv *)calloc(1, sizeof(struct aim_filetransfer_priv))))
+ return NULL;
+ memcpy(priv->cookie, cookie, 8);
+ memcpy(priv->sn, destsn, sizeof(priv->sn));
+ memcpy(priv->fh.name, "listing.txt", strlen("listing.txt"));
+ priv->state = 1;
+
+ cookie->data = priv;
+
+ aim_cachecookie(sess, cookie);
+
+ /* Channel ID */
+ curbyte += aimutil_put16(newpacket->data+curbyte,0x0002);
+
+ /* Destination SN (prepended with byte length) */
+ curbyte += aimutil_put8(newpacket->data+curbyte,strlen(destsn));
+ curbyte += aimutil_putstr(newpacket->data+curbyte, destsn, strlen(destsn));
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0003);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0000);
+
+ /* enTLV start */
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0005);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0042);
+
+ /* Flag data / ICBM Parameters? */
+ curbyte += aimutil_put8(newpacket->data+curbyte, 0x00);
+ curbyte += aimutil_put8(newpacket->data+curbyte, 0x00);
+
+ /* Cookie */
+ curbyte += aimutil_putstr(newpacket->data+curbyte, (char *)cookie, 8);
+
+ /* Capability String */
+ curbyte += aimutil_putstr(newpacket->data+curbyte, (char *)cap, 0x10);
+
+ /* 000a/0002 : 0001 */
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x000a);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0002);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0001);
+
+ /* 0003/0004: IP address */
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0003);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0004);
+ for(i = 0; i < 4; i++)
+ curbyte += aimutil_put8(newpacket->data+curbyte, d[i]);
+
+ /* already in network byte order */
+
+ /* 0005/0002: Port */
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0005);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0002);
+ curbyte += aimutil_put16(newpacket->data+curbyte, port);
+
+ /* 000f/0000: ?? */
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x000f);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x0000);
+
+ /* 2711/000c: ?? */
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x2711);
+ curbyte += aimutil_put16(newpacket->data+curbyte, 0x000c);
+ curbyte += aimutil_put32(newpacket->data+curbyte, 0x00120001);
+
+ for(i = 0; i < 0x000c - 4; i++)
+ curbyte += aimutil_put8(newpacket->data+curbyte, 0x00);
+
+ newpacket->commandlen = curbyte;
+ newpacket->lock = 0;
+ aim_tx_enqueue(sess, newpacket);
+
+ /* allocate and set up our connection */
+
+ i = fcntl(listenfd, F_GETFL, 0);
+ fcntl(listenfd, F_SETFL, i | O_NONBLOCK);
+ newconn = aim_newconn(sess, AIM_CONN_TYPE_RENDEZVOUS_OUT, NULL);
+
+ if (!newconn){
+ perror("aim_newconn");
+ return NULL;
+ }
+
+ newconn->fd = listenfd;
+ newconn->subtype = AIM_CONN_SUBTYPE_OFT_GETFILE;
+ newconn->priv = priv;
+
+ return newconn;
+#endif
+}
+
+/**
+ * aim_oft_getfile_request - request a particular file over an established getfile connection
+ * @sess: your session
+ * @conn: the established OFT getfile connection
+ * @name: filename to request
+ * @size: size of the file
+ *
+ *
+ * returns -1 on error, 0 on successful enqueuing
+ */
+int aim_oft_getfile_request(aim_session_t *sess, aim_conn_t *conn, const char *name, int size)
+{
+ return -EINVAL;
+#if 0
+ struct command_tx_struct *newoft;
+ struct aim_filetransfer_priv *ft;
+ if (!sess || !conn || !conn->priv || !name)
+ return -1;
+
+ if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x120c, 0))) {
+ faimdprintf(sess, 2, "faim: aim_accepttransfer: tx_new OFT failed\n");
+ return -1;
+ }
+
+ newoft->lock = 1;
+
+ memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+ newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+ ft = (struct aim_filetransfer_priv *)conn->priv;
+ ft->fh.filesleft = 1;
+ ft->fh.totfiles = 1;
+ ft->fh.totparts = 1;
+ ft->fh.partsleft = 1;
+ ft->fh.totsize = size;
+ ft->fh.size = size;
+ ft->fh.checksum = 0;
+ memcpy(ft->fh.name, name, strlen(name));
+ memset(ft->fh.name+strlen(name), 0, 1);
+
+ if (!(newoft->hdr.oft.hdr2 = (unsigned char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+ newoft->lock = 0;
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ if (!(aim_oft_buildheader(newoft->hdr.oft.hdr2, &(ft->fh)))) {
+ newoft->lock = 0;
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ newoft->lock = 0;
+
+ aim_tx_enqueue(sess, newoft);
+ return 0;
+#endif
+}
+
+/**
+ * aim_oft_getfile_ack - acknowledge a getfile download as complete
+ * @sess: your session
+ * @conn: the getfile conn to send the ack over
+ *
+ * Call this function after you have read all the data in a particular
+ * filetransfer. Returns -1 on error, 0 on apparent success
+ *
+ */
+int aim_oft_getfile_ack(aim_session_t *sess, aim_conn_t *conn)
+{
+ return -EINVAL;
+#if 0
+ struct command_tx_struct *newoft;
+ struct aim_filetransfer_priv *ft;
+
+ if (!sess || !conn || !conn->priv)
+ return -1;
+
+ if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x0202, 0))) {
+ faimdprintf(sess, 2, "faim: aim_accepttransfer: tx_new OFT failed\n");
+ return -1;
+ }
+
+ newoft->lock = 1;
+
+ memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+ newoft->hdr.oft.hdr2len = 0x100-8;
+
+ if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+ newoft->lock = 0;
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ ft = (struct aim_filetransfer_priv *)conn->priv;
+
+ if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh)))) {
+ newoft->lock = 0;
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ newoft->lock = 0;
+ aim_tx_enqueue(sess, newoft);
+ return 0;
+#endif
+}
+
+/**
+ * aim_oft_getfile_end - end a getfile.
+ * @sess: your session
+ * @conn: the getfile connection
+ *
+ * call this before you close the getfile connection if you're on the
+ * receiving/requesting end.
+ */
+int aim_oft_getfile_end(aim_session_t *sess, aim_conn_t *conn)
+{
+ return -EINVAL;
+#if 0
+ struct command_tx_struct *newoft;
+ struct aim_filetransfer_priv *ft;
+
+ if (!sess || !conn || !conn->priv)
+ return -1;
+
+ if (!(newoft = aim_tx_new(sess, conn, AIM_FRAMETYPE_OFT, 0x0204, 0))) {
+ faimdprintf(sess, 2, "faim: aim_accepttransfer: tx_new OFT failed\n");
+ return -1;
+ }
+
+ newoft->lock = 1;
+
+ memcpy(newoft->hdr.oft.magic, "OFT2", 4);
+ newoft->hdr.oft.hdr2len = 0x100 - 8;
+
+ if (!(newoft->hdr.oft.hdr2 = (char *)calloc(1,newoft->hdr.oft.hdr2len))) {
+ newoft->lock = 0;
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ ft = (struct aim_filetransfer_priv *)conn->priv;
+ ft->state = 4; /* no longer wanting data */
+ ft->fh.nrecvd = ft->fh.size;
+ ft->fh.recvcsum = ft->fh.checksum;
+ ft->fh.flags = 0x21;
+
+ if (!(aim_oft_buildheader((unsigned char *)newoft->hdr.oft.hdr2, &(ft->fh)))) {
+ newoft->lock = 0;
+ aim_frame_destroy(newoft);
+ return -1;
+ }
+
+ newoft->lock = 0;
+ aim_tx_enqueue(sess, newoft);
+
+ return 0;
+#endif /* 0 */
+}
+
diff --git a/protocols/oscar/ft.h b/protocols/oscar/ft.h
new file mode 100644
index 00000000..7b1142eb
--- /dev/null
+++ b/protocols/oscar/ft.h
@@ -0,0 +1,78 @@
+#ifndef __OSCAR_FT_H__
+#define __OSCAR_FT_H__
+
+#define AIM_CB_FAM_OFT 0xfffe /* OFT/Rvous */
+
+/*
+ * 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.*/
+
+struct aim_fileheader_t {
+#if 0
+ char magic[4]; /* 0 */
+ short hdrlen; /* 4 */
+ short hdrtype; /* 6 */
+#endif
+ char bcookie[8]; /* 8 */
+ short encrypt; /* 16 */
+ short compress; /* 18 */
+ short totfiles; /* 20 */
+ short filesleft; /* 22 */
+ short totparts; /* 24 */
+ short partsleft; /* 26 */
+ long totsize; /* 28 */
+ long size; /* 32 */
+ long modtime; /* 36 */
+ long checksum; /* 40 */
+ long rfrcsum; /* 44 */
+ long rfsize; /* 48 */
+ long cretime; /* 52 */
+ long rfcsum; /* 56 */
+ long nrecvd; /* 60 */
+ long recvcsum; /* 64 */
+ char idstring[32]; /* 68 */
+ char flags; /* 100 */
+ char lnameoffset; /* 101 */
+ char lsizeoffset; /* 102 */
+ char dummy[69]; /* 103 */
+ char macfileinfo[16]; /* 172 */
+ short nencode; /* 188 */
+ short nlanguage; /* 190 */
+ char name[64]; /* 192 */
+ /* 256 */
+};
+
+
+
+struct aim_filetransfer_priv {
+ char sn[MAXSNLEN];
+ guint8 cookie[8];
+ char ip[30];
+ int state;
+ struct aim_fileheader_t fh;
+};
+
+#define AIM_CB_FAM_OFT 0xfffe /* OFT/Rvous */
+
+#endif /* __OSCAR_FT_H__ */
diff --git a/protocols/oscar/icq.c b/protocols/oscar/icq.c
new file mode 100644
index 00000000..23959b75
--- /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);
+ do_error_dialog(sess->aux_data, "corrupt ICQ response\n", "Gaim");
+ 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..99661846
--- /dev/null
+++ b/protocols/oscar/im.c
@@ -0,0 +1,2029 @@
+/*
+ * 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 &#2026; 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) {
+ do_error_dialog(sess->aux_data, "icbm: ICBM recieved on unsupported channel. Ignoring.", "Gaim");
+ 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 {
+ // do_error_dialog(sess->aux_data, "Unknown TLV encountered", "Gaim");
+ }
+
+ /*
+ * 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_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 gaim_connection *gc = 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,
+ gc->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)
+ do_error_dialog(sess->aux_data, "rend: warning cookies don't match!", "Gaim");
+ 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);
+
+
+ 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 {
+
+ do_error_dialog(sess->aux_data, "ICBM received on an unsupported channel. Ignoring.", "Gaim");
+
+ 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, &params);
+
+ 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;
+}
+
+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);
+
+ 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..061ff5b5
--- /dev/null
+++ b/protocols/oscar/im.h
@@ -0,0 +1,198 @@
+#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_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_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..ffe29d1f
--- /dev/null
+++ b/protocols/oscar/info.c
@@ -0,0 +1,725 @@
+/*
+ * 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*/
+ 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
+ // do_error_dialog(sess->aux_data, G_STRLOC, "Unknown TLV encountered");
+#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) {
+ do_error_dialog(sess->aux_data, "major problem: no snac stored!", "Gaim");
+ 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)) {
+ do_error_dialog(sess->aux_data, "unknown infotype in request!", "Gaim");
+ 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..bb498d72
--- /dev/null
+++ b/protocols/oscar/msgcookie.c
@@ -0,0 +1,196 @@
+/*
+ * 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;
+}
+
+#if 0 /* debugging feature */
+int aim_dumpcookie(aim_msgcookie_t *cookie)
+{
+
+ if (!cookie)
+ return -EINVAL;
+
+ printf("\tCookie at %p: %d/%s with %p, next %p\n",
+ cookie, cookie->type, cookie->cookie,
+ cookie->data, cookie->next);
+
+ return 0;
+}
+#endif
+
+/**
+ * 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;
+}
+
+/* XXX I hate switch */
+int aim_msgcookie_gettype(int reqclass)
+{
+ /* XXX: hokey-assed. needs fixed. */
+ switch(reqclass) {
+ case AIM_CAPS_BUDDYICON: return AIM_COOKIETYPE_OFTICON;
+ case AIM_CAPS_VOICE: return AIM_COOKIETYPE_OFTVOICE;
+ case AIM_CAPS_IMIMAGE: return AIM_COOKIETYPE_OFTIMAGE;
+ case AIM_CAPS_CHAT: return AIM_COOKIETYPE_CHAT;
+ case AIM_CAPS_GETFILE: return AIM_COOKIETYPE_OFTGET;
+ case AIM_CAPS_SENDFILE: return AIM_COOKIETYPE_OFTSEND;
+ default: return AIM_COOKIETYPE_UNKNOWN;
+ }
+}
+
+
diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c
new file mode 100644
index 00000000..5a1ddc45
--- /dev/null
+++ b/protocols/oscar/oscar.c
@@ -0,0 +1,2491 @@
+/*
+ * gaim
+ *
+ * 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 "sock.h"
+#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 "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"
+
+/* 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;
+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;
+ gboolean icq;
+ GSList *evilhack;
+
+ 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 gaim_connection *gc; /* i hate this. */
+ struct conversation *cnv; /* bah. */
+ int maxlen;
+ int maxvis;
+};
+
+struct ask_direct {
+ struct gaim_connection *gc;
+ char *sn;
+ char ip[64];
+ guint8 cookie[8];
+};
+
+struct icq_auth {
+ struct gaim_connection *gc;
+ guint32 uin;
+};
+
+static char *extract_name(const char *name) {
+ char *tmp;
+ int i, j;
+ char *x = strchr(name, '-');
+ if (!x) return NULL;
+ x = strchr(++x, '-');
+ if (!x) return NULL;
+ 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;
+}
+
+#if 0
+static struct chat_connection *find_oscar_chat(struct gaim_connection *gc, int id) {
+ GSList *g = ((struct oscar_data *)gc->proto_data)->oscar_chats;
+ struct chat_connection *c = NULL;
+
+ while (g) {
+ c = (struct chat_connection *)g->data;
+ if (c->id == id)
+ break;
+ g = g->next;
+ c = NULL;
+ }
+
+ return c;
+}
+#endif
+
+static struct chat_connection *find_oscar_chat_by_conn(struct gaim_connection *gc,
+ aim_conn_t *conn) {
+ GSList *g = ((struct oscar_data *)gc->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_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_memrequest (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_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;
+
+static void oscar_callback(gpointer data, gint source,
+ GaimInputCondition condition) {
+ aim_conn_t *conn = (aim_conn_t *)data;
+ aim_session_t *sess = aim_conn_getsess(conn);
+ struct gaim_connection *gc = sess ? sess->aux_data : NULL;
+ struct oscar_data *odata;
+
+ if (!gc) {
+ /* gc is null. we return, else we seg SIGSEG on next line. */
+ return;
+ }
+
+ if (!g_slist_find(get_connections(), gc)) {
+ /* oh boy. this is probably bad. i guess the only thing we
+ * can really do is return? */
+ return;
+ }
+
+ odata = (struct oscar_data *)gc->proto_data;
+
+ if (condition & GAIM_INPUT_READ) {
+ if (conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) {
+ if (aim_handlerendconnect(odata->sess, conn) < 0) {
+ aim_conn_kill(odata->sess, &conn);
+ }
+ } else {
+ if (aim_get_command(odata->sess, conn) >= 0) {
+ aim_rxdispatch(odata->sess);
+ if (odata->killme)
+ signoff(gc);
+ } else {
+ if ((conn->type == AIM_CONN_TYPE_BOS) ||
+ !(aim_getconn_type(odata->sess, AIM_CONN_TYPE_BOS))) {
+ hide_login_progress_error(gc, _("Disconnected."));
+ signoff(gc);
+ } else if (conn->type == AIM_CONN_TYPE_CHAT) {
+ struct chat_connection *c = find_oscar_chat_by_conn(gc, conn);
+ char buf[BUF_LONG];
+ c->conn = NULL;
+ if (c->inpa > 0)
+ gaim_input_remove(c->inpa);
+ c->inpa = 0;
+ c->fd = -1;
+ aim_conn_kill(odata->sess, &conn);
+ sprintf(buf, _("You have been disconnected from chat room %s."), c->name);
+ do_error_dialog(sess->aux_data, buf, _("Chat Error!"));
+ } else if (conn->type == AIM_CONN_TYPE_CHATNAV) {
+ if (odata->cnpa > 0)
+ gaim_input_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);
+ do_error_dialog(sess->aux_data, _("Chat is currently unavailable"),
+ _("Gaim - Chat"));
+ }
+ aim_conn_kill(odata->sess, &conn);
+ } else if (conn->type == AIM_CONN_TYPE_AUTH) {
+ if (odata->paspa > 0)
+ gaim_input_remove(odata->paspa);
+ odata->paspa = 0;
+ aim_conn_kill(odata->sess, &conn);
+ } else {
+ aim_conn_kill(odata->sess, &conn);
+ }
+ }
+ }
+ }
+}
+
+static void oscar_login_connect(gpointer data, gint source, GaimInputCondition cond)
+{
+ struct gaim_connection *gc = data;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *conn;
+
+ if (!g_slist_find(get_connections(), gc)) {
+ closesocket(source);
+ return;
+ }
+
+ odata = gc->proto_data;
+ sess = odata->sess;
+ conn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH);
+
+ if (source < 0) {
+ hide_login_progress(gc, _("Couldn't connect to host"));
+ signoff(gc);
+ return;
+ }
+
+ aim_conn_completeconnect(sess, conn);
+ gc->inpa = gaim_input_add(conn->fd, GAIM_INPUT_READ,
+ oscar_callback, conn);
+}
+
+static void oscar_login(struct aim_user *user) {
+ aim_session_t *sess;
+ aim_conn_t *conn;
+ char buf[256];
+ struct gaim_connection *gc = new_gaim_conn(user);
+ struct oscar_data *odata = gc->proto_data = g_new0(struct oscar_data, 1);
+
+ if (isdigit(*user->username)) {
+ odata->icq = TRUE;
+ /* this is odd but it's necessary for a proper do_import and do_export */
+ gc->protocol = PROTO_ICQ;
+ gc->password[8] = 0;
+ } else {
+ gc->protocol = PROTO_TOC;
+ gc->flags |= OPT_CONN_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 = gc;
+
+ conn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, NULL);
+ if (conn == NULL) {
+ hide_login_progress(gc, _("Unable to login to AIM"));
+ signoff(gc);
+ return;
+ }
+
+ if (g_strcasecmp(user->proto_opt[USEROPT_AUTH], "login.icq.com") != 0 &&
+ g_strcasecmp(user->proto_opt[USEROPT_AUTH], "login.oscar.aol.com") != 0) {
+ serv_got_crap(gc, "Warning: Unknown OSCAR server: `%s'. Please review your configuration if the connection fails.");
+ }
+
+ g_snprintf(buf, sizeof(buf), _("Signon: %s"), gc->username);
+ set_login_progress(gc, 2, buf);
+
+ 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(user->proto_opt[USEROPT_AUTH][0] ?
+ user->proto_opt[USEROPT_AUTH] : AIM_DEFAULT_LOGIN_SERVER,
+ user->proto_opt[USEROPT_AUTHPORT][0] ?
+ atoi(user->proto_opt[USEROPT_AUTHPORT]) : AIM_LOGIN_PORT,
+ oscar_login_connect, gc);
+ if (conn->fd < 0) {
+ hide_login_progress(gc, _("Couldn't connect to host"));
+ signoff(gc);
+ return;
+ }
+ aim_request_login(sess, conn, gc->username);
+}
+
+static void oscar_close(struct gaim_connection *gc) {
+ struct oscar_data *odata = (struct oscar_data *)gc->proto_data;
+
+ while (odata->oscar_chats) {
+ struct chat_connection *n = odata->oscar_chats->data;
+ if (n->inpa > 0)
+ gaim_input_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->email)
+ g_free(odata->email);
+ if (odata->newp)
+ g_free(odata->newp);
+ if (odata->oldp)
+ g_free(odata->oldp);
+ if (gc->inpa > 0)
+ gaim_input_remove(gc->inpa);
+ if (odata->cnpa > 0)
+ gaim_input_remove(odata->cnpa);
+ if (odata->paspa > 0)
+ gaim_input_remove(odata->paspa);
+ aim_session_kill(odata->sess);
+ g_free(odata->sess);
+ odata->sess = NULL;
+ g_free(gc->proto_data);
+ gc->proto_data = NULL;
+}
+
+static void oscar_bos_connect(gpointer data, gint source, GaimInputCondition cond) {
+ struct gaim_connection *gc = data;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *bosconn;
+
+ if (!g_slist_find(get_connections(), gc)) {
+ closesocket(source);
+ return;
+ }
+
+ odata = gc->proto_data;
+ sess = odata->sess;
+ bosconn = odata->conn;
+
+ if (source < 0) {
+ hide_login_progress(gc, _("Could Not Connect"));
+ signoff(gc);
+ return;
+ }
+
+ aim_conn_completeconnect(sess, bosconn);
+ gc->inpa = gaim_input_add(bosconn->fd, GAIM_INPUT_READ,
+ oscar_callback, bosconn);
+ set_login_progress(gc, 4, _("Connection established, cookie sent"));
+}
+
+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;
+ struct aim_user *user;
+ aim_conn_t *bosconn;
+
+ struct gaim_connection *gc = sess->aux_data;
+ struct oscar_data *od = gc->proto_data;
+ user = gc->user;
+ port = user->proto_opt[USEROPT_AUTHPORT][0] ?
+ atoi(user->proto_opt[USEROPT_AUTHPORT]) : 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 */
+ hide_login_progress(gc, _("Incorrect nickname or password."));
+// plugin_event(event_error, (void *)980, 0, 0, 0);
+ break;
+ case 0x11:
+ /* Suspended account */
+ hide_login_progress(gc, _("Your account is currently suspended."));
+ break;
+ case 0x18:
+ /* connecting too frequently */
+ hide_login_progress(gc, _("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 */
+ hide_login_progress(gc, _("The client version you are using is too old. Please upgrade at " WEBSITE));
+ break;
+ default:
+ hide_login_progress(gc, _("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) {
+ hide_login_progress(gc, _("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, 0x1f, gaim_memrequest, 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);
+
+ ((struct oscar_data *)gc->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, gc);
+ g_free(host);
+ if (bosconn->fd < 0) {
+ hide_login_progress(gc, _("Could Not Connect"));
+ od->killme = TRUE;
+ return 0;
+ }
+ aim_sendcookie(sess, bosconn, info->cookie);
+ gaim_input_remove(gc->inpa);
+
+ return 1;
+}
+
+struct pieceofcrap {
+ struct gaim_connection *gc;
+ unsigned long offset;
+ unsigned long len;
+ char *modname;
+ int fd;
+ aim_conn_t *conn;
+ unsigned int inpa;
+};
+
+static void damn_you(gpointer data, gint source, GaimInputCondition c)
+{
+ struct pieceofcrap *pos = data;
+ struct oscar_data *od = pos->gc->proto_data;
+ char in = '\0';
+ int x = 0;
+ unsigned char m[17];
+
+ while (read(pos->fd, &in, 1) == 1) {
+ if (in == '\n')
+ x++;
+ else if (in != '\r')
+ x = 0;
+ if (x == 2)
+ break;
+ in = '\0';
+ }
+ if (in != '\n') {
+ do_error_dialog(pos->gc, "Gaim was unable to get a valid hash for logging into AIM."
+ " You may be disconnected shortly.", "Login Error");
+ gaim_input_remove(pos->inpa);
+ closesocket(pos->fd);
+ g_free(pos);
+ return;
+ }
+ read(pos->fd, m, 16);
+ m[16] = '\0';
+ gaim_input_remove(pos->inpa);
+ closesocket(pos->fd);
+ aim_sendmemblock(od->sess, pos->conn, 0, 16, m, AIM_SENDMEMBLOCK_FLAG_ISHASH);
+ g_free(pos);
+}
+
+static void straight_to_hell(gpointer data, gint source, GaimInputCondition cond) {
+ struct pieceofcrap *pos = data;
+ char buf[BUF_LONG];
+
+ if (source < 0) {
+ do_error_dialog(pos->gc, "Gaim was unable to get a valid hash for logging into AIM."
+ " You may be disconnected shortly.", "Login Error");
+ if (pos->modname)
+ g_free(pos->modname);
+ g_free(pos);
+ return;
+ }
+
+ g_snprintf(buf, sizeof(buf), "GET " AIMHASHDATA
+ "?offset=%ld&len=%ld&modname=%s HTTP/1.0\n\n",
+ pos->offset, pos->len, pos->modname ? pos->modname : "");
+ write(pos->fd, buf, strlen(buf));
+ if (pos->modname)
+ g_free(pos->modname);
+ pos->inpa = gaim_input_add(pos->fd, GAIM_INPUT_READ, damn_you, pos);
+ return;
+}
+
+/* size of icbmui.ocm, the largest module in AIM 3.5 */
+#define AIM_MAX_FILE_SIZE 98304
+
+int gaim_memrequest(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ struct pieceofcrap *pos;
+ guint32 offset, len;
+ char *modname;
+ int fd;
+
+ va_start(ap, fr);
+ offset = (guint32)va_arg(ap, unsigned long);
+ len = (guint32)va_arg(ap, unsigned long);
+ modname = va_arg(ap, char *);
+ va_end(ap);
+
+ if (len == 0) {
+ aim_sendmemblock(sess, fr->conn, offset, len, NULL,
+ AIM_SENDMEMBLOCK_FLAG_ISREQUEST);
+ return 1;
+ }
+ /* uncomment this when you're convinced it's right. remember, it's been wrong before.
+ if (offset > AIM_MAX_FILE_SIZE || len > AIM_MAX_FILE_SIZE) {
+ char *buf;
+ int i = 8;
+ if (modname)
+ i += strlen(modname);
+ buf = g_malloc(i);
+ i = 0;
+ if (modname) {
+ memcpy(buf, modname, strlen(modname));
+ i += strlen(modname);
+ }
+ buf[i++] = offset & 0xff;
+ buf[i++] = (offset >> 8) & 0xff;
+ buf[i++] = (offset >> 16) & 0xff;
+ buf[i++] = (offset >> 24) & 0xff;
+ buf[i++] = len & 0xff;
+ buf[i++] = (len >> 8) & 0xff;
+ buf[i++] = (len >> 16) & 0xff;
+ buf[i++] = (len >> 24) & 0xff;
+ aim_sendmemblock(sess, command->conn, offset, i, buf, AIM_SENDMEMBLOCK_FLAG_ISREQUEST);
+ g_free(buf);
+ return 1;
+ }
+ */
+
+ pos = g_new0(struct pieceofcrap, 1);
+ pos->gc = sess->aux_data;
+ pos->conn = fr->conn;
+
+ pos->offset = offset;
+ pos->len = len;
+ pos->modname = modname ? g_strdup(modname) : NULL;
+
+ fd = proxy_connect("gaim.sourceforge.net", 80, straight_to_hell, pos);
+ if (fd < 0) {
+ if (pos->modname)
+ g_free(pos->modname);
+ g_free(pos);
+ do_error_dialog(sess->aux_data, "Gaim was unable to get a valid hash for logging into AIM."
+ " You may be disconnected shortly.", "Login Error");
+ }
+ pos->fd = fd;
+
+ return 1;
+}
+
+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 gaim_connection *gc = sess->aux_data;
+
+ va_start(ap, fr);
+ key = va_arg(ap, char *);
+ va_end(ap);
+
+ aim_send_login(sess, fr->conn, gc->username, gc->password, &info, key);
+
+ return 1;
+}
+
+static int conninitdone_chat(aim_session_t *sess, aim_frame_t *fr, ...) {
+ struct gaim_connection *gc = sess->aux_data;
+ struct chat_connection *chatcon;
+ 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(gc, fr->conn);
+ chatcon->id = id;
+ chatcon->cnv = serv_got_joined_chat(gc, id++, chatcon->show);
+
+ 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 void oscar_chatnav_connect(gpointer data, gint source, GaimInputCondition cond) {
+ struct gaim_connection *gc = data;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *tstconn;
+
+ if (!g_slist_find(get_connections(), gc)) {
+ closesocket(source);
+ return;
+ }
+
+ odata = gc->proto_data;
+ sess = odata->sess;
+ tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_CHATNAV);
+
+ if (source < 0) {
+ aim_conn_kill(sess, &tstconn);
+ return;
+ }
+
+ aim_conn_completeconnect(sess, tstconn);
+ odata->cnpa = gaim_input_add(tstconn->fd, GAIM_INPUT_READ,
+ oscar_callback, tstconn);
+}
+
+static void oscar_auth_connect(gpointer data, gint source, GaimInputCondition cond)
+{
+ struct gaim_connection *gc = data;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *tstconn;
+
+ if (!g_slist_find(get_connections(), gc)) {
+ closesocket(source);
+ return;
+ }
+
+ odata = gc->proto_data;
+ sess = odata->sess;
+ tstconn = aim_getconn_type_all(sess, AIM_CONN_TYPE_AUTH);
+
+ if (source < 0) {
+ aim_conn_kill(sess, &tstconn);
+ return;
+ }
+
+ aim_conn_completeconnect(sess, tstconn);
+ odata->paspa = gaim_input_add(tstconn->fd, GAIM_INPUT_READ,
+ oscar_callback, tstconn);
+}
+
+static void oscar_chat_connect(gpointer data, gint source, GaimInputCondition cond)
+{
+ struct chat_connection *ccon = data;
+ struct gaim_connection *gc = ccon->gc;
+ struct oscar_data *odata;
+ aim_session_t *sess;
+ aim_conn_t *tstconn;
+
+ if (!g_slist_find(get_connections(), gc)) {
+ closesocket(source);
+ g_free(ccon->show);
+ g_free(ccon->name);
+ g_free(ccon);
+ return;
+ }
+
+ odata = gc->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;
+ }
+
+ aim_conn_completeconnect(sess, ccon->conn);
+ ccon->inpa = gaim_input_add(tstconn->fd,
+ GAIM_INPUT_READ,
+ oscar_callback, tstconn);
+ odata->oscar_chats = g_slist_append(odata->oscar_chats, ccon);
+}
+
+/* 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 gaim_connection *gc = sess->aux_data;
+ struct aim_user *user = gc->user;
+ aim_conn_t *tstconn;
+ int i;
+ char *host;
+ int port;
+
+ port = user->proto_opt[USEROPT_AUTHPORT][0] ?
+ atoi(user->proto_opt[USEROPT_AUTHPORT]) : AIM_LOGIN_PORT,
+
+ va_start(ap, fr);
+ redir = va_arg(ap, struct aim_redirect_data *);
+ va_end(ap);
+
+ 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, gc);
+ 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, gc);
+ 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->gc = gc;
+ 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 gaim_connection *gc = sess->aux_data;
+ struct oscar_data *od = gc->proto_data;
+ aim_userinfo_t *info;
+ time_t time_idle = 0, signon = 0;
+ int type = 0;
+ int caps = 0;
+ char *tmp;
+
+ va_list ap;
+ va_start(ap, fr);
+ info = va_arg(ap, aim_userinfo_t *);
+ va_end(ap);
+
+ if (info->present & AIM_USERINFO_PRESENT_CAPABILITIES)
+ caps = info->capabilities;
+ if (info->flags & AIM_FLAG_ACTIVEBUDDY)
+ type |= UC_AB;
+
+ if ((!od->icq) && (info->present & AIM_USERINFO_PRESENT_FLAGS)) {
+ if (info->flags & AIM_FLAG_UNCONFIRMED)
+ type |= UC_UNCONFIRMED;
+ if (info->flags & AIM_FLAG_ADMINISTRATOR)
+ type |= UC_ADMIN;
+ if (info->flags & AIM_FLAG_AOL)
+ type |= UC_AOL;
+ if (info->flags & AIM_FLAG_FREE)
+ type |= UC_NORMAL;
+ if (info->flags & AIM_FLAG_AWAY)
+ type |= UC_UNAVAILABLE;
+ if (info->flags & AIM_FLAG_WIRELESS)
+ type |= UC_WIRELESS;
+ }
+ if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS) {
+ type = (info->icqinfo.status << 7);
+ if (!(info->icqinfo.status & AIM_ICQ_STATE_CHAT) &&
+ (info->icqinfo.status != AIM_ICQ_STATE_NORMAL)) {
+ type |= UC_UNAVAILABLE;
+ }
+ }
+
+ if (caps & AIM_CAPS_ICQ)
+ caps ^= AIM_CAPS_ICQ;
+
+ 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;
+
+ tmp = g_strdup(normalize(gc->username));
+ if (!strcmp(tmp, normalize(info->sn)))
+ g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", info->sn);
+ g_free(tmp);
+
+ serv_got_update(gc, info->sn, 1, info->warnlevel/10, signon,
+ time_idle, type, caps);
+
+ return 1;
+}
+
+static int gaim_parse_offgoing(aim_session_t *sess, aim_frame_t *fr, ...) {
+ aim_userinfo_t *info;
+ va_list ap;
+ struct gaim_connection *gc = sess->aux_data;
+
+ va_start(ap, fr);
+ info = va_arg(ap, aim_userinfo_t *);
+ va_end(ap);
+
+ serv_got_update(gc, info->sn, 0, 0, 0, 0, 0, 0);
+
+ 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 gaim_connection *gc = sess->aux_data;
+ int flags = 0;
+
+ if (args->icbmflags & AIM_IMFLAGS_AWAY)
+ flags |= IM_FLAG_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
+ g_snprintf(tmp, BUF_LONG, "%s", args->msg);
+
+ strip_linefeed(tmp);
+ serv_got_im(gc, userinfo->sn, tmp, flags, time(NULL), -1);
+ g_free(tmp);
+
+ return 1;
+}
+
+static int incomingim_chan2(aim_session_t *sess, aim_conn_t *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch2_args *args) {
+#if 0
+ struct gaim_connection *gc = sess->aux_data;
+#endif
+
+ if (args->status != AIM_RENDEZVOUS_PROPOSE)
+ return 1;
+#if 0
+ if (args->reqclass & AIM_CAPS_CHAT) {
+ char *name = extract_name(args->info.chat.roominfo.name);
+ int *exch = g_new0(int, 1);
+ GList *m = NULL;
+ 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);
+ serv_got_chat_invite(gc,
+ name ? name : args->info.chat.roominfo.name,
+ userinfo->sn,
+ (char *)args->msg,
+ m);
+ if (name)
+ g_free(name);
+ }
+#endif
+ return 1;
+}
+
+static void gaim_icq_authgrant(gpointer w, struct icq_auth *data) {
+ char *uin, message;
+ struct oscar_data *od = (struct oscar_data *)data->gc->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);
+ show_got_added(data->gc, NULL, uin, NULL, NULL);
+
+ g_free(uin);
+ g_free(data);
+}
+
+static void gaim_icq_authdeny(gpointer w, struct icq_auth *data) {
+ char *uin, *message;
+ struct oscar_data *od = (struct oscar_data *)data->gc->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 gaim_connection *gc, guint32 uin, char *msg) {
+ struct icq_auth *data = g_new(struct icq_auth, 1);
+ char *reason = NULL;
+ char *dialog_msg;
+
+ 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:\n\n%s", uin, reason ? reason : "No reason given.");
+ data->gc = gc;
+ data->uin = uin;
+ do_ask_dialog(gc, 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 gaim_connection *gc = 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);
+ serv_got_im(gc, uin, message, 0, time(NULL), -1);
+ 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);
+ serv_got_im(gc, uin, message, 0, time(NULL), -1);
+ g_free(uin);
+ g_free(m);
+ g_free(message);
+ } break;
+
+ case 0x0006: { /* Someone requested authorization */
+ gaim_icq_authask(gc, args->uin, args->msg);
+ } break;
+
+ case 0x0007: { /* Someone has denied you authorization */
+ serv_got_crap(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 */
+ serv_got_crap(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;
+}
+/*
+int handle_cmp_aim(const char * a, const char * b) {
+ return handle_cmp(a, b, PROTO_TOC);
+}
+*/
+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 *);
+
+ if (set_getint(sess->aux_data, "debug")) {
+ serv_got_crap(sess->aux_data, "channel %i called", channel);
+ }
+
+ 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;
+ char buf[1024];
+
+ 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) */
+ g_snprintf(buf,
+ sizeof(buf),
+ 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 */
+ g_snprintf(buf,
+ sizeof(buf),
+ 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 */
+ g_snprintf(buf,
+ sizeof(buf),
+ 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 */
+ g_snprintf(buf,
+ sizeof(buf),
+ 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 */
+ g_snprintf(buf,
+ sizeof(buf),
+ 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:
+ g_snprintf(buf,
+ sizeof(buf),
+ nummissed == 1 ?
+ _("You missed %d message from %s for unknown reasons.") :
+ _("You missed %d messages from %s for unknown reasons."),
+ nummissed,
+ userinfo->sn);
+ break;
+ }
+ do_error_dialog(sess->aux_data, buf, _("Gaim - Error"));
+
+ return 1;
+}
+
+static int gaim_parse_genericerr(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ guint16 reason;
+ char *m;
+
+ va_start(ap, fr);
+ reason = (guint16)va_arg(ap, unsigned int);
+ va_end(ap);
+
+ m = g_strdup_printf(_("SNAC threw error: %s\n"),
+ reason < msgerrreasonlen ? msgerrreason[reason] : "Unknown error");
+ do_error_dialog(sess->aux_data, m, _("Gaim - Oscar SNAC Error"));
+ g_free(m);
+
+ return 1;
+}
+
+static int gaim_parse_msgerr(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ char *destn;
+ guint16 reason;
+ char buf[1024];
+
+ va_start(ap, fr);
+ reason = (guint16)va_arg(ap, unsigned int);
+ destn = va_arg(ap, char *);
+ va_end(ap);
+
+ sprintf(buf, _("Your message to %s did not get sent: %s"), destn,
+ (reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown"));
+ do_error_dialog(sess->aux_data, buf, _("Gaim - Error"));
+
+ return 1;
+}
+
+static int gaim_parse_locerr(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ char *destn;
+ guint16 reason;
+ char buf[1024];
+
+ va_start(ap, fr);
+ reason = (guint16)va_arg(ap, unsigned int);
+ destn = va_arg(ap, char *);
+ va_end(ap);
+
+ sprintf(buf, _("User information for %s unavailable: %s"), destn,
+ (reason < msgerrreasonlen) ? msgerrreason[reason] : _("Reason unknown"));
+ do_error_dialog(sess->aux_data, buf, _("Gaim - Error"));
+
+
+ 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)
+ do_error_dialog(sess->aux_data, _("Your connection may be lost."),
+ _("AOL error"));
+
+ return 1;
+}
+
+static int gaim_chatnav_info(aim_session_t *sess, aim_frame_t *fr, ...) {
+ va_list ap;
+ guint16 type;
+ struct gaim_connection *gc = sess->aux_data;
+ struct oscar_data *odata = (struct oscar_data *)gc->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 gaim_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++)
+ add_chat_buddy(c->cnv, 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 gaim_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++)
+ remove_chat_buddy(c->cnv, 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 gaim_connection *gc = sess->aux_data;
+ struct chat_connection *ccon = find_oscar_chat_by_conn(gc, 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 gaim_connection *gc = sess->aux_data;
+ struct chat_connection *ccon = find_oscar_chat_by_conn(gc, 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);
+ serv_got_chat_in(gc, ccon->id, info->sn, 0, tmp, time((time_t)NULL));
+ 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) {
+ do_error_dialog(sess->aux_data, _("The last message was not sent because you are over the rate limit. "
+ "Please wait 10 seconds and try again."), _("Gaim - Error"));
+ 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 gaim_connection *gc = sess->aux_data;
+
+ va_start(ap, fr);
+ info = va_arg(ap, aim_userinfo_t *);
+ va_end(ap);
+
+ gc->evil = info->warnlevel/10;
+ /* gc->correction_time = (info->onlinesince - gc->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 gaim_connection *gc = sess->aux_data;
+ struct oscar_data *od = gc->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->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 gaim_connection *gc = sess->aux_data;
+ struct oscar_data *odata = (struct oscar_data *)gc->proto_data;
+
+ va_start(ap, fr);
+ maxsiglen = va_arg(ap, int);
+ va_end(ap);
+
+ odata->rights.maxsiglen = odata->rights.maxawaymsglen = (guint)maxsiglen;
+
+ aim_bos_setprofile(sess, fr->conn, gc->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 gaim_connection *gc = sess->aux_data;
+ struct oscar_data *odata = (struct oscar_data *)gc->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 gaim_connection *gc = sess->aux_data;
+ struct oscar_data *odata = (struct oscar_data *)gc->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;
+
+// serv_finish_login(gc);
+
+ if (bud_list_cache_exists(gc))
+ do_import(gc, NULL);
+
+ 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 gaim_connection *gc = 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);
+ serv_got_im(gc, sender, dialog_msg, 0, t, -1);
+ 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);
+ serv_got_im(gc, sender, dialog_msg, 0, t, -1);
+ g_free(dialog_msg);
+ g_free(m);
+ } break;
+
+ case 0x0006: { /* Authorization request */
+ gaim_icq_authask(gc, msg->sender, msg->msg);
+ } break;
+
+ case 0x0007: { /* Someone has denied you authorization */
+ serv_got_crap(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 */
+ serv_got_crap(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 gaim_connection *gc) {
+ struct oscar_data *odata = (struct oscar_data *)gc->proto_data;
+ aim_flap_nop(odata->sess, odata->conn);
+}
+
+static int oscar_send_im(struct gaim_connection *gc, char *name, char *message, int len, int imflags) {
+ struct oscar_data *odata = (struct oscar_data *)gc->proto_data;
+ int ret = 0;
+ if (imflags & IM_FLAG_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 gaim_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 gaim_connection *g, char *who) {
+ struct oscar_data *odata = (struct oscar_data *)g->proto_data;
+ if (odata->icq) {
+ struct buddy *budlight = 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 gaim_connection *gc, struct oscar_data *od, const char *state, const char *message)
+{
+
+ 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 (od->rights.maxawaymsglen == 0)
+ do_error_dialog(gc, "oscar_set_away_aim called before locate rights received", "Protocol Error");
+
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL);
+
+ if (gc->away)
+ g_free(gc->away);
+ gc->away = NULL;
+
+ if (!message) {
+ aim_bos_setprofile(od->sess, od->conn, NULL, "", gaim_caps);
+ return;
+ }
+
+ if (strlen(message) > od->rights.maxawaymsglen) {
+ gchar *errstr;
+
+ errstr = g_strdup_printf("Maximum away message length of %d bytes exceeded, truncating", od->rights.maxawaymsglen);
+
+ do_error_dialog(gc, errstr, "Away Message Too Long");
+
+ g_free(errstr);
+ }
+
+ gc->away = g_strndup(message, od->rights.maxawaymsglen);
+ aim_bos_setprofile(od->sess, od->conn, NULL, gc->away, gaim_caps);
+
+ return;
+}
+
+static void oscar_set_away_icq(struct gaim_connection *gc, struct oscar_data *od, const char *state, const char *message)
+{
+ const char *msg = NULL;
+ gboolean no_message = FALSE;
+
+ /* clean old states */
+ if (gc->away) {
+ g_free(gc->away);
+ gc->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 (!g_strcasecmp(state, "Online")) {
+ 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);
+ gc->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);
+ gc->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);
+ gc->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);
+ gc->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);
+ gc->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);
+ gc->away = g_strdup(msg);
+ } else if (!g_strcasecmp(state, GAIM_AWAY_CUSTOM)) {
+ if (no_message) {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_NORMAL);
+ } else {
+ aim_setextstatus(od->sess, od->conn, AIM_ICQ_STATE_AWAY);
+ gc->away = g_strdup(msg);
+ od->sess->aim_icq_state = AIM_MTYPE_AUTOAWAY;
+ }
+ }
+
+ return;
+}
+
+static void oscar_set_away(struct gaim_connection *gc, char *state, char *message)
+{
+ struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+
+ oscar_set_away_aim(gc, od, state, message);
+ if (od->icq)
+ oscar_set_away_icq(gc, od, state, message);
+
+ return;
+}
+
+static void oscar_add_buddy(struct gaim_connection *g, char *name) {
+ struct oscar_data *odata = (struct oscar_data *)g->proto_data;
+ aim_ssi_addbuddies(odata->sess, odata->conn, OSCAR_GROUP, &name, 1, 0);
+}
+
+static void oscar_remove_buddy(struct gaim_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 gaim_connection *gc = sess->aux_data;
+ struct aim_ssi_item *curitem;
+ int tmp;
+
+ /* Add from server list to local list */
+ tmp = 0;
+ for (curitem=sess->ssi.items; curitem; curitem=curitem->next) {
+ switch (curitem->type) {
+ case 0x0000: /* Buddy */
+ if ((curitem->name) && (!find_buddy(gc, curitem->name))) {
+ char *realname = NULL;
+
+ if (curitem->data && aim_gettlv(curitem->data, 0x0131, 1))
+ realname = aim_gettlv_str(curitem->data, 0x0131, 1);
+
+ add_buddy(gc, NULL, curitem->name, realname);
+
+ if (realname)
+ g_free(realname);
+ }
+ break;
+
+ case 0x0002: /* Permit buddy */
+ if (curitem->name) {
+ GSList *list;
+ for (list=gc->permit; (list && aim_sncmp(curitem->name, list->data)); list=list->next);
+ if (!list) {
+ char *name;
+ name = g_strdup(normalize(curitem->name));
+ gc->permit = g_slist_append(gc->permit, name);
+ build_allow_list();
+ tmp++;
+ }
+ }
+ break;
+
+ case 0x0003: /* Deny buddy */
+ if (curitem->name) {
+ GSList *list;
+ for (list=gc->deny; (list && aim_sncmp(curitem->name, list->data)); list=list->next);
+ if (!list) {
+ char *name;
+ name = g_strdup(normalize(curitem->name));
+ gc->deny = g_slist_append(gc->deny, name);
+ build_block_list();
+ tmp++;
+ }
+ }
+ break;
+
+ case 0x0004: /* Permit/deny setting */
+ if (curitem->data) {
+ guint8 permdeny;
+ if ((permdeny = aim_ssi_getpermdeny(sess->ssi.items)) && (permdeny != gc->permdeny)) {
+ gc->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 */
+
+ if (tmp)
+ do_export(gc);
+ 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. */
+ account_online(gc);
+
+ 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... */
+ do_error_dialog( sess->aux_data, "Received SSI ACK package with non-even length", "Gaim - Error" );
+ return( 0 );
+ }
+ count >>= 1;
+
+ list = (char *) origsnac->data;
+ for( i = 0; i < count; i ++ )
+ {
+ st = aimbs_get16( &fr->data );
+ if( st == 0x0E )
+ {
+ serv_got_crap( 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 );
+ }
+ list += strlen( list ) + 1;
+ }
+ }
+
+ return( 1 );
+}
+
+static void oscar_set_permit_deny(struct gaim_connection *gc) {
+ struct oscar_data *od = (struct oscar_data *)gc->proto_data;
+ if (od->icq) {
+ GSList *list;
+ char buf[MAXMSGLEN];
+ int at;
+
+ switch(gc->permdeny) {
+ case 1:
+ aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_DENYADD, gc->username);
+ break;
+ case 2:
+ aim_bos_changevisibility(od->sess, od->conn, AIM_VISIBILITYCHANGE_PERMITADD, gc->username);
+ break;
+ case 3:
+ list = gc->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 = gc->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;
+ }
+ signoff_blocked(gc);
+ } else {
+ if (od->sess->ssi.received_data)
+ aim_ssi_setpermdeny(od->sess, od->conn, gc->permdeny, 0xffffffff);
+ }
+}
+
+static void oscar_add_permit(struct gaim_connection *gc, char *who) {
+ struct oscar_data *od = (struct oscar_data *)gc->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 gaim_connection *gc, char *who) {
+ struct oscar_data *od = (struct oscar_data *)gc->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 gaim_connection *gc, char *who) {
+ struct oscar_data *od = (struct oscar_data *)gc->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 gaim_connection *gc, char *who) {
+ struct oscar_data *od = (struct oscar_data *)gc->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 gaim_connection *gc)
+{
+ struct oscar_data *od = gc->proto_data;
+ GList *m = NULL;
+
+ if (!od->icq)
+ return g_list_append(m, GAIM_AWAY_CUSTOM);
+
+ m = g_list_append(m, "Online");
+ 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;
+}
+
+static int gaim_icqinfo(aim_session_t *sess, aim_frame_t *fr, ...)
+{
+ struct gaim_connection *gc = sess->aux_data;
+ gchar who[16];
+ GString *str;
+ va_list ap;
+ struct aim_icq_info *info;
+
+ 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(100);
+ g_snprintf(who, sizeof(who), "%u", info->uin);
+
+ g_string_sprintfa(str, "%s: %s - %s: %s", _("UIN"), who, _("Nick"),
+ info->nick ? info->nick : "-");
+ info_string_append(str, "\n", _("First Name"), info->first);
+ info_string_append(str, "\n", _("Last Name"), info->last);
+ info_string_append(str, "\n", _("Email Address"), info->email);
+ if (info->numaddresses && info->email2) {
+ int i;
+ for (i = 0; i < info->numaddresses; i++) {
+ info_string_append(str, "\n", _("Email Address"), info->email2[i]);
+ }
+ }
+ info_string_append(str, "\n", _("Mobile Phone"), info->mobile);
+ info_string_append(str, "\n", _("Gender"), info->gender==1 ? _("Female") : _("Male"));
+ if (info->birthyear || info->birthmonth || info->birthday) {
+ char date[30];
+ struct tm tm;
+ tm.tm_mday = (int)info->birthday;
+ tm.tm_mon = (int)info->birthmonth-1;
+ tm.tm_year = (int)info->birthyear-1900;
+ strftime(date, sizeof(date), "%Y-%m-%d", &tm);
+ info_string_append(str, "\n", _("Birthday"), date);
+ }
+ if (info->age) {
+ char age[5];
+ g_snprintf(age, sizeof(age), "%hhd", info->age);
+ info_string_append(str, "\n", _("Age"), age);
+ }
+ info_string_append(str, "\n", _("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_sprintfa(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_sprintfa(str, "%s:", _("Home Address"));
+ info_string_append(str, "\n", _("Address"), info->homeaddr);
+ info_string_append(str, "\n", _("City"), info->homecity);
+ info_string_append(str, "\n", _("State"), info->homestate);
+ info_string_append(str, "\n", _("Zip Code"), info->homezip);
+ g_string_sprintfa(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_sprintfa(str, "%s:", _("Work Address"));
+ info_string_append(str, "\n", _("Address"), info->workaddr);
+ info_string_append(str, "\n", _("City"), info->workcity);
+ info_string_append(str, "\n", _("State"), info->workstate);
+ info_string_append(str, "\n", _("Zip Code"), info->workzip);
+ g_string_sprintfa(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_sprintfa(str, "%s:", _("Work Information"));
+ info_string_append(str, "\n", _("Company"), info->workcompany);
+ info_string_append(str, "\n", _("Division"), info->workdivision);
+ info_string_append(str, "\n", _("Position"), info->workposition);
+ if (info->workwebpage && info->workwebpage[0]) {
+ info_string_append(str, "\n", _("Web Page"), info->workwebpage);
+ }
+ g_string_sprintfa(str, "\n");
+ }
+
+ serv_got_crap(gc, "%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 gaim_connection *gc = 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);
+ serv_got_crap(gc, "%s: %s", _("Idle Time"), buff);
+ }
+
+ if(text) {
+ utf8 = oscar_encoding_to_utf8(extracted_encoding, text, text_length);
+ serv_got_crap(gc, "%s\n%s", _("User Info"), utf8);
+ } else {
+ serv_got_crap(gc, _("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);
+ serv_got_crap(gc, "%s\n%s", _("Away Message"), utf8);
+ }
+
+ g_free(utf8);
+
+ return 1;
+}
+
+static char *oscar_get_status_string( struct gaim_connection *gc, int number )
+{
+ struct oscar_data *od = gc->proto_data;
+
+ if( ! number & UC_UNAVAILABLE )
+ {
+ return( NULL );
+ }
+ else if( od->icq )
+ {
+ number >>= 7;
+ if( number & AIM_ICQ_STATE_DND )
+ return( "Do Not Disturb" );
+ else if( number & AIM_ICQ_STATE_OUT )
+ return( "Not Available" );
+ else if( number & AIM_ICQ_STATE_BUSY )
+ return( "Occupied" );
+ else if( number & AIM_ICQ_STATE_INVISIBLE )
+ return( "Invisible" );
+ else
+ return( "Away" );
+ }
+ else
+ {
+ return( "Away" );
+ }
+}
+
+static struct prpl *my_protocol = NULL;
+
+void oscar_init(struct prpl *ret) {
+ ret->protocol = PROTO_OSCAR;
+ ret->away_states = oscar_away_states;
+ ret->login = oscar_login;
+ ret->close = oscar_close;
+ ret->send_im = oscar_send_im;
+ 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->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->keepalive = oscar_keepalive;
+ ret->get_status_string = oscar_get_status_string;
+
+ my_protocol = ret;
+}
diff --git a/protocols/oscar/oscar_util.c b/protocols/oscar/oscar_util.c
new file mode 100644
index 00000000..ed8409a4
--- /dev/null
+++ b/protocols/oscar/oscar_util.c
@@ -0,0 +1,167 @@
+/*
+ *
+ *
+ *
+ */
+
+#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) NULL) {
+ 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) NULL) && (*curPtr2 != (char) NULL) ) {
+ 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..eb0898ec
--- /dev/null
+++ b/protocols/oscar/rxhandlers.c
@@ -0,0 +1,408 @@
+/*
+ * 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 */
+ }
+
+ if (set_getint(sess->aux_data, "debug")) {
+ serv_got_crap(sess->aux_data, "snac %x/%x received", snac.family, snac.subtype);
+ }
+
+ 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;
+
+ /*
+ * This is a debugging/sanity check only and probably
+ * could/should be removed for stable code.
+ */
+ if (((cur->hdrtype == AIM_FRAMETYPE_OFT) &&
+ (cur->conn->type != AIM_CONN_TYPE_RENDEZVOUS)) ||
+ ((cur->hdrtype == AIM_FRAMETYPE_FLAP) &&
+ (cur->conn->type == AIM_CONN_TYPE_RENDEZVOUS))) {
+ do_error_dialog(sess->aux_data, "incompatible frame type/connection type combination", "Gaim");
+ cur->handled = 1;
+ continue;
+ }
+
+ if (cur->conn->type == AIM_CONN_TYPE_RENDEZVOUS) {
+ if (cur->hdrtype != AIM_FRAMETYPE_OFT) {
+ do_error_dialog(sess->aux_data, "non-OFT frames on OFT connection", "Gaim");
+ cur->handled = 1; /* get rid of it */
+ } else {
+ /* FIXME: implement this (OFT frame) */
+ cur->handled = 1; /* get rid of it */
+ }
+ continue;
+ }
+
+ if (cur->conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) {
+ /* not possible */
+ do_error_dialog(sess->aux_data, "RENDEZVOUS packet in rxqueue", "Gaim");
+ cur->handled = 1;
+ 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..d95bc026
--- /dev/null
+++ b/protocols/oscar/rxqueue.c
@@ -0,0 +1,508 @@
+/*
+ * 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 */
+
+ if (frame->hdrtype == AIM_FRAMETYPE_OFT)
+ g_free(frame->hdr.oft.hdr2);
+ g_free(frame);
+
+ return;
+}
+
+
+/*
+ * 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 */
+
+ if (conn->fd < 3) /* can happen when people abuse the interface */
+ return 0;
+
+ if (conn->status & AIM_CONN_STATUS_INPROGRESS)
+ return aim_conn_completeconnect(sess, conn);
+
+ /*
+ * Rendezvous (client-client) connections do not speak
+ * FLAP, so this function will break on them.
+ */
+ if (conn->type == AIM_CONN_TYPE_RENDEZVOUS)
+ return aim_get_command_rendezvous(sess, conn);
+ else if (conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) {
+ do_error_dialog(sess->aux_data,"AIM_CONN_TYPE_RENDEZVOUS_OUT shouldn't use FLAP", "Gaim");
+ return 0;
+ }
+
+ 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);
+ do_error_dialog(sess->aux_data, "FLAP framing disrupted", "Gaim");
+ 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..9685a3d1
--- /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))) {
+ do_error_dialog(sess->aux_data, "couldn't get snac", "Gaim");
+ 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..875a2eb0
--- /dev/null
+++ b/protocols/oscar/service.c
@@ -0,0 +1,946 @@
+/*
+ * 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);
+
+ do_error_dialog(sess->aux_data, "bifurcated migration unsupported", "Gaim");
+ }
+
+ 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;
+
+ data = AIM_ICQ_STATE_WEBAWARE | AIM_ICQ_STATE_HIDEIP | status; /* yay for error checking ;^) */
+
+ tlvlen = aim_addtlvtochain32(&tl, 0x0006, data);
+
+ printf("%d\n", tlvlen);
+
+ 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
+
+ } 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
+ do_error_dialog(sess->aux_data, "WARNING: unknown hash request", "Gaim");
+
+ }
+
+ 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..e2bac179
--- /dev/null
+++ b/protocols/oscar/snac.c
@@ -0,0 +1,145 @@
+/*
+ *
+ * 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>
+
+/*
+ * 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.
+ */
+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..76b5b427
--- /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")*sizeof(char));
+ 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..74d177ad
--- /dev/null
+++ b/protocols/oscar/tlv.c
@@ -0,0 +1,600 @@
+#include <aim.h>
+
+static aim_tlv_t *createtlv(void)
+{
+ return g_new0(aim_tlv_t, 1);
+}
+
+static void freetlv(aim_tlv_t **oldtlv)
+{
+
+ if (!oldtlv || !*oldtlv)
+ return;
+
+ g_free((*oldtlv)->value);
+ g_free(*oldtlv);
+ *oldtlv = NULL;
+
+ return;
+}
+
+/**
+ * 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);
+
+#if 0 /* temporarily disabled until I know if they're still doing it or not */
+ /*
+ * Okay, so now AOL has decided that any TLV of
+ * type 0x0013 can only be two bytes, despite
+ * what the actual given length is. So here
+ * we dump any invalid TLVs of that sort. Hopefully
+ * theres no special cases to this special case.
+ * - mid (30jun2000)
+ */
+ if ((type == 0x0013) && (length != 0x0002))
+ length = 0x0002;
+#else
+ if (0)
+ ;
+#endif
+ else {
+
+ cur = g_new0(aim_tlvlist_t, 1);
+
+ cur->tlv = createtlv();
+ 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 = createtlv())) {
+ 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;
+}
+
+/**
+ * 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..51415089
--- /dev/null
+++ b/protocols/oscar/txqueue.c
@@ -0,0 +1,440 @@
+/*
+ * 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) {
+ do_error_dialog(sess->aux_data, "no connection specified", "Gaim");
+ return NULL;
+ }
+
+ /* For sanity... */
+ if ((conn->type == AIM_CONN_TYPE_RENDEZVOUS) ||
+ (conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT)) {
+ if (framing != AIM_FRAMETYPE_OFT) {
+ do_error_dialog(sess->aux_data, "attempted to allocate inappropriate frame type for rendezvous connection", "Gaim");
+ return NULL;
+ }
+ } else {
+ if (framing != AIM_FRAMETYPE_FLAP) {
+ do_error_dialog(sess->aux_data, "attempted to allocate inappropriate frame type for FLAP connection", "Gaim");
+ 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 if (fr->hdrtype == AIM_FRAMETYPE_OFT) {
+
+ fr->hdr.oft.type = chan;
+ fr->hdr.oft.hdr2len = 0; /* this will get setup by caller */
+
+ } else
+ do_error_dialog(sess->aux_data, "unknown framing", "Gaim");
+
+ 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) {
+ do_error_dialog(sess->aux_data, "WARNING: enqueueing packet with no connection", "Gaim");
+ 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) {
+ do_error_dialog(sess->aux_data, "packet has no connection", "Gaim");
+ 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 ((conn->type == AIM_CONN_TYPE_RENDEZVOUS) &&
+ (conn->subtype == AIM_CONN_SUBTYPE_OFT_DIRECTIM)) {
+ /* I strongly suspect that this is a horrible thing to do
+ * and I feel really guilty doing it. */
+ const char *sn = aim_directim_getsn(conn);
+ aim_rxcallback_t userfunc;
+ while (count - wrote > 1024) {
+ wrote = wrote + aim_send(conn->fd, bs->data + bs->offset + wrote, 1024);
+ if ((userfunc=aim_callhandler(conn->sessv, conn,
+ AIM_CB_FAM_SPECIAL,
+ AIM_CB_SPECIAL_IMAGETRANSFER)))
+ userfunc(conn->sessv, NULL, sn,
+ count-wrote>1024 ? ((double)wrote / count) : 1);
+ }
+ }
+ 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;
+}
+
+static int sendframe_oft(aim_session_t *sess, aim_frame_t *fr)
+{
+ aim_bstream_t hbs;
+ guint8 *hbs_raw;
+ int hbslen;
+ int err = 0;
+
+ hbslen = 8 + fr->hdr.oft.hdr2len;
+ if (!(hbs_raw = g_malloc(hbslen)))
+ return -1;
+
+ aim_bstream_init(&hbs, hbs_raw, hbslen);
+
+ aimbs_putraw(&hbs, fr->hdr.oft.magic, 4);
+ aimbs_put16(&hbs, fr->hdr.oft.hdr2len + 8);
+ aimbs_put16(&hbs, fr->hdr.oft.type);
+ aimbs_putraw(&hbs, fr->hdr.oft.hdr2, fr->hdr.oft.hdr2len);
+
+ aim_bstream_rewind(&hbs);
+
+
+ if (aim_bstream_send(&hbs, fr->conn, hbslen) != hbslen) {
+
+ err = -errno;
+
+ } else if (aim_bstream_curpos(&fr->data)) {
+ int len;
+
+ len = aim_bstream_curpos(&fr->data);
+ aim_bstream_rewind(&fr->data);
+
+ if (aim_bstream_send(&fr->data, fr->conn, len) != len)
+ err = -errno;
+ }
+
+ g_free(hbs_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);
+ else if (fr->hdrtype == AIM_FRAMETYPE_OFT)
+ return sendframe_oft(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;
+}
+
+