From 37472dad1f00d37122bdb79ff786495d8bd894d3 Mon Sep 17 00:00:00 2001 From: Packit Service Date: Dec 09 2020 21:54:07 +0000 Subject: meanwhile-1.1.0 base --- diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..2130d93 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,21 @@ +aclocal.m4 +autom4te.cache +compile +config.* +configure +configwrap +depcomp +install-sh +libtool +Makefile +Makefile.in +missing +mkinstalldirs +*.pc +*.rpm +*.spec +*.swp +*.tar.gz +test* +tmp + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..6579e0f --- /dev/null +++ b/AUTHORS @@ -0,0 +1,16 @@ + + +Maintainer: +Christopher (siege) O'Brien + + +Packager: +Stepher Dawkins + + +Patches: +Mikael B. +Jeremy Kerr +Richard Laager +Torrey McMahon +Chris Ross diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..92b8903 --- /dev/null +++ b/COPYING @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 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 library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + 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 Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the 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 a program 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. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + 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, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +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 compile 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) 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. + + c) 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. + + d) 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 source code 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 to +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 Library 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + 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. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/CVS/Entries b/CVS/Entries new file mode 100644 index 0000000..89bf941 --- /dev/null +++ b/CVS/Entries @@ -0,0 +1,16 @@ +/.cvsignore/1.11/Mon Jun 27 05:20:21 2005//Tmeanwhile_v1_1_0 +/AUTHORS/1.5.2.1/Thu Jan 3 20:47:00 2008//Tmeanwhile_v1_1_0 +/COPYING/1.1/Tue Jun 1 23:24:52 2004//Tmeanwhile_v1_1_0 +/ChangeLog/1.58.2.3/Thu Jan 3 20:47:24 2008//Tmeanwhile_v1_1_0 +/HACKING/1.3/Tue Sep 28 08:18:10 2004//Tmeanwhile_v1_1_0 +/INSTALL/1.2/Sun Jun 27 09:08:30 2004//Tmeanwhile_v1_1_0 +/LICENSE/1.1/Tue Jun 1 23:24:52 2004//Tmeanwhile_v1_1_0 +/Makefile.am/1.11/Sat Oct 29 01:35:24 2005//Tmeanwhile_v1_1_0 +/NEWS/1.1/Tue Jun 1 23:24:52 2004//Tmeanwhile_v1_1_0 +/README/1.3/Tue Sep 28 08:18:10 2004//Tmeanwhile_v1_1_0 +/TODO/1.13/Fri Oct 7 17:43:02 2005//Tmeanwhile_v1_1_0 +/autogen.sh/1.10/Mon Jun 27 05:20:21 2005//Tmeanwhile_v1_1_0 +/configure.ac/1.67.2.1/Thu Jan 3 20:42:22 2008//Tmeanwhile_v1_1_0 +/meanwhile.pc.in/1.8/Wed Sep 21 23:25:09 2005//Tmeanwhile_v1_1_0 +/meanwhile.spec.in/1.14/Sat Nov 19 04:57:54 2005//Tmeanwhile_v1_1_0 +D diff --git a/CVS/Entries.Log b/CVS/Entries.Log new file mode 100644 index 0000000..4d83b28 --- /dev/null +++ b/CVS/Entries.Log @@ -0,0 +1,5 @@ +A D/doc//// +A D/pixmaps//// +A D/samples//// +A D/src//// +R D/pixmaps//// diff --git a/CVS/Repository b/CVS/Repository new file mode 100644 index 0000000..a609058 --- /dev/null +++ b/CVS/Repository @@ -0,0 +1 @@ +meanwhile diff --git a/CVS/Root b/CVS/Root new file mode 100644 index 0000000..7717627 --- /dev/null +++ b/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@meanwhile.cvs.sourceforge.net:/cvsroot/meanwhile diff --git a/CVS/Tag b/CVS/Tag new file mode 100644 index 0000000..112c41e --- /dev/null +++ b/CVS/Tag @@ -0,0 +1 @@ +Tmeanwhile_v1_1_0 diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..daecd62 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,187 @@ +version 1.1.0 (2008-01-04) + + Bug Fixes + - Bug #1576187 Errant return in cipher.c + - Bug #1626349 Buddy list shows wrong status + - Patch #1863383 strict-prototypes warning fix + + Features + - added flag in status enum for mobile users + +version 1.0.2 (2005-12-29) + + Bug Fixes + - fix error 0x8000001f when inviting Sametime Connect user to a + chat (stopped offering encryption on conferences) + - Bug #1390997 crash when accepting conference invite + +version 1.0.1 (2005-12-16) + + Bug Fixes + - fix C++ ifdefs in headers, removed C++ keywords in variable + names + - Bug #1379550 meanwhile 1.0.0, login verification unavailable + error + +version 1.0.0 (2005-12-13) + + - using shared library versioning, 1:0:0 + - using MPI code from http://www.cs.dartmouth.edu/~sting/mpi/ as a + replacement for the GMP dependancy + + Features + - support for sending legacy invitations via Place service + - Bug #1352944 Gaim crashes when I try to logon to Sametime + + Bug Fixes + - Crash and other problems with joining a Place + +version 0.5.0 (2005-10-28) + + - added meanwhile-doc package, with html and latex docs and + sample code + + Features + - Automatically breaks up large messages for transport (using + NotesBuddy multi-segment messages) + - Support for DH RC2/128 channel encryption + - Support for DH RC2/128 login encryption + - RFE #1307892 Support for Announcements + - Support for chats via the Places service + + Bug Fixes + - Bug #1239210 Unspecified fatal error when VPN disconnects + - Fixed memleaks and crashes in the aware service (which needs + to be re-written sometime soon) + +version 0.4.2 (2005-05-27) + + Features + - RFE #1054813 Privacy support + - Support for updates from attribute deletes in aware service + + Bug Fixes + - Bug #1194631 Windows file transfer crashes gaim + - Bug #1200841 Sametime 2.0 clients responses not seen + +version 0.4.1 (2005-05-01) + + Features + - Support for File Transfers (mwServiceFileTransfer) + - Support for getting/setting Aware attributes + - Support sending the LoginCont message in response to a + LoginRedirect + - Support for multi-segment messages from NotesBuddy (fixes + problems with large MIME, text, and HTML messages) + + Bug Fixes + - Bug #1192747 Crash when someone initiate talk with me + - Bug #1168421 crash when opening specific contacts + - Problem with NAB group members not being announced after the + initial group status update + +version 0.4.0 (2005-03-18) + + - installed header file names have changed to now have a "mw_" + prefix + + Features + - RFE #994716 Support for Server-side Groups to Aware Service + - RFE #1077794 NotesBuddy style formatting + - Support for resolve service + + Bug Fixes + - Bug #1008335 Error in common.c when compiling on x86_64 + - Bug #1021353 meanwhile won't build w/Sun GNOME2 + - Bug #1036056 meanwhile-CRITICAL - when add a buddy to my list + - Bug #1089971 pkg-config file does not list liner flags + - Bug #1110902 configure shouldn't set CFLAGS + - Dumb crash when removing non-existant members from an aware list + +version 0.3.1 (2004-08-05) + + - Fix [989872] Version incompatibility (0x80000200 errors) + - Fix some st_list parsing problems + +version 0.3.0 (2004-06-28) + + 2004-06-27: Fixed bug #980572. Thanks a lot to Joe Marcus Clarke + for all the debugging help! + + * common.c: privacy list has a name in it, who knew? + + * st_list.c: revamped the parsing and generation of sametime's + buddy list format. Seems to be working alright, but there can be + some serious ugliness with commas in user ids and in aliases. + + 2004-06-23: Sametime blist format parser/generator seems to be + working. + + * common.c: fixed a nasty crash/bug discovered by nosnilmot. Base + type serialization wasn't correctly checking available buffer + lengths, leading to unhealthy overruns. Won't happen anymore. + + * srvc_aware.c: fixed nasty bug in removing entries from a list + which made the whole service unstable. + + * st_list.c, st_list.h: added to CVS. Simple API for loading and + storing a heirarchy of group/users into a sametime format list. + + 2004-06-21: Bug fix and API cleanup. + + * service.c, service.h: refinements to service start/stop and + state checking + + * srvc_im.c: fixed a reported bug where messages were not being + rejected even when the user was in mwStatus_BUSY. Thanks jdob! + + 2004-06-18: Hopefully fixed bug #968973, requires testing by + someone normally afflicted + + * channel.c, channel.h: added mwChannel_markActive + + * service.c, service.h: added start/stop, as well as related state + tracking calls and utility functions + + * session.c: effectively rewrote mwSession_recv + + * srvc_store.c, srvc_store.h: added to CVS + + 2004-06-15: Reworking services to follow a start/stop system. + Moving service structures into a non-visible space. Added support + for the storage service. Meanwhile now has its own client type + identifier, 0x1700, and defaults to using that rather than the + Java App identifier of 0x1003 + +version 0.2 Chris O'Brien + + 2004-05-17: Refactoring service and channel related code out of + session.[ch], started using g_return_* functions. Moved + meanwhile-gaim plugin into its own module, for my own sanity. + +version 0.1 Chris O'Brien + + 2004-04-23: Release against Gaim 0.77, tagged v0_77 + + 2004-04-20: Changed buddy list status text to be their message + rather than the name of their state. If there's no status + message, it still falls back to the state name. Updates to match + Gaim 0.77cvs. Chat rejection should be working now; Admin + messages might work. Difficult to test as only admins can send + those messages. + + 2004-04-14: Added meanwhile-gaim.spec, tagged it into v0_76. + First rpm released + + 2004-04-05: Release against Gaim 0.76, tagged v0_76 + + 2004-02-25: Added Features: create a conference with another user. + This seems to work with pretty much any client on the receiving + end; accept a conference from another user. This seems to work + with only some clients. eg: Sanity works, ICT doesn't. + + 2004-02-23: First import to IIOSB CVS + + Features: working buddy list; status and away messages; idle + timer; send/receive im; encryption support; lovable icon + diff --git a/HACKING b/HACKING new file mode 100644 index 0000000..561a142 --- /dev/null +++ b/HACKING @@ -0,0 +1,55 @@ + +Hacking on the Meanwhile Project +- a brief guide by siege + + + +Table of Contents + 1 Coding Style + 1.1 Indentation and Line Width + 1.2 Block Style + 1.3 Function and Variable Naming + + 2 Abbreviated API Guide + 2.1 Sessions + 2.2 Messages + 2.2 Channels + 2.3 Services + + 3 Patch Submission + + + +1 Coding Style +In general, just make it look pretty. + +1.1 Indentation and Line Width +two-space tabs/indent, < 80 char wide lines, with the continuation +under the last containing statement. + +1.2 Block Style +sexy braces on the defining line, not after + +1.3 Function and Variable Naming +mwTypeName_someAction + + +2 Abbreviated API Guide + +2.1 Sessions + +2.2 Messages + +2.3 Channels + +2.4 Services + + +3 Patch Submission + +Patches must be submitted through the sf.net tracker. Patches must be +in the `diff -ud` format, and should be relative to the base sandbox +directory (the directory containing this file). One topic per patch +submission, eg: no bugfixes along with new features (unless they are +mutually inclusive). + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..54caf7c --- /dev/null +++ b/INSTALL @@ -0,0 +1,229 @@ +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software +Foundation, Inc. + + This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the `--target=TYPE' option to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +will cause the specified gcc to be used as the C compiler (unless it is +overridden in the site shell script). + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..92b8903 --- /dev/null +++ b/LICENSE @@ -0,0 +1,481 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 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 library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + 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 Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the 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 a program 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. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + 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, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +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 compile 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) 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. + + c) 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. + + d) 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 source code 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 to +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 Library 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + 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. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..9e3e193 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,13 @@ + + +SUBDIRS = doc samples src + + +EXTRA_DIST = meanwhile.spec \ + meanwhile.pc \ + LICENSE + + +pkgconfig_DATA = meanwhile.pc +pkgconfigdir = $(libdir)/pkgconfig + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/NEWS diff --git a/README b/README new file mode 100644 index 0000000..fb83fec --- /dev/null +++ b/README @@ -0,0 +1,39 @@ + +===================== +The Meanwhile Project +===================== + +http://meanwhile.sourceforge.net/ +Christopher (siege) O'Brien + +Meanwhile is a library for connecting to a LIM (Lotus Instant +Messaging, formerly Lotus Sametime, formerly VPBuddy) community. It +uses a protocol based in part off of the IMPP draft(*1), and in part +off of traces of TCP sessions from existing clients. + +Meanwhile-python is a set of Python wrappers for the Meanwhile library + +The gaim-meanwhile plugin allows Gaim to connect to a Lotus Sametime +(tm) community using a compiled-in version of libMeanwhile. + +See INSTALL for instructions on building and installing + + +======= +License +======= + +Meanwhile and Meanwhile-python are released under the LGPL. See +LICENSE for the full license text and terms. + +Meanwhile-gaim is released under the GPL + + +========= +Footnotes +========= + +(*1) draft-houri-sametime-community-client-00.txt submitted to the +IETF as a draft proposal for the IMPP working group charter. + + diff --git a/TODO b/TODO new file mode 100644 index 0000000..e39d385 --- /dev/null +++ b/TODO @@ -0,0 +1,48 @@ + +- Storage service + - why does the server crash on some load requests?? + +- Messages + - Handshake is more complex than implemented, with login + information/token + +- Directory Service + +- Aware Service + - remove getText stuff, make the clients records that stuff + +- IM Service + - new Client Type, 0x00c0ffee (0x00 CO FF EE) + - data message type 0xdecafbad feature negotiation + - subtype 0x01 offer named features + OPAQUE + COUNT + STRING + ADDTL + + - subtype 0x02 accept named features + OPAQUE + COUNT + STRING + ADDTL + + - subtype 0x03 remove named features + OPAQUE + COUNT + STRING + + - example features: + "st.plaintext" + "st.typing" + "nb.html" + "nb.mime" + "nb.topic" + "mw.encrypt" + "mw. + + - data message type 0xdeadbeef + - subtype 0x01 named feature message + OPAQUE + STRING name of feature + OPAQUE data for message + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..40e4029 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,82 @@ +#! /bin/sh + + +# just to help us differentiate messages +ECHO() { + echo "[AUTOGEN]" $* +} + + +quiet_test() { + sh -c "$*" >/dev/null 2>&1 + return $? +} + + +# OSX has glibtoolize, everywhere else is just libtoolize +if test -z "${LTIZE}" ; then + ECHO "Trying to find a libtoolize" + + if quiet_test "libtoolize --version" ; then + LTIZE=libtoolize + elif quiet_test "glibtoolize --version" ; then + LTIZE=glibtoolize + fi +fi +if test -z "${LTIZE}" ; then + ECHO "Couldn't figure out a libtoolize to use. Specify one with LTIZE" +else + ECHO "Running $LTIZE --force" + $LTIZE --force || exit $? +fi + + + +# let's make sure we can find our aclocal macros +if test -d /usr/local/share/aclocal ; then + ACLOCAL_FLAGS="-I /usr/local/share/aclocal" +fi + +ECHO "Running aclocal $ACLOCAL_FLAGS" +ECHO "(please ignore any non-fatal errors)" +aclocal $ACLOCAL_FLAGS || exit $? + + + +# ECHO "Running autoheader" +# autoheader || exit $? + + + +# put in license and stuff if necessary +if test -z "$AUTOMAKE_FLAGS" ; then + AUTOMAKE_FLAGS="--add-missing --copy" +fi + +ECHO "Running automake $AUTOMAKE_FLAGS" +automake $AUTOMAKE_FLAGS + + + +ECHO "Running autoconf" +autoconf || exit $? + + + +ECHO "Running automake" +automake || exit $? +automake Makefile 2> /dev/null + + + +if test -f "configwrap" ; then + ECHO "Running ./configwrap $@" + ./configwrap $@ +else + ECHO "Running ./configure $@" + ./configure $@ +fi + + +ECHO "Done" +# The End diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..7684261 --- /dev/null +++ b/configure.ac @@ -0,0 +1,151 @@ + +# version of meanwhile +m4_define(meanwhile_major, 1) +m4_define(meanwhile_minor, 1) +m4_define(meanwhile_micro, 0) + +# release of meanwhile. 0 for CVS, 1 for release +m4_define(meanwhile_release, 0) + +# required and compat version of glib2. +m4_define(glib_required_version, 2.0.0) + + + +AC_INIT +AM_INIT_AUTOMAKE(meanwhile, meanwhile_major.meanwhile_minor.meanwhile_micro) + +RELEASE=meanwhile_release +AC_SUBST(RELEASE) + +AC_PREREQ([2.50]) + +AM_MAINTAINER_MODE + +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LIBTOOL +LIBTOOL="$LIBTOOL --silent" + +AC_HEADER_STDC + + + +# current:revision:age +MW_SO_VERSION=$((meanwhile_major+meanwhile_minor)):meanwhile_micro:meanwhile_minor +AC_SUBST(MW_SO_VERSION) + + + +# os-specific so flags +case "${host}" in +*darwin) + MW_SO_OS_FLAGS="-dynamic-lib";; +*mingw32 | *cygwin) + MW_SO_OS_FLAGS="-shared -no-undefined";; +*) + MW_SO_OS_FLAGS="-shared";; +esac +AC_SUBST(MW_SO_OS_FLAGS) + + + +# Debugging option +AC_ARG_ENABLE(debug, + [ --enable-debug compile with debugging support], + AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.]), ) + + + +# debugging output urging people to send mail +enableval="yes" +AC_ARG_ENABLE(mailme, + [ --enable-mailme[[=yes]] enable mw_debug_mailme output], ) + +MW_MAILME=1 +if test "$enableval" = "no" ; then + MW_MAILME=0 +fi +AC_SUBST(MW_MAILME) + + + +# Doxygen generation option +enableval="yes" +AC_ARG_ENABLE(doxygen, + [ --enable-doxygen[[=yes]] enable the doc package], ) + +enable_doxygen=$enableval + +AM_CONDITIONAL(ENABLE_DOXYGEN, test "$enable_doxygen" = "yes") + + + +# doc sub package +# right now this depends only on doxygen +WITH_DOC_SUBPACKAGE= +if test "$enable_doxygen" = "yes" ; then + WITH_DOC_SUBPACKAGE=1 +fi +AC_SUBST(WITH_DOC_SUBPACKAGE) + + + +# libm for use in mpi.c +AC_CHECK_LIB(m, main, LIBM="-lm") +AC_CHECK_HEADER(math.h) +AC_SUBST(LIBM) + + + +# Glib-2.0 +PKG_CHECK_MODULES(GLIB, +[glib-2.0 >= glib_required_version], +[ + AC_DEFINE(HAVE_GLIB, 1, [Define if we've found glib.]) +]) + +GLIB_VERSION=glib_required_version +AC_SUBST(GLIB_VERSION) +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) + + + +AC_CONFIG_FILES( + [Makefile src/Makefile src/mpi/Makefile] + [samples/Makefile] + [doc/Makefile doc/Doxyfile] + [meanwhile.spec meanwhile.pc] +) + +AC_CONFIG_FILES([samples/build], [chmod +x samples/build]) + +AC_OUTPUT() + + + +echo + +echo -n "mailme debug mode........ : " +if test "$MW_MAILME" = 1 ; then + echo "enabled" +else + echo "disabled" +fi + +echo -n "Doxygen generation....... : " +if test "$enable_doxygen" = "yes" ; then + echo "enabled" +else + echo "disabled" +fi + +echo +echo configure complete, now run \`make\` +echo +echo you may need to run \`ldconfig\` as root after installation before +echo being able to load this library +echo + +# The End. diff --git a/doc/.cvsignore b/doc/.cvsignore new file mode 100644 index 0000000..646ea1a --- /dev/null +++ b/doc/.cvsignore @@ -0,0 +1,6 @@ +Doxyfile +html +latex +man +Makefile +Makefile.in diff --git a/doc/CVS/Entries b/doc/CVS/Entries new file mode 100644 index 0000000..2359538 --- /dev/null +++ b/doc/CVS/Entries @@ -0,0 +1,4 @@ +/.cvsignore/1.5/Sun Jan 30 03:55:42 2005//Tmeanwhile_v1_1_0 +/Doxyfile.in/1.1/Tue Oct 19 04:59:17 2004//Tmeanwhile_v1_1_0 +/Makefile.am/1.4/Sat Oct 29 01:35:25 2005//Tmeanwhile_v1_1_0 +D diff --git a/doc/CVS/Entries.Log b/doc/CVS/Entries.Log new file mode 100644 index 0000000..18c5a0a --- /dev/null +++ b/doc/CVS/Entries.Log @@ -0,0 +1,2 @@ +A D/diagrams//// +R D/diagrams//// diff --git a/doc/CVS/Repository b/doc/CVS/Repository new file mode 100644 index 0000000..7534225 --- /dev/null +++ b/doc/CVS/Repository @@ -0,0 +1 @@ +meanwhile/doc diff --git a/doc/CVS/Root b/doc/CVS/Root new file mode 100644 index 0000000..7717627 --- /dev/null +++ b/doc/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@meanwhile.cvs.sourceforge.net:/cvsroot/meanwhile diff --git a/doc/CVS/Tag b/doc/CVS/Tag new file mode 100644 index 0000000..112c41e --- /dev/null +++ b/doc/CVS/Tag @@ -0,0 +1 @@ +Tmeanwhile_v1_1_0 diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in new file mode 100644 index 0000000..69e46f8 --- /dev/null +++ b/doc/Doxyfile.in @@ -0,0 +1,1135 @@ +# Doxyfile 1.3.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = @PACKAGE@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @VERSION@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, +# Danish, Dutch, Finnish, French, German, Greek, Hungarian, Italian, +# Japanese, Japanese-en (Japanese with English messages), Korean, +# Korean-en, Norwegian, Polish, Portuguese, Romanian, Russian, +# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated +# output. The encoding is not always determined by the language that +# is chosen, but also whether or not the output is meant for Windows +# or non-Windows users. In case there is a difference, setting the +# USE_WINDOWS_ENCODING tag to YES forces the Windows encoding (this is +# the default for the Windows binary), whereas setting the tag to NO +# uses a Unix-style encoding (the default for all platforms other than +# Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will +# prepend the brief description of a member or function before the +# detailed description. Note: if both HIDE_UNDOC_MEMBERS and +# BRIEF_MEMBER_DESC are set to NO, the brief descriptions will be +# completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description +# abbreviator that is used to form the text in various listings. Each +# string in this list, if found as the leading text of the brief +# description, will be stripped from the text and the result after +# processing the whole list, is used as the annotated text. Otherwise, +# the brief description is used as-is. If left blank, the following +# values are used ("$name" is automatically replaced with the name of +# the entity): "The $name class" "The $name widget" "The $name file" +# "is" "provides" "specifies" "contains" "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show +# all inherited members of a class in the documentation of that class +# as if those members were ordinary class members. Constructors, +# destructors and assignment operators of the base classes will not be +# shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or +# /// comments) as a brief description. This used to be the default +# behaviour. The new default is to treat a multi-line C++ comment +# block as a detailed description. Set this tag to YES if you prefer +# the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = YES + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If member grouping is used in the documentation and the +# DISTRIBUTE_GROUP_DOC tag is set to YES, then doxygen will reuse the +# documentation of the first member in the group (if any) for the +# other members of the group. By default all members of a group must +# be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of +# C sources only. Doxygen will then generate output that is more +# tailored for C. For instance, some of the names that are used will +# be different. The list of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of +# Java sources only. Doxygen will then generate output that is more +# tailored for Java. For instance, namespaces will be presented as +# packages, qualified scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then +# Doxygen will put a list of the files that are included by a file in +# the documentation of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = YES + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to +# appear in the documentation. If the initializer consists of more +# lines than specified here it will be hidden. Use a value of 0 to +# hide initializers completely. The appearance of the initializer of +# individual variables and defines in the documentation can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files +# generated at the bottom of the documentation of classes and +# structs. If set to YES the list will mention the files that were +# used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../src + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like +# *.cpp and *.h) to filter out the source-files in the directories. If +# left blank the following patterns are tested: *.c *.cc *.cxx *.cpp +# *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp *.h++ +# *.idl *.odl *.cs *.php *.php3 *.inc + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that +# should excluded from the INPUT source files. This way you can easily +# exclude a subdirectory from a directory tree whose root is specified +# with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are +# excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen +# should invoke to filter for each input file. Doxygen will invoke the +# filter program by executing (via popen()) the command +# , where is the value of the INPUT_FILTER tag, +# and is the name of an input file. Doxygen will then use +# the output that the filter program writes to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if +# set using INPUT_FILTER) will be used to filter the input files when +# producing source files to browse (i.e. when SOURCE_BROWSER is set to +# YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files +# will be generated. Documented entities will be cross-referenced with +# these sources. Note: To get rid of all source code in the generated +# output, make sure also VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = mw + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = YES + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 1 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = YES + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES +# then this tag can be used to specify a list of macro names that +# should be expanded. The macro definition that is found in the +# sources will be used. Use the PREDEFINED tag if you want to use a +# different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are +# alone on a line, have an all uppercase name, and do not end with a +# semicolon. Such function macros are typically used for boiler-plate +# code, and will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes +# with base or super classes. Setting the tag to NO turns the diagrams +# off. Note that this option is superseded by the HAVE_DOT option +# below. This is only a fallback. It is recommended to install and use +# dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot +# tool is available from the path. This tool is part of Graphviz, a +# graph visualization toolkit from AT&T and Lucent Bell Labs. The +# other options in this section have no effect if this option is set +# to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class +# method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable +# call graphs for selected functions only using the \callgraph +# command. + +CALL_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of +# the graphs generated by dot. A depth value of 3 means that only +# nodes reachable from the root by following a path via at most 3 +# edges will be shown. Nodes that lay further from the root node will +# be omitted. Note that setting this option to 1 or 2 may greatly +# reduce the computation time needed for large code bases. Also note +# that a graph may be further truncated if the graph's image +# dimensions are not sufficient to fit the graph (see +# MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the +# depth value (the default), the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..5616b52 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,30 @@ + +noinst_DATA = Doxyfile + + +if ENABLE_DOXYGEN + +htmldoc_DATA = html/* +htmldocdir = $(datadir)/doc/@PACKAGE@-doc-@VERSION@/html + +latexdoc_DATA = latex/* +latexdocdir = $(datadir)/doc/@PACKAGE@-doc-@VERSION@/latex + +docdata = $(htmldoc_DATA) $(latexdoc_DATA) + +$(docdata): doxygen + +else + +install: + +endif + + +doxygen: + @doxygen Doxyfile > /dev/null + + +clean-local: + rm -rf html latex man + diff --git a/meanwhile.pc.in b/meanwhile.pc.in new file mode 100644 index 0000000..fc8acce --- /dev/null +++ b/meanwhile.pc.in @@ -0,0 +1,16 @@ + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Meanwhile +Description: Open Reimplementation of the Lotus Sametime client protocol +Version: @VERSION@ +Requires: glib-2.0 >= @GLIB_VERSION@ + +Libs: -L${libdir} -lmeanwhile +Cflags: -I${includedir}/meanwhile + +libarchive=${libdir}/libmeanwhile.a + diff --git a/meanwhile.spec.in b/meanwhile.spec.in new file mode 100644 index 0000000..2260650 --- /dev/null +++ b/meanwhile.spec.in @@ -0,0 +1,131 @@ +# Christopher (siege) O'Brien + +%define with_doc_subpackage @WITH_DOC_SUBPACKAGE@%{nil} + + +Summary: Lotus Sametime Community Client library +Name: @PACKAGE@ +Epoch: 0 +Version: @VERSION@ +Release: @RELEASE@ +License: LGPL +Group: Applications/Internet +URL: http://meanwhile.sourceforge.net/ + +Requires: glib2 >= @GLIB_VERSION@ + +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +BuildRequires: glib2-devel >= @GLIB_VERSION@ + +%if %{with_doc_subpackage} +BuildRequires: doxygen +%endif + + +%description +Library for connecting as a client to a Lotus Sametime +community. Provides a simplified interface for authentication, +presence, messaging, conferencing, and remote preferences. + + +%prep +%setup -q + + +%build +%if %{with_doc_subpackage} +%configure --enable-doxygen +%else +%configure +%endif +%{__make} %{?_smp_mflags} + + +%install +%{__rm} -rf %{buildroot} +%{makeinstall} + + +%clean +%{__rm} -rf %{buildroot} + + +%files +%defattr(-,root,root,0755) +%doc AUTHORS ChangeLog COPYING INSTALL LICENSE NEWS README TODO +%{_libdir}/libmeanwhile.so* + + +%package devel +Group: Applications/Internet +Summary: Development package for the Meanwhile library +License: LGPL +Requires: %{name} = %{version}-%{release} + + +%description devel +Development package for the Meanwhile library + + +%files devel +%defattr(-,root,root,-) +%{_includedir}/meanwhile/ +%{_libdir}/libmeanwhile.a +%{_libdir}/libmeanwhile.la +%{_libdir}/pkgconfig/meanwhile.pc + + +%if %{with_doc_subpackage} +%package doc +Group: Applications/Internet +Summary: Documentation for the Meanwhile library +License: GNU Free Documentation License + + +%description doc +Documentation for the Meanwhile library + + +%files doc +%defattr(-,root,root,-) +%{_datadir}/doc/%{name}-doc-%{version}/ +%endif + + +%post +/sbin/ldconfig 2> /dev/null + + +%postun +/sbin/ldconfig 2> /dev/null + + +%changelog +* Fri Nov 18 2005 +- removed the gmp and gmp-devel requirements + +* Wed Sep 21 2005 +- added doc sub package + +* Sat Sep 17 2005 +- added gmp and gmp-devel requrements + +* Sun Jan 16 2005 +- removed python package (now in meanwhile-python module) + +* Mon Dec 27 2004 +- updated python package + +* Thu Jul 22 2004 +- moved .a, .la into -devel package +- added docs to install + +* Mon May 10 2004 +- Separated meanwhile from meanwhile-gaim +- First distribution with autoconf/automake/libtool + +* Tue Apr 13 2004 +- Initial rpm build. + + diff --git a/samples/.cvsignore b/samples/.cvsignore new file mode 100644 index 0000000..c6cc57a --- /dev/null +++ b/samples/.cvsignore @@ -0,0 +1,13 @@ +build +.deps +.libs +logging_proxy +login_server +Makefile +Makefile.in +nocipher_proxy +*.o +redirect_server +sendmessage +socket +*tmp* diff --git a/samples/CVS/Entries b/samples/CVS/Entries new file mode 100644 index 0000000..92e0c00 --- /dev/null +++ b/samples/CVS/Entries @@ -0,0 +1,11 @@ +/.cvsignore/1.6/Mon Dec 5 01:28:48 2005//Tmeanwhile_v1_1_0 +/Makefile.am/1.10/Sat Oct 29 01:35:25 2005//Tmeanwhile_v1_1_0 +/README/1.5/Thu Oct 13 05:31:15 2005//Tmeanwhile_v1_1_0 +/build.in/1.2/Thu Dec 29 03:37:59 2005//Tmeanwhile_v1_1_0 +/logging_proxy.c/1.8/Mon Dec 5 00:53:19 2005//Tmeanwhile_v1_1_0 +/login_server.c/1.6/Mon Dec 5 01:16:56 2005//Tmeanwhile_v1_1_0 +/nocipher_proxy.c/1.3/Mon Dec 5 01:16:57 2005//Tmeanwhile_v1_1_0 +/redirect_server.c/1.2/Mon Dec 5 01:16:57 2005//Tmeanwhile_v1_1_0 +/sendmessage.c/1.3/Thu Dec 8 00:44:34 2005//Tmeanwhile_v1_1_0 +/socket.c/1.7/Thu Dec 8 00:44:34 2005//Tmeanwhile_v1_1_0 +D diff --git a/samples/CVS/Repository b/samples/CVS/Repository new file mode 100644 index 0000000..a038525 --- /dev/null +++ b/samples/CVS/Repository @@ -0,0 +1 @@ +meanwhile/samples diff --git a/samples/CVS/Root b/samples/CVS/Root new file mode 100644 index 0000000..7717627 --- /dev/null +++ b/samples/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@meanwhile.cvs.sourceforge.net:/cvsroot/meanwhile diff --git a/samples/CVS/Tag b/samples/CVS/Tag new file mode 100644 index 0000000..112c41e --- /dev/null +++ b/samples/CVS/Tag @@ -0,0 +1 @@ +Tmeanwhile_v1_1_0 diff --git a/samples/Makefile.am b/samples/Makefile.am new file mode 100644 index 0000000..000a648 --- /dev/null +++ b/samples/Makefile.am @@ -0,0 +1,34 @@ + + +SAMPLES_SRC = \ + logging_proxy.c \ + login_server.c \ + nocipher_proxy.c \ + sendmessage.c \ + socket.c \ + redirect_server.c + +SAMPLES = $(SAMPLES_SRC:.c=) + + +sample_SCRIPTS = \ + build + +sample_DATA = \ + README \ + $(SAMPLES_SRC) + + +sampledir = $(datadir)/doc/@PACKAGE@-doc-@VERSION@/samples + + +EXTRA_DIST = $(sample_DATA) $(sample_SCRIPTS) + + +$(SAMPLES): %: %.c + $(CC) $< -o $@ $(GLIB_CFLAGS) $(GLIB_LIBS) `PKG_CONFIG_PATH="$(prefix)/lib/pkgconfig" $(PKG_CONFIG) --cflags --libs meanwhile` + + +clean-local: + rm -f $(SAMPLES) + diff --git a/samples/README b/samples/README new file mode 100644 index 0000000..eaca38a --- /dev/null +++ b/samples/README @@ -0,0 +1,102 @@ + +These samples demonstrate how to do a few things with the Meanwhile +library. Some of these are also tools which can be used to help in +debugging and obtaining protocol information. + + +Examples +======== + + socket.c + +Compile with `./build socket`. This is the simplest possible +client. All it does is connect and authenticate to a host (specified +on the command line). Most of the code is dedicated to performing I/O. + + + sendmessage.c + +Compile with `./build sendmessage`. Expands upon socket.c to send a +message via the IM service after login, then immediately logs off and +exits. + + + echo.c (forthcoming) + +Compile with `./build echo`. This extends on the socket example above, +but introduces the use of the IM service. All incoming messages will +be echo'd verbatim back at whomever sends them. Can anger co-workers. + + + pipe.c (forthcoming) + +Compile with `./build pipe`. Extending upon the echo example, but is +slightly more amusing and can be possibly useful for scripting. Will +output all incoming IM messages to stdout. Listens on stdin and will +write each line from stdin back to anyone who sent us a message +(responding in order received) + +Since this application uses stdout, glib logging is smothered. + + + blist_storage.c (forthcoming) + +Compile with `./build blist_storage`. An example of the Storage +service and buddy list parser. Operates in two modes, up and down. In +up mode, this sample will read a buddy list file on stdin, check it +for validity, then upload it to the storage service. In down mode, +this sample will fetch the buddy list from the storage service and +write it to stdout. + +Since this application uses stdout, glib logging is smothered. + + +Utilities +========= + + redirect_server.c + +Compile with `./build redirect_server`. Acts as a redirecting sametime +server; any client attempting to connect to the socket this utility +listens on will be instructed to redirect its connection to an +alternative host (which is specified on the command line). Useful for +ensuring client code can handle redirects correctly when there's no +real redirecting server to test against. + + + nocipher_proxy.c + +Compile with `./build nocipher_proxy`. Acts as a sametime server +proxy, passing messages between a real client and server. However, it +will intercept and mangle channel creation messages to ensure that +they will not be used with encryption. This will cause many clients to +fail in strange places (where they demand encryption), but is useful +for getting some messages from a service in the clear. Will print all +messages in hex pairs to stdout using the hexdump utility. This may be +more useful than using ethereal, as it will actually group its output +by message rather than by receipt from the TCP stream. + + + login_server.c + +Compile with `./build login_server`. Acts as a sametime server; any +client attempting to connect to the socket this utility listens on +will be able to complete handshaking and send a login message. The +tool then analyzes the authentication method and data and prints the +decrypted data to stdout. This was useful for reverse-engineering the +RC2/128 auth method (and determining what one of the guint32 fields of +the handshake ack was for). Probably not very useful for anything +else. + + + logging_proxy.c + +Compile with `./build logging_proxy`. Acts as a sametime server proxy, +passing messages between a real client and server. However, it will +intercept and mangle channel data in order to obtain the unencrypted +data. This should be invisible to both the client and the server. Will +print all messages in hex pairs to stdout using the hexdump utility, +and will print decrypted contents of encrypted channel messages +separately. This is certainly more useful than using ethereal, as it +groups its output by message as well as provides an unencrypted view +of otherwise obscured service protocols. diff --git a/samples/build.in b/samples/build.in new file mode 100644 index 0000000..19195ed --- /dev/null +++ b/samples/build.in @@ -0,0 +1,37 @@ + +#! /bin/sh + + +CC=@CC@ +PKG_CONFIG=@PKG_CONFIG@ + + +function build() { + if (echo "$1" | grep '\.c$' > /dev/null) ; then + SAMPLE=$(echo "$1" | sed 's/\.c$//') + SOURCE="$1" + else + SAMPLE="$1" + SOURCE="${SAMPLE}.c" + fi + + if test -z "$SOURCE" ; then + echo "please specify a target to build" + return 1 + + elif test ! -f "$SOURCE" ; then + echo "file not found: $SOURCE" + return 1 + fi + + PKG_CONFIG_PATH="$PKG_CONFIG_PATH":@prefix@/lib/pkgconfig + export PKG_CONFIG_PATH + + CFLAGS=`$PKG_CONFIG --libs --cflags glib-2.0 meanwhile` + + "$CC" $CFLAGS -o "$SAMPLE" "$SOURCE" + return $? +} + + +build $1 diff --git a/samples/logging_proxy.c b/samples/logging_proxy.c new file mode 100644 index 0000000..26a7d96 --- /dev/null +++ b/samples/logging_proxy.c @@ -0,0 +1,1004 @@ + +/* + Logging Sametime Proxy Utility + The Meanwhile Project + + This is a tool which can act as a proxy between a client and a + sametime server, which will log all messages to stdout. It will also + munge channel creation messages in order to be able to decrypt any + encrypted data sent over a channel, and will log decrypted chunks to + stdout as well. This makes reverse-engineering of services much, + much easier. + + The idea is simple, but the implementation made my head hurt. + + Christopher O'Brien +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +/** one side of the proxy (either the client side or the server + side). The forward method for one should push data into the socket + of the other. */ +struct proxy_side { + int sock; + GIOChannel *chan; + gint chan_io; + + guchar *buf; + gsize buf_size; + gsize buf_recv; + + void (*forward)(const guchar *buf, gsize len); +}; + + +static struct proxy_side client; /**< side facing the client */ +static struct proxy_side server; /**< side facing the server */ + + +static char *host = NULL; +static int client_port = 0; +static int server_port = 0; + + +static int counter = 0; +static int listen_sock = 0; +static GIOChannel *listen_chan = NULL; +static gint listen_io = 0; + + + + + +/** given one side, get the other */ +#define OTHER_SIDE(side) \ + ((side == &client)? &server: &client) + + +/** encryption state information used in the RC2/40 cipher */ +struct rc2_40enc { + guchar outgoing_iv[8]; + int outgoing_key[64]; + guchar incoming_iv[8]; + int incoming_key[64]; +}; + + +/* re-usable rc2 40 stuff */ +static int session_key[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + + +/** encryption state information used in the RC2/128 cipher */ +struct rc2_128enc { + guchar outgoing_iv[8]; + guchar incoming_iv[8]; + int shared_key[64]; +}; + + +/* re-usable rc2 128 stuff */ +static struct mwMpi *private_key; +static struct mwOpaque public_key; + + +/** represents a channel. The channel has a left side and a right + side. The left side is the creator of the channel. The right side + is the target of the channel. Each side has its own encryption + state information, so an incoming message from either side can + be decrypted, then re-encrypted for the other side. */ +struct channel { + guint32 id; + + /* login id of creator or NULL if created by client side */ + char *creator; + + /* the offer from the left side */ + struct mwEncryptOffer offer; + + /** the mode of encryption */ + enum { + enc_none = 0, /**< nothing encrypted */ + enc_easy, /**< easy (rc2/40) encryption */ + enc_hard, /**< hard (rc2/128) encryption */ + } enc_mode; + + /** encryption data for the left side */ + union { + struct rc2_40enc easy; + struct rc2_128enc hard; + } left_enc; + + /** encryption data for the right side */ + union { + struct rc2_40enc easy; + struct rc2_128enc hard; + } right_enc; + + struct proxy_side *left; /**< proxy side acting as the left side */ + struct proxy_side *right; /**< proxy side acting as the right side */ +}; + + +/* collection of channels */ +static GHashTable *channels; + + +#define PUT_CHANNEL(chan) \ + g_hash_table_insert(channels, GUINT_TO_POINTER((chan)->id), (chan)) + +#define GET_CHANNEL(id) \ + g_hash_table_lookup(channels, GUINT_TO_POINTER(id)) + +#define REMOVE_CHANNEL(id) \ + g_hash_table_remove(channels, GUINT_TO_POINTER(id)) + + +/** print a message to stdout and use hexdump to print a data chunk */ +static void hexdump_vprintf(const guchar *buf, gsize len, + const char *txt, va_list args) { + FILE *fp; + + if(txt) { + fputc('\n', stdout); + vfprintf(stdout, txt, args); + fputc('\n', stdout); + } + fflush(stdout); + + fp = popen("hexdump -C", "w"); + fwrite(buf, len, 1, fp); + fflush(fp); + pclose(fp); +} + + +/** print a message to stdout and use hexdump to print a data chunk */ +static void hexdump_printf(const guchar *buf, gsize len, + const char *txt, ...) { + va_list args; + va_start(args, txt); + hexdump_vprintf(buf, len, txt, args); + va_end(args); +} + + +/** serialize a message for sending */ +static void put_msg(struct mwMessage *msg, struct mwOpaque *o) { + struct mwPutBuffer *b; + + b = mwPutBuffer_new(); + mwMessage_put(b, msg); + mwPutBuffer_finalize(o, b); + + b = mwPutBuffer_new(); + mwOpaque_put(b, o); + mwOpaque_clear(o); + mwPutBuffer_finalize(o, b); +} + + +static void key_copy(int to[64], int from[64]) { + int i = 64; + while(i--) to[i] = from[i]; +} + + +/* we don't want to be redirected away from the proxy, so eat any + redirect messages from the server and respond with a login cont */ +static void munge_redir() { + struct mwMessage *msg; + struct mwOpaque o = { 0, 0 }; + + msg = mwMessage_new(mwMessage_LOGIN_CONTINUE); + put_msg(msg, &o); + mwMessage_free(msg); + + server.forward(o.data, o.len); + + mwOpaque_clear(&o); +} + + +/* handle receipt of channel create messages from either side, + recording the offered ciphers, and munging it to instead include + our own key as applicable, then sending it on */ +static void munge_create(struct proxy_side *side, + struct mwMsgChannelCreate *msg) { + + struct mwOpaque o = { 0, 0 }; + GList *l; + struct channel *c; + + /* create a new channel on the side */ + c = g_new0(struct channel, 1); + c->id = msg->channel; + c->left = side; + c->right = OTHER_SIDE(side); + + if(msg->creator_flag) { + c->creator = g_strdup(msg->creator.login_id); + } + + /* record the mode and encryption items */ + c->offer.mode = msg->encrypt.mode; + c->offer.items = msg->encrypt.items; + c->offer.extra = msg->encrypt.extra; + c->offer.flag = msg->encrypt.flag; + + PUT_CHANNEL(c); + + /* replace the encryption items with our own as applicable */ + if(msg->encrypt.items) { + l = msg->encrypt.items; + msg->encrypt.items = NULL; /* steal them */ + + for(; l; l = l->next) { + struct mwEncryptItem *i1, *i2; + + /* the original we've stolen */ + i1 = l->data; + + /* the munged replacement */ + i2 = g_new0(struct mwEncryptItem, 1); + i2->id = i1->id; + + switch(i1->id) { + case mwCipher_RC2_128: + printf("munging an offered RC2/128\n"); + mwOpaque_clone(&i2->info, &public_key); + break; + case mwCipher_RC2_40: + printf("munging an offered RC2/40\n"); + default: + ; + } + + msg->encrypt.items = g_list_append(msg->encrypt.items, i2); + } + } + + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); +} + + +/* find an enc item by id in a list of items */ +struct mwEncryptItem *find_item(GList *items, guint16 id) { + GList *ltmp; + for(ltmp = items; ltmp; ltmp = ltmp->next) { + struct mwEncryptItem *i = ltmp->data; + if(i->id == id) return i; + } + return NULL; +} + + +/* handle acceptance of a channel */ +static void munge_accept(struct proxy_side *side, + struct mwMsgChannelAccept *msg) { + + struct mwOpaque o = {0,0}; + struct channel *chan; + struct mwEncryptItem *item; + + chan = GET_CHANNEL(msg->head.channel); + item = msg->encrypt.item; + + if(! item) { + /* cut to the chase */ + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); + return; + } + + /* init right-side encryption with our enc and accepted enc */ + switch(item->id) { + case mwCipher_RC2_128: { + struct mwMpi *remote, *shared; + struct mwOpaque k; + + remote = mwMpi_new(); + shared = mwMpi_new(); + + printf("right side accepted RC2/128\n"); + + mwMpi_import(remote, &item->info); + mwMpi_calculateDHShared(shared, remote, private_key); + mwMpi_export(shared, &k); + + chan->enc_mode = enc_hard; + + mwIV_init(chan->right_enc.hard.outgoing_iv); + mwIV_init(chan->right_enc.hard.incoming_iv); + mwKeyExpand(chan->right_enc.hard.shared_key, k.data+(k.len-16), 16); + + mwMpi_free(remote); + mwMpi_free(shared); + mwOpaque_clear(&k); + break; + } + case mwCipher_RC2_40: { + char *who; + + printf("right side accepted RC2/40\n"); + + chan->enc_mode = enc_easy; + + mwIV_init(chan->right_enc.easy.outgoing_iv); + mwIV_init(chan->right_enc.easy.incoming_iv); + + if(msg->acceptor_flag) { + who = msg->acceptor.login_id; + printf("right side is the server\n"); + printf("server is %s\n", who); + mwKeyExpand(chan->right_enc.easy.incoming_key, (guchar *) who, 5); + key_copy(chan->right_enc.easy.outgoing_key, session_key); + + } else { + who = chan->creator; + printf("right side is the client\n"); + printf("server is %s\n", who); + key_copy(chan->right_enc.easy.incoming_key, session_key); + mwKeyExpand(chan->right_enc.easy.outgoing_key, (guchar *) who, 5); + } + + break; + } + default: + chan->enc_mode = enc_none; + break; + } + + /* init left-side encryption with offered enc and our enc, munge accept */ + switch(item->id) { + case mwCipher_RC2_128: { + struct mwMpi *remote, *shared; + struct mwOpaque k; + struct mwEncryptItem *offered; + + remote = mwMpi_new(); + shared = mwMpi_new(); + + printf("accepting left side with RC2/128\n"); + + offered = find_item(chan->offer.items, mwCipher_RC2_128); + mwMpi_import(remote, &offered->info); + mwMpi_calculateDHShared(shared, remote, private_key); + mwMpi_export(shared, &k); + + mwIV_init(chan->left_enc.hard.outgoing_iv); + mwIV_init(chan->left_enc.hard.incoming_iv); + mwKeyExpand(chan->left_enc.hard.shared_key, k.data+(k.len-16), 16); + + mwMpi_free(remote); + mwMpi_free(shared); + mwOpaque_clear(&k); + + /* munge accept with out public key */ + mwOpaque_clear(&item->info); + mwOpaque_clone(&item->info, &public_key); + break; + } + case mwCipher_RC2_40: + printf("accepting left side with RC2/40\n"); + + mwIV_init(chan->left_enc.easy.outgoing_iv); + mwIV_init(chan->left_enc.easy.incoming_iv); + + key_copy(chan->left_enc.easy.outgoing_key, + chan->right_enc.easy.incoming_key); + + key_copy(chan->left_enc.easy.incoming_key, + chan->right_enc.easy.outgoing_key); + break; + + default: + ; + } + + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); +} + + +static void dec(struct channel *chan, struct proxy_side *side, + struct mwOpaque *to, struct mwOpaque *from) { + + switch(chan->enc_mode) { + case enc_easy: { + if(chan->left == side) { + /* left side decrypt */ + mwDecryptExpanded(chan->left_enc.easy.incoming_key, + chan->left_enc.easy.incoming_iv, + from, to); + } else { + /* right side decrypt */ + mwDecryptExpanded(chan->right_enc.easy.incoming_key, + chan->right_enc.easy.incoming_iv, + from, to); + } + break; + } + case enc_hard: { + if(chan->left == side) { + /* left side decrypt */ + mwDecryptExpanded(chan->left_enc.hard.shared_key, + chan->left_enc.hard.incoming_iv, + from, to); + } else { + /* right side decrypt */ + mwDecryptExpanded(chan->right_enc.hard.shared_key, + chan->right_enc.hard.incoming_iv, + from, to); + } + break; + } + } +} + + +static void enc(struct channel *chan, struct proxy_side *side, + struct mwOpaque *to, struct mwOpaque *from) { + + switch(chan->enc_mode) { + case enc_easy: { + if(chan->left == side) { + /* left side encrypt */ + mwEncryptExpanded(chan->left_enc.easy.outgoing_key, + chan->left_enc.easy.outgoing_iv, + from, to); + } else { + /* right side encrypt */ + mwEncryptExpanded(chan->right_enc.easy.outgoing_key, + chan->right_enc.easy.outgoing_iv, + from, to); + } + break; + } + case enc_hard: { + if(chan->left == side) { + /* left side encrypt */ + mwEncryptExpanded(chan->left_enc.hard.shared_key, + chan->left_enc.hard.outgoing_iv, + from, to); + } else { + /* right side encrypt */ + mwEncryptExpanded(chan->right_enc.hard.shared_key, + chan->right_enc.hard.outgoing_iv, + from, to); + } + break; + } + } +} + + +static void munge_channel(struct proxy_side *side, + struct mwMsgChannelSend *msg) { + + struct mwOpaque o = {0,0}; + + if(msg->head.options & mwMessageOption_ENCRYPT) { + struct mwOpaque d = {0,0}; + struct channel *chan; + + chan = GET_CHANNEL(msg->head.channel); + + /* decrypt from side */ + dec(chan, side, &d, &msg->data); + + /* display */ + hexdump_printf(d.data, d.len, "decrypted channel message data:", + msg->type); + + /* encrypt to other side */ + mwOpaque_clear(&msg->data); + enc(chan, OTHER_SIDE(side), &msg->data, &d); + mwOpaque_clear(&d); + } + + /* send to other side */ + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); +} + + +/* handle destruction of a channel */ +static void handle_destroy(struct proxy_side *side, + struct mwMsgChannelDestroy *msg) { + + struct channel *chan; + GList *l; + + chan = GET_CHANNEL(msg->head.channel); + REMOVE_CHANNEL(msg->head.channel); + + if(! chan) return; + + for(l = chan->offer.items; l; l = l->next) { + mwEncryptItem_free(l->data); + } + g_list_free(chan->offer.items); + chan->offer.items = NULL; + + g_free(chan->creator); + chan->creator = NULL; + + g_free(chan); +} + + +static void forward(struct proxy_side *to, + struct mwOpaque *data) { + + struct mwPutBuffer *pb = mwPutBuffer_new(); + struct mwOpaque po = { 0, 0 }; + + mwOpaque_put(pb, data); + mwPutBuffer_finalize(&po, pb); + to->forward(po.data, po.len); + mwOpaque_clear(&po); +} + + +/* handle messages from either side */ +static void side_process(struct proxy_side *s, const guchar *buf, gsize len) { + struct mwOpaque o = { .len = len, .data = (guchar *) buf }; + struct mwGetBuffer *b; + guint16 type; + + if(! len) return; + + if(s == &server) { + hexdump_printf(buf, len, "server -> client"); + } else { + hexdump_printf(buf, len, "client -> server"); + } + + b = mwGetBuffer_wrap(&o); + type = guint16_peek(b); + + switch(type) { + case mwMessage_LOGIN_ACK: { + struct mwMsgLoginAck *msg = (struct mwMsgLoginAck *) mwMessage_get(b); + printf("client is %s\n", msg->login.login_id); + mwKeyExpand(session_key, (guchar *) msg->login.login_id, 5); + mwMessage_free(MW_MESSAGE(msg)); + forward(s, &o); + break; + } + + case mwMessage_LOGIN_REDIRECT: { + munge_redir(); + break; + } + + case mwMessage_CHANNEL_CREATE: { + struct mwMessage *msg = mwMessage_get(b); + munge_create(s, (struct mwMsgChannelCreate *) msg); + mwMessage_free(msg); + break; + } + + case mwMessage_CHANNEL_ACCEPT: { + struct mwMessage *msg = mwMessage_get(b); + munge_accept(s, (struct mwMsgChannelAccept *) msg); + mwMessage_free(msg); + break; + } + + case mwMessage_CHANNEL_DESTROY: { + struct mwMessage *msg = mwMessage_get(b); + handle_destroy(s, (struct mwMsgChannelDestroy *) msg); + mwMessage_free(msg); + forward(s, &o); + break; + } + + case mwMessage_CHANNEL_SEND: { + struct mwMessage *msg = mwMessage_get(b); + munge_channel(s, (struct mwMsgChannelSend *) msg); + mwMessage_free(msg); + break; + } + + default: + forward(s, &o); + } + + mwGetBuffer_free(b); +} + + +/** clean up a proxy side's inner buffer */ +static void side_buf_free(struct proxy_side *s) { + g_free(s->buf); + s->buf = NULL; + s->buf_size = 0; + s->buf_recv = 0; +} + + +#define ADVANCE(b, n, count) { b += count; n -= count; } + + +/** handle input to complete an existing buffer */ +static gsize side_recv_cont(struct proxy_side *s, const guchar *b, gsize n) { + + gsize x = s->buf_size - s->buf_recv; + + if(n < x) { + memcpy(s->buf+s->buf_recv, b, n); + s->buf_recv += n; + return 0; + + } else { + memcpy(s->buf+s->buf_recv, b, x); + ADVANCE(b, n, x); + + if(s->buf_size == 4) { + struct mwOpaque o = { .len = 4, .data = s->buf }; + struct mwGetBuffer *gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + + if(n < x) { + guchar *t; + x += 4; + t = (guchar *) g_malloc(x); + memcpy(t, s->buf, 4); + memcpy(t+4, b, n); + + side_buf_free(s); + + s->buf = t; + s->buf_size = x; + s->buf_recv = n + 4; + return 0; + + } else { + side_buf_free(s); + side_process(s, b, x); + ADVANCE(b, n, x); + } + + } else { + side_process(s, s->buf+4, s->buf_size-4); + side_buf_free(s); + } + } + + return n; +} + + +/** handle input when there's nothing previously buffered */ +static gsize side_recv_empty(struct proxy_side *s, const guchar *b, gsize n) { + struct mwOpaque o = { .len = n, .data = (guchar *) b }; + struct mwGetBuffer *gb; + gsize x; + + if(n < 4) { + s->buf = (guchar *) g_malloc0(4); + memcpy(s->buf, b, n); + s->buf_size = 4; + s->buf_recv = n; + return 0; + } + + gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + if(! x) return n - 4; + + if(n < (x + 4)) { + + x += 4; + s->buf = (guchar *) g_malloc(x); + memcpy(s->buf, b, n); + s->buf_size = x; + s->buf_recv = n; + return 0; + + } else { + ADVANCE(b, n, 4); + side_process(s, b, x); + ADVANCE(b, n, x); + + return n; + } +} + + +/** handle input in chunks */ +static gsize side_recv(struct proxy_side *s, const guchar *b, gsize n) { + + if(n && (s->buf_size == 0) && (*b & 0x80)) { + ADVANCE(b, n, 1); + } + + if(n == 0) { + return 0; + + } else if(s->buf_size > 0) { + return side_recv_cont(s, b, n); + + } else { + return side_recv_empty(s, b, n); + } +} + + +/** handle input */ +static void feed_buf(struct proxy_side *side, const guchar *buf, gsize n) { + guchar *b = (guchar *) buf; + gsize remain = 0; + + g_return_if_fail(side != NULL); + + while(n > 0) { + remain = side_recv(side, b, n); + b += (n - remain); + n = remain; + } +} + + +static int read_recv(struct proxy_side *side) { + guchar buf[2048]; + int len; + + len = read(side->sock, buf, 2048); + if(len > 0) feed_buf(side, buf, (gsize) len); + + return len; +} + + +static void init_listen(); + + +static void side_done(struct proxy_side *side) { + if(side->sock) { + g_source_remove(side->chan_io); + close(side->sock); + side->sock = 0; + side->chan = NULL; + side->chan_io = 0; + } +} + + +static void done() { + printf("closing connection\n"); + + side_done(&client); + side_done(&server); + + if(counter--) { + init_listen(); + } else { + exit(0); + } +} + + +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct proxy_side *side = data; + int ret = 0; + + if(cond & G_IO_IN) { + ret = read_recv(side); + if(ret > 0) return TRUE; + } + + done(); + + return FALSE; +} + + +static void client_cb(const guchar *buf, gsize len) { + if(server.sock) write(server.sock, buf, len); +} + + +/** setup the client */ +static void init_client(int sock) { + + client.forward = client_cb; + client.sock = sock; + client.chan = g_io_channel_unix_new(sock); + client.chan_io = g_io_add_watch(client.chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, &client); +} + + +static void server_cb(const guchar *buf, gsize len) { + if(client.sock) write(client.sock, buf, len); +} + + +/** generate a private/public DH keypair for internal (re)use */ +static void init_rc2_128() { + struct mwMpi *public; + + private_key = mwMpi_new(); + public = mwMpi_new(); + + mwMpi_randDHKeypair(private_key, public); + mwMpi_export(public, &public_key); + + mwMpi_free(public); +} + + +/** address lookup used by init_sock */ +static void init_sockaddr(struct sockaddr_in *addr, + const char *host, int port) { + + struct hostent *hostinfo; + + addr->sin_family = AF_INET; + addr->sin_port = htons (port); + hostinfo = gethostbyname(host); + if(hostinfo == NULL) { + fprintf(stderr, "Unknown host %s.\n", host); + exit(1); + } + addr->sin_addr = *(struct in_addr *) hostinfo->h_addr; +} + + +/** connect to server on host:port */ +static void init_server() { + struct sockaddr_in srvrname; + int sock; + + printf("connecting to %s:%i\n", host, server_port); + + sock = socket(PF_INET, SOCK_STREAM, 0); + if(sock < 0) { + fprintf(stderr, "socket failure"); + exit(1); + } + + init_sockaddr(&srvrname, host, server_port); + connect(sock, (struct sockaddr *)&srvrname, sizeof(srvrname)); + + server.forward = server_cb; + server.sock = sock; + server.chan = g_io_channel_unix_new(sock); + server.chan_io = g_io_add_watch(server.chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, &server); + + printf("connected to %s:%i\n", host, server_port); +} + + + +static gboolean listen_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sockaddr_in rem; + guint len = sizeof(rem); + int sock; + + printf("got connection\n"); + + sock = accept(listen_sock, (struct sockaddr *) &rem, &len); + /* g_assert(sock > 0); */ + + init_server(); + init_client(sock); + + listen_io = 0; + + return FALSE; +} + + +static void init_listen() { + if(! listen_sock) { + struct sockaddr_in sin; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + g_assert(sock >= 0); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = PF_INET; + sin.sin_port = htons(client_port); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + g_assert_not_reached(); + + if(listen(sock, 1) < 0) + g_assert_not_reached(); + + listen_sock = sock; + listen_chan = g_io_channel_unix_new(sock); + } + + if(! listen_io) { + listen_io = g_io_add_watch(listen_chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + listen_cb, NULL); + printf("listening on port %i\n", client_port); + } +} + + +int main(int argc, char *argv[]) { + + memset(&client, 0, sizeof(struct proxy_side)); + memset(&server, 0, sizeof(struct proxy_side)); + + if(argc > 1) { + char *z; + + host = argv[1]; + z = host; + + host = strchr(z, ':'); + if(host) *host++ = '\0'; + client_port = atoi(z); + + z = strchr(host, ':'); + if(z) *z++ = '\0'; + server_port = atoi(z); + } + + if(argc > 2) { + counter = atoi(argv[2]); + } + + if(!host || !*host || !client_port || !server_port) { + fprintf(stderr, + ( " Usage: %s local_port:remote_host:remote_port [n]\n" + " Creates a locally-running sametime proxy which enforces" + " unencrypted channels. Will drop the first n connections\n" ), + argv[0]); + exit(1); + } + + /* @todo create signal handlers to cleanup sockets */ + + channels = g_hash_table_new(g_direct_hash, g_direct_equal); + + init_rc2_128(); + init_listen(); + + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return 0; +} + diff --git a/samples/login_server.c b/samples/login_server.c new file mode 100644 index 0000000..3264467 --- /dev/null +++ b/samples/login_server.c @@ -0,0 +1,421 @@ + +/* + Login-parsing Faux Server + The Meanwhile Project + + This is a tool to aide in reverse engineering different types of + authentication schemes. + + Christopher O'Brien +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +/** the server socket or the connected socket */ +static int sock; + +/** the io channel */ +static GIOChannel *chan; + +/** the listening event on the io channel */ +static int chan_io; + + +static guchar *sbuf; +static gsize sbuf_size; +static gsize sbuf_recv; + + +struct mwMpi *private, *public; + + +static void hexout(const char *txt, const guchar *buf, gsize len) { + FILE *fp; + + if(txt) fprintf(stdout, "\n%s\n", txt); + fflush(stdout); + + fp = popen("hexdump -C", "w"); + fwrite(buf, len, 1, fp); + fflush(fp); + pclose(fp); +} + + +static void send_msg(struct mwMessage *msg) { + struct mwPutBuffer *b; + struct mwOpaque o = { 0, 0 }; + + b = mwPutBuffer_new(); + mwMessage_put(b, msg); + mwPutBuffer_finalize(&o, b); + + b = mwPutBuffer_new(); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + mwPutBuffer_finalize(&o, b); + + if(sock) write(sock, o.data, o.len); + + hexout("sent:", o.data, o.len); + + mwOpaque_clear(&o); +} + + +static void handshake_ack() { + struct mwMsgHandshakeAck *msg; + + msg = (struct mwMsgHandshakeAck *) + mwMessage_new(mwMessage_HANDSHAKE_ACK); + + msg->major = 0x1e; + msg->minor = 0x1d; + + mwMpi_randDHKeypair(private, public); + mwMpi_export(public, &msg->data); + + msg->magic = 0x01234567; + hexout("sending pubkey:", msg->data.data, msg->data.len); + + send_msg(MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); +} + + +static void handle_login(struct mwMsgLogin *msg) { + struct mwGetBuffer *gb; + struct mwOpaque a, b, c; + guint16 z; + struct mwMpi *remote, *shared; + guchar iv[8]; + + remote = mwMpi_new(); + shared = mwMpi_new(); + + mwIV_init(iv); + + gb = mwGetBuffer_wrap(&msg->auth_data); + guint16_get(gb, &z); + mwOpaque_get(gb, &a); + mwOpaque_get(gb, &b); + mwGetBuffer_free(gb); + + mwMpi_import(remote, &a); + mwOpaque_clear(&a); + + mwMpi_calculateDHShared(shared, remote, private); + mwMpi_export(shared, &a); + hexout("shared key:", a.data, a.len); + + mwDecrypt(a.data+(a.len-16), 16, iv, &b, &c); + hexout("decrypted to:", c.data, c.len); + + mwOpaque_clear(&a); + mwOpaque_clear(&b); + mwOpaque_clear(&c); + + mwMpi_free(remote); + mwMpi_free(shared); +} + + +static void done() { + close(sock); + exit(0); +} + + +static void side_process(const guchar *buf, gsize len) { + struct mwOpaque o = { .len = len, .data = (guchar *) buf }; + struct mwGetBuffer *b; + guint16 type; + + if(! len) return; + + b = mwGetBuffer_wrap(&o); + type = guint16_peek(b); + + hexout("received:", buf, len); + + switch(type) { + case mwMessage_HANDSHAKE: + printf("got handshake, sending handshake_ack\n"); + handshake_ack(); + break; + + case mwMessage_LOGIN: + printf("got login, attempting to decipher\n"); + { + struct mwMsgLogin *msg = (struct mwMsgLogin *) mwMessage_get(b); + handle_login(msg); + mwMessage_free(MW_MESSAGE(msg)); + done(); + } + break; + + case mwMessage_CHANNEL_DESTROY: + printf("channel destroy\n"); + done(); + break; + + default: + ; + } + + mwGetBuffer_free(b); +} + + +static void sbuf_free() { + g_free(sbuf); + sbuf = NULL; + sbuf_size = 0; + sbuf_recv = 0; +} + + +#define ADVANCE(b, n, count) { b += count; n -= count; } + + +/* handle input to complete an existing buffer */ +static gsize side_recv_cont(const guchar *b, gsize n) { + + gsize x = sbuf_size - sbuf_recv; + + if(n < x) { + memcpy(sbuf + sbuf_recv, b, n); + sbuf_recv += n; + return 0; + + } else { + memcpy(sbuf + sbuf_recv, b, x); + ADVANCE(b, n, x); + + if(sbuf_size == 4) { + struct mwOpaque o = { .len = 4, .data = sbuf }; + struct mwGetBuffer *gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + + if(n < x) { + guchar *t; + x += 4; + t = (guchar *) g_malloc(x); + memcpy(t, sbuf, 4); + memcpy(t+4, b, n); + + sbuf_free(); + + sbuf = t; + sbuf_size = x; + sbuf_recv = n + 4; + return 0; + + } else { + sbuf_free(); + side_process(b, x); + ADVANCE(b, n, x); + } + + } else { + side_process(sbuf+4, sbuf_size-4); + sbuf_free(); + } + } + + return n; +} + + +/* handle input when there's nothing previously buffered */ +static gsize side_recv_empty(const guchar *b, gsize n) { + struct mwOpaque o = { .len = n, .data = (guchar *) b }; + struct mwGetBuffer *gb; + gsize x; + + if(n < 4) { + sbuf = (guchar *) g_malloc0(4); + memcpy(sbuf, b, n); + sbuf_size = 4; + sbuf_recv = n; + return 0; + } + + gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + if(! x) return n - 4; + + if(n < (x + 4)) { + + x += 4; + sbuf = (guchar *) g_malloc(x); + memcpy(sbuf, b, n); + sbuf_size = x; + sbuf_recv = n; + return 0; + + } else { + ADVANCE(b, n, 4); + side_process(b, x); + ADVANCE(b, n, x); + + return n; + } +} + + +static gsize side_recv(const guchar *b, gsize n) { + + if(n && (sbuf_size == 0) && (*b & 0x80)) { + ADVANCE(b, n, 1); + } + + if(n == 0) { + return 0; + + } else if(sbuf_size > 0) { + return side_recv_cont(b, n); + + } else { + return side_recv_empty(b, n); + } +} + + +static void feed_buf(const guchar *buf, gsize n) { + guchar *b = (guchar *) buf; + gsize remain = 0; + + while(n > 0) { + remain = side_recv(b, n); + b += (n - remain); + n = remain; + } +} + + +static int read_recv() { + guchar buf[2048]; + int len; + + len = read(sock, buf, 2048); + if(len > 0) feed_buf(buf, (gsize) len); + + return len; +} + + +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + int ret = 0; + + if(cond & G_IO_IN) { + ret = read_recv(); + if(ret > 0) return TRUE; + } + + if(sock) { + g_source_remove(chan_io); + close(sock); + sock = 0; + chan = NULL; + chan_io = 0; + } + + done(); + + return FALSE; +} + + +static gboolean listen_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sockaddr_in rem; + guint len = sizeof(rem); + + printf("accepting connection\n"); + + sock = accept(sock, (struct sockaddr *) &rem, &len); + g_assert(sock > 0); + + g_source_remove(chan_io); + chan = g_io_channel_unix_new(sock); + chan_io = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, NULL); + + return FALSE; +} + + +static void init_socket(int port) { + /* start listening on the local port specifier */ + + struct sockaddr_in sin; + + sock = socket(PF_INET, SOCK_STREAM, 0); + g_assert(sock >= 0); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = PF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + g_assert_not_reached(); + + if(listen(sock, 1) < 0) + g_assert_not_reached(); + + chan = g_io_channel_unix_new(sock); + chan_io = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + listen_cb, NULL); +} + + +int main(int argc, char *argv[]) { + int port = 0; + + private = mwMpi_new(); + public = mwMpi_new(); + + if(argc > 1) { + port = atoi(argv[1]); + } + + if(!port) { + fprintf(stderr, + ( " Usage: %s local_port\n" + " Creates a locally-running sametime server which prints" + " login information to stdout\n" ), + argv[0]); + exit(1); + } + + /* @todo create signal handlers to cleanup socket */ + + init_socket(port); + + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return 0; +} + diff --git a/samples/nocipher_proxy.c b/samples/nocipher_proxy.c new file mode 100644 index 0000000..dde6485 --- /dev/null +++ b/samples/nocipher_proxy.c @@ -0,0 +1,476 @@ + +/* + Clear Channel Sametime Proxy Utility + The Meanwhile Project + + This is a tool which can act as a proxy between a client and a + sametime server, which will force all channels to be created without + any encryption method. This makes reverse-engineering much, much + easier. + + It also outputs the messages sent to and from the client to stdout + as hex pairs. If compiled with USE_HEXDUMP, output will be printed + via `hexdump -C` + + All it really does is nab all Channel Create messages, strip the + offered ciphers portion from the message and replace it with an + empty ciphers list. + + Christopher O'Brien +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +struct proxy_side { + int sock; + GIOChannel *chan; + gint chan_io; + + guchar *buf; + gsize buf_size; + gsize buf_recv; + + void (*forward)(const guchar *buf, gsize len); +}; + + +static struct proxy_side client; +static struct proxy_side server; + + +static void hexdump(const char *txt, const guchar *buf, gsize len) { + FILE *fp; + + if(txt) fprintf(stdout, "\n%s\n", txt); + fflush(stdout); + + fp = popen("hexdump -C", "w"); + fwrite(buf, len, 1, fp); + fflush(fp); + pclose(fp); +} + + +static void put_msg(struct mwMessage *msg, struct mwOpaque *o) { + struct mwPutBuffer *b; + + b = mwPutBuffer_new(); + mwMessage_put(b, msg); + mwPutBuffer_finalize(o, b); + + b = mwPutBuffer_new(); + mwOpaque_put(b, o); + mwOpaque_clear(o); + mwPutBuffer_finalize(o, b); +} + + +static void side_buf_free(struct proxy_side *s) { + g_free(s->buf); + s->buf = NULL; + s->buf_size = 0; + s->buf_recv = 0; +} + + +static void munge_redir() { + struct mwMessage *msg; + struct mwOpaque o = { 0, 0 }; + + msg = mwMessage_new(mwMessage_LOGIN_CONTINUE); + put_msg(msg, &o); + mwMessage_free(msg); + + server.forward(o.data, o.len); + + mwOpaque_clear(&o); +} + + +static void munge_create(struct proxy_side *side, + struct mwMsgChannelCreate *msg) { + + struct mwOpaque o = { 0, 0 }; + GList *l; + + for(l = msg->encrypt.items; l; l = l->next) { + mwEncryptItem_clear(l->data); + g_free(l->data); + } + g_list_free(msg->encrypt.items); + msg->encrypt.items = NULL; + + msg->encrypt.mode = 0x00; + msg->encrypt.extra = 0x00; + msg->encrypt.flag = FALSE; + + put_msg(MW_MESSAGE(msg), &o); + side->forward(o.data, o.len); + mwOpaque_clear(&o); +} + + +static void side_process(struct proxy_side *s, const guchar *buf, gsize len) { + struct mwOpaque o = { .len = len, .data = (guchar *) buf }; + struct mwGetBuffer *b; + guint16 type; + + if(! len) return; + + b = mwGetBuffer_wrap(&o); + type = guint16_peek(b); + + switch(type) { + case mwMessage_LOGIN_REDIRECT: + munge_redir(); + break; + + case mwMessage_CHANNEL_CREATE: + { + struct mwMessage *msg = mwMessage_get(b); + munge_create(s, (struct mwMsgChannelCreate *) msg); + mwMessage_free(msg); + break; + } + + default: + { + struct mwPutBuffer *pb = mwPutBuffer_new(); + struct mwOpaque po = { 0, 0 }; + mwOpaque_put(pb, &o); + mwPutBuffer_finalize(&po, pb); + s->forward(po.data, po.len); + mwOpaque_clear(&po); + } + } + + mwGetBuffer_free(b); +} + + +#define ADVANCE(b, n, count) { b += count; n -= count; } + + +/* handle input to complete an existing buffer */ +static gsize side_recv_cont(struct proxy_side *s, const guchar *b, gsize n) { + + gsize x = s->buf_size - s->buf_recv; + + if(n < x) { + memcpy(s->buf+s->buf_recv, b, n); + s->buf_recv += n; + return 0; + + } else { + memcpy(s->buf+s->buf_recv, b, x); + ADVANCE(b, n, x); + + if(s->buf_size == 4) { + struct mwOpaque o = { .len = 4, .data = s->buf }; + struct mwGetBuffer *gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + + if(n < x) { + guchar *t; + x += 4; + t = (guchar *) g_malloc(x); + memcpy(t, s->buf, 4); + memcpy(t+4, b, n); + + side_buf_free(s); + + s->buf = t; + s->buf_size = x; + s->buf_recv = n + 4; + return 0; + + } else { + side_buf_free(s); + side_process(s, b, x); + ADVANCE(b, n, x); + } + + } else { + side_process(s, s->buf+4, s->buf_size-4); + side_buf_free(s); + } + } + + return n; +} + + +/* handle input when there's nothing previously buffered */ +static gsize side_recv_empty(struct proxy_side *s, const guchar *b, gsize n) { + struct mwOpaque o = { .len = n, .data = (guchar *) b }; + struct mwGetBuffer *gb; + gsize x; + + if(n < 4) { + s->buf = (guchar *) g_malloc0(4); + memcpy(s->buf, b, n); + s->buf_size = 4; + s->buf_recv = n; + return 0; + } + + gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + if(! x) return n - 4; + + if(n < (x + 4)) { + + x += 4; + s->buf = (guchar *) g_malloc(x); + memcpy(s->buf, b, n); + s->buf_size = x; + s->buf_recv = n; + return 0; + + } else { + ADVANCE(b, n, 4); + side_process(s, b, x); + ADVANCE(b, n, x); + + return n; + } +} + + +static gsize side_recv(struct proxy_side *s, const guchar *b, gsize n) { + + if(n && (s->buf_size == 0) && (*b & 0x80)) { + ADVANCE(b, n, 1); + } + + if(n == 0) { + return 0; + + } else if(s->buf_size > 0) { + return side_recv_cont(s, b, n); + + } else { + return side_recv_empty(s, b, n); + } +} + + +static void feed_buf(struct proxy_side *side, const guchar *buf, gsize n) { + guchar *b = (guchar *) buf; + gsize remain = 0; + + g_return_if_fail(side != NULL); + + while(n > 0) { + remain = side_recv(side, b, n); + b += (n - remain); + n = remain; + } +} + + +static int read_recv(struct proxy_side *side) { + guchar buf[2048]; + int len; + + len = read(side->sock, buf, 2048); + if(len > 0) feed_buf(side, buf, (gsize) len); + + return len; +} + + +static void done() { + close(client.sock); + close(server.sock); + exit(0); +} + + +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct proxy_side *side = data; + int ret = 0; + + if(cond & G_IO_IN) { + ret = read_recv(side); + if(ret > 0) return TRUE; + } + + if(side->sock) { + g_source_remove(side->chan_io); + close(side->sock); + side->sock = 0; + side->chan = NULL; + side->chan_io = 0; + } + + done(); + + return FALSE; +} + + +static void client_cb(const guchar *buf, gsize len) { + if(server.sock) { + hexdump("client -> server", buf, len); + write(server.sock, buf, len); + } +} + + +static gboolean listen_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sockaddr_in rem; + guint len = sizeof(rem); + struct proxy_side *side = data; + int sock; + + sock = accept(side->sock, (struct sockaddr *) &rem, &len); + g_assert(sock > 0); + + g_source_remove(side->chan_io); + side->sock = sock; + side->chan = g_io_channel_unix_new(sock); + side->chan_io = g_io_add_watch(side->chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, side); + + return FALSE; +} + + +static void init_client(int port) { + /* start listening on the local port specifier */ + + struct sockaddr_in sin; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + g_assert(sock >= 0); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = PF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + g_assert_not_reached(); + + if(listen(sock, 1) < 0) + g_assert_not_reached(); + + client.forward = client_cb; + client.sock = sock; + client.chan = g_io_channel_unix_new(sock); + client.chan_io = g_io_add_watch(client.chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + listen_cb, &client); +} + + +static void server_cb(const guchar *buf, gsize len) { + if(client.sock) { + hexdump("server -> client", buf, len); + write(client.sock, buf, len); + } +} + + +/* address lookup used by init_sock */ +static void init_sockaddr(struct sockaddr_in *addr, + const char *host, int port) { + + struct hostent *hostinfo; + + addr->sin_family = AF_INET; + addr->sin_port = htons (port); + hostinfo = gethostbyname(host); + if(hostinfo == NULL) { + fprintf(stderr, "Unknown host %s.\n", host); + exit(1); + } + addr->sin_addr = *(struct in_addr *) hostinfo->h_addr; +} + + +static void init_server(const char *host, int port) { + /* connect to server on host/port */ + struct sockaddr_in srvrname; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if(sock < 0) { + fprintf(stderr, "socket failure"); + exit(1); + } + + init_sockaddr(&srvrname, host, port); + connect(sock, (struct sockaddr *)&srvrname, sizeof(srvrname)); + + server.forward = server_cb; + server.sock = sock; + server.chan = g_io_channel_unix_new(sock); + server.chan_io = g_io_add_watch(server.chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, &server); +} + + +int main(int argc, char *argv[]) { + char *host = NULL; + int client_port = 0, server_port = 0; + + memset(&client, 0, sizeof(struct proxy_side)); + memset(&server, 0, sizeof(struct proxy_side)); + + if(argc > 1) { + char *z; + + host = argv[1]; + z = host; + + host = strchr(z, ':'); + if(host) *host++ = '\0'; + client_port = atoi(z); + + z = strchr(host, ':'); + if(z) *z++ = '\0'; + server_port = atoi(z); + } + + if(!host || !*host || !client_port || !server_port) { + fprintf(stderr, + ( " Usage: %s local_port:remote_host:remote_port\n" + " Creates a locally-running sametime proxy which enforces" + " unencrypted channels\n" ), + argv[0]); + exit(1); + } + + /* @todo create signal handlers to cleanup sockets */ + + init_client(client_port); + init_server(host, server_port); + + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return 0; +} + diff --git a/samples/redirect_server.c b/samples/redirect_server.c new file mode 100644 index 0000000..91f43f7 --- /dev/null +++ b/samples/redirect_server.c @@ -0,0 +1,365 @@ + +/* + Redirecting Sametime Faux-Server + The Meanwhile Project + + This is a tool which helps to test handling of login-redirects when + you don't have a server which will issue them. It will listen for a + single incoming connection, will accept a handshake message and send + a handshake ack, and will respond to a login message with a redirect + message to a server specified on the command line. + + Christopher O'Brien +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +/** the server socket or the connected socket */ +static int sock; + +/** the io channel */ +static GIOChannel *chan; + +/** the listening event on the io channel */ +static int chan_io; + +static char *host; + +static guchar *sbuf; +static gsize sbuf_size; +static gsize sbuf_recv; + + +static void send_msg(struct mwMessage *msg) { + struct mwPutBuffer *b; + struct mwOpaque o = { 0, 0 }; + + b = mwPutBuffer_new(); + mwMessage_put(b, msg); + mwPutBuffer_finalize(&o, b); + + b = mwPutBuffer_new(); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + mwPutBuffer_finalize(&o, b); + + if(sock) write(sock, o.data, o.len); + + mwOpaque_clear(&o); +} + + +static void handshake_ack() { + struct mwMsgHandshakeAck *msg; + + msg = (struct mwMsgHandshakeAck *) + mwMessage_new(mwMessage_HANDSHAKE_ACK); + + send_msg(MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); +} + + +static void login_redir() { + struct mwMsgLoginRedirect *msg; + + msg = (struct mwMsgLoginRedirect *) + mwMessage_new(mwMessage_LOGIN_REDIRECT); + msg->host = g_strdup(host); + msg->server_id = NULL; + + send_msg(MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); +} + + +static void done() { + close(sock); + exit(0); +} + + +static void side_process(const guchar *buf, gsize len) { + struct mwOpaque o = { .len = len, .data = (guchar *) buf }; + struct mwGetBuffer *b; + guint16 type; + + if(! len) return; + + b = mwGetBuffer_wrap(&o); + type = guint16_peek(b); + + switch(type) { + case mwMessage_HANDSHAKE: + handshake_ack(); + break; + + case mwMessage_LOGIN: + login_redir(); + break; + + case mwMessage_CHANNEL_DESTROY: + done(); + break; + + default: + ; + } + + mwGetBuffer_free(b); +} + + +static void sbuf_free() { + g_free(sbuf); + sbuf = NULL; + sbuf_size = 0; + sbuf_recv = 0; +} + + +#define ADVANCE(b, n, count) { b += count; n -= count; } + + +/* handle input to complete an existing buffer */ +static gsize side_recv_cont(const guchar *b, gsize n) { + + gsize x = sbuf_size - sbuf_recv; + + if(n < x) { + memcpy(sbuf + sbuf_recv, b, n); + sbuf_recv += n; + return 0; + + } else { + memcpy(sbuf + sbuf_recv, b, x); + ADVANCE(b, n, x); + + if(sbuf_size == 4) { + struct mwOpaque o = { .len = 4, .data = sbuf }; + struct mwGetBuffer *gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + + if(n < x) { + guchar *t; + x += 4; + t = (guchar *) g_malloc(x); + memcpy(t, sbuf, 4); + memcpy(t+4, b, n); + + sbuf_free(); + + sbuf = t; + sbuf_size = x; + sbuf_recv = n + 4; + return 0; + + } else { + sbuf_free(); + side_process(b, x); + ADVANCE(b, n, x); + } + + } else { + side_process(sbuf+4, sbuf_size-4); + sbuf_free(); + } + } + + return n; +} + + +/* handle input when there's nothing previously buffered */ +static gsize side_recv_empty(const guchar *b, gsize n) { + struct mwOpaque o = { .len = n, .data = (guchar *) b }; + struct mwGetBuffer *gb; + gsize x; + + if(n < 4) { + sbuf = (guchar *) g_malloc0(4); + memcpy(sbuf, b, n); + sbuf_size = 4; + sbuf_recv = n; + return 0; + } + + gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + if(! x) return n - 4; + + if(n < (x + 4)) { + + x += 4; + sbuf = (guchar *) g_malloc(x); + memcpy(sbuf, b, n); + sbuf_size = x; + sbuf_recv = n; + return 0; + + } else { + ADVANCE(b, n, 4); + side_process(b, x); + ADVANCE(b, n, x); + + return n; + } +} + + +static gsize side_recv(const guchar *b, gsize n) { + + if(n && (sbuf_size == 0) && (*b & 0x80)) { + ADVANCE(b, n, 1); + } + + if(n == 0) { + return 0; + + } else if(sbuf_size > 0) { + return side_recv_cont(b, n); + + } else { + return side_recv_empty(b, n); + } +} + + +static void feed_buf(const guchar *buf, gsize n) { + guchar *b = (guchar *) buf; + gsize remain = 0; + + while(n > 0) { + remain = side_recv(b, n); + b += (n - remain); + n = remain; + } +} + + +static int read_recv() { + guchar buf[2048]; + int len; + + len = read(sock, buf, 2048); + if(len > 0) feed_buf(buf, (gsize) len); + + return len; +} + + +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + int ret = 0; + + if(cond & G_IO_IN) { + ret = read_recv(); + if(ret > 0) return TRUE; + } + + if(sock) { + g_source_remove(chan_io); + close(sock); + sock = 0; + chan = NULL; + chan_io = 0; + } + + done(); + + return FALSE; +} + + +static gboolean listen_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sockaddr_in rem; + guint len = sizeof(rem); + + sock = accept(sock, (struct sockaddr *) &rem, &len); + g_assert(sock > 0); + + g_source_remove(chan_io); + chan = g_io_channel_unix_new(sock); + chan_io = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, NULL); + + return FALSE; +} + + +static void init_socket(int port) { + /* start listening on the local port specifier */ + + struct sockaddr_in sin; + + sock = socket(PF_INET, SOCK_STREAM, 0); + g_assert(sock >= 0); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = PF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(INADDR_ANY); + + if(bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + g_assert_not_reached(); + + if(listen(sock, 1) < 0) + g_assert_not_reached(); + + chan = g_io_channel_unix_new(sock); + chan_io = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + listen_cb, NULL); +} + + +int main(int argc, char *argv[]) { + int port = 0; + + if(argc > 1) { + char *z; + + host = argv[1]; + z = host; + + host = strchr(z, ':'); + if(host) *host++ = '\0'; + port = atoi(z); + } + + if(!host || !*host || !port) { + fprintf(stderr, + ( " Usage: %s local_port:redirect_host\n" + " Creates a locally-running sametime proxy which redirects" + " logins to the\n specified host\n" ), + argv[0]); + exit(1); + } + + /* @todo create signal handlers to cleanup socket */ + + init_socket(port); + + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + return 0; +} + diff --git a/samples/sendmessage.c b/samples/sendmessage.c new file mode 100644 index 0000000..efc4fbf --- /dev/null +++ b/samples/sendmessage.c @@ -0,0 +1,366 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + + +#define BUF_LEN 2048 + + +/* client data should be put into a structure and associated with the + session. Then it will be available from the many call-backs + handling events from the session */ +struct sample_client { + struct mwSession *session; /* the actual meanwhile session */ + int sock; /* the socket connecting to the server */ + int sock_event; /* glib event id polling the socket */ + char *target; + char *message; +}; + + +/* handler function for when the session wants to close IO */ +static void mw_session_io_close(struct mwSession *session) { + struct sample_client *client; + + client = mwSession_getClientData(session); + if(client->sock) { + g_source_remove(client->sock_event); + close(client->sock); + client->sock = 0; + client->sock_event = 0; + } +} + + +/* handler function for when the session wants to write data */ +static int mw_session_io_write(struct mwSession *session, + const guchar *buf, gsize len) { + + struct sample_client *client; + int ret = 0; + + client = mwSession_getClientData(session); + + /* socket was already closed. */ + if(client->sock == 0) + return 1; + + while(len) { + ret = write(client->sock, buf, len); + if(ret <= 0) break; + len -= ret; + } + + if(len > 0) { + /* stop watching the socket */ + g_source_remove(client->sock_event); + + /* close the socket */ + close(client->sock); + + /* remove traces of socket from client */ + client->sock = 0; + client->sock_event = 0; + + /* return error code */ + return -1; + } + + return 0; +} + + +/* handles when a conversation has been fully established between + this client and another. */ +static void mw_im_conversation_opened(struct mwConversation *conv) { + struct mwServiceIm *srvc; + struct mwSession *session; + struct sample_client *client; + struct mwIdBlock *idb; + + /* get a reference to the client data */ + srvc = mwConversation_getService(conv); + session = mwService_getSession(MW_SERVICE(srvc)); + client = mwSession_getClientData(session); + + /* make sure that it's not someone just randomly IM-ing us... */ + idb = mwConversation_getTarget(conv); + g_return_if_fail(! strcmp(client->target, idb->user)); + + /* send the message and close the conversation */ + mwConversation_send(conv, mwImSend_PLAIN, client->message); + mwConversation_close(conv, ERR_SUCCESS); + + /* the polite way to close everything up. Will call + mw_session_stateChange after doing what needs to be done */ + mwSession_stop(session, ERR_SUCCESS); +} + + +static struct mwImHandler mw_im_handler = { + .conversation_opened = mw_im_conversation_opened, + .conversation_closed = NULL, + .conversation_recv = NULL, + .clear = NULL, +}; + + +static void mw_session_stateChange(struct mwSession *session, + enum mwSessionState state, + gpointer info) { + + struct sample_client *client; + struct mwServiceIm *service; + struct mwConversation *conv; + struct mwIdBlock idb; + + if(state == mwSession_STARTED) { + /* session is now fully started */ + + client = mwSession_getClientData(session); + g_return_if_fail(client != NULL); + + /* create the im service, add it to the session, and start it up */ + service = mwServiceIm_new(session,&mw_im_handler); + mwSession_addService(session, MW_SERVICE(service)); + mwService_start(MW_SERVICE(service)); + + /* obtain a conversation with the specified user */ + idb.user = client->target; + idb.community = NULL; + conv = mwServiceIm_getConversation(service, &idb); + + /* and open it up. When it's finally opened, the + conversation_opened handler for the IM service will be + triggered */ + mwConversation_open(conv); + + } else if(state == mwSession_STOPPED) { + /* session has stopped */ + + if(info) { + /* stopped due to an error */ + guint32 errcode; + char *err; + + errcode = GPOINTER_TO_UINT(info); + err = mwError(errcode); + fprintf(stderr, "meanwhile error %s\n", err); + g_free(err); + + exit(1); + + } else { + exit(0); + } + } +} + + +/* the session handler structure is where you should indicate what + functions will perform many of the functions necessary for the + session to operate. Among these, only io_write and io_close are + absolutely required. */ +static struct mwSessionHandler mw_session_handler = { + .io_write = mw_session_io_write, + .io_close = mw_session_io_close, + .clear = NULL, + .on_stateChange = mw_session_stateChange, + .on_setPrivacyInfo = NULL, + .on_setUserStatus = NULL, + .on_admin = NULL, +}; + + +/** called from read_cb, attempts to read available data from sock and + pass it to the session, passing back the return code from the read + call for handling in read_cb */ +static int read_recv(struct mwSession *session, int sock) { + guchar buf[BUF_LEN]; + int len; + + len = read(sock, buf, BUF_LEN); + if(len > 0) mwSession_recv(session, buf, len); + + return len; +} + + +/** callback triggered from gaim_input_add, watches the socked for + available data to be processed by the session */ +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sample_client *client = data; + int ret = 0; + int source = g_io_channel_unix_get_fd(chan); + + if(cond & G_IO_IN) { + ret = read_recv(client->session, client->sock); + } + + /* normal operation ends here */ + if(ret > 0) return TRUE; + + /* read problem occured if we're here, so we'll need to take care of + it and clean up internal state */ + + if(client->sock) { + g_source_remove(client->sock_event); + close(client->sock); + client->sock = 0; + client->sock_event = 0; + } + + return FALSE; +} + + +/* address lookup */ +static void init_sockaddr(struct sockaddr_in *addr, + const char *host, int port) { + + struct hostent *hostinfo; + + addr->sin_family = AF_INET; + addr->sin_port = htons (port); + hostinfo = gethostbyname(host); + if(hostinfo == NULL) { + fprintf(stderr, "Unknown host %s.\n", host); + exit(1); + } + addr->sin_addr = *(struct in_addr *) hostinfo->h_addr; +} + + +/* open a network socket to host:port */ +static int init_sock(const char *host, int port) { + struct sockaddr_in srvrname; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if(sock < 0) { + perror("socket failure"); + exit(1); + } + + init_sockaddr(&srvrname, host, port); + connect(sock, (struct sockaddr *)&srvrname, sizeof(srvrname)); + + return sock; +} + + +/* logging is redirected to here */ +static void mw_log_handler(const gchar *domain, GLogLevelFlags flags, + const gchar *msg, gpointer data) { +#if DEBUG + /* ok, debugging is enabled, so let's print it like normal */ + g_log_default_handler(domain, flags, msg, data); +#else + ; /* nothing! very quiet */ +#endif +} + + +int main(int argc, char *argv[]) { + + char *server; + int portno; + char *sender; + char *password; + char *recipient; + char *message; + + /* the meanwhile session itself */ + struct mwSession *session; + + /* client program data */ + struct sample_client *client; + + /* something glib uses to watch the socket for available data */ + GIOChannel *io_chan; + + if (argc < 7) { + fprintf(stderr, + "usage %s: server port sender password" + "recipient message\n", argv[0]); + exit(0); + } + + server = argv[1]; + portno = atoi(argv[2]); + sender = argv[3]; + password = argv[4]; + recipient = argv[5]; + message = argv[6]; + + /* let's redirect the output of the glib logging facilities */ + g_log_set_handler("meanwhile", + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, + mw_log_handler, NULL); + + /* set up the session stuff */ + session = mwSession_new(&mw_session_handler); + mwSession_setProperty(session, mwSession_AUTH_USER_ID, sender, NULL); + mwSession_setProperty(session, mwSession_AUTH_PASSWORD, password, NULL); + + mwSession_setProperty(session, mwSession_CLIENT_TYPE_ID, + GUINT_TO_POINTER(mwLogin_MEANWHILE), NULL); + + /* set up the client data structure with the things we need it to + remember */ + client = g_new0(struct sample_client, 1); + client->session = session; + client->sock = init_sock(server, portno); + client->target = recipient; + client->message = message; + + /* associate the client data with the session */ + mwSession_setClientData(session, client, g_free); + + /* start the session up */ + mwSession_start(session); + + /* add a watch on the socket */ + io_chan = g_io_channel_unix_new(client->sock); + client->sock_event = g_io_add_watch(io_chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, client); + + /* ... and start the glib loop */ + g_main_loop_run(g_main_loop_new(NULL, FALSE)); +} diff --git a/samples/socket.c b/samples/socket.c new file mode 100644 index 0000000..2e09d87 --- /dev/null +++ b/samples/socket.c @@ -0,0 +1,327 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + + +/** + @file socket.c + + This file is a simple demonstration of using unix socket code to + connect a mwSession to a sametime server and get it fully logged + in. It doesn't do anything after logging in. + + Here you'll find examples of: + - opening a socket to the host + - using the socket to feed data to the session + - using a session handler to allow the session to write data to the + socket + - using a session handler to allow the session to close the socket + - watching for error conditions on read/write +*/ + + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + + + +/** help text if you don't give the right number of arguments */ +#define HELP \ +"Meanwhile sample socket client\n" \ +"Usage: %s server userid password\n" \ +"\n" \ +"Connects to a sametime server and logs in with the supplied user ID\n" \ +"and password. Doesn't actually do anything useful after that.\n\n" + + + +/** how much to read from the socket in a single call */ +#define BUF_LEN 2048 + + + +/* client data should be put into a structure and associated with the + session. Then it will be available from the many call-backs + handling events from the session */ +struct sample_client { + struct mwSession *session; /* the actual meanwhile session */ + int sock; /* the socket connecting to the server */ + int sock_event; /* glib event id polling the socket */ +}; + + + +/* the io_close function from the session handler */ +static void mw_session_io_close(struct mwSession *session) { + struct sample_client *client; + + /* get the client data from the session */ + client = mwSession_getClientData(session); + + /* safety check */ + g_return_if_fail(client != NULL); + + /* if the client still has a socket to be closed, close it */ + if(client->sock) { + g_source_remove(client->sock_event); + close(client->sock); + client->sock = 0; + client->sock_event = 0; + } +} + + + +/* the io_write function from the session handler */ +static int mw_session_io_write(struct mwSession *session, + const guchar *buf, gsize len) { + + struct sample_client *client; + int ret = 0; + + /* get the client data from the session */ + client = mwSession_getClientData(session); + + /* safety check */ + g_return_val_if_fail(client != NULL, -1); + + /* socket was already closed, so we can't possibly write to it */ + if(client->sock == 0) return -1; + + /* write out the data to the socket until it's all gone */ + while(len) { + ret = write(client->sock, buf, len); + if(ret <= 0) break; + len -= ret; + } + + /* if for some reason we couldn't finish writing all the data, there + must have been a problem */ + if(len > 0) { + /* stop watching the socket */ + g_source_remove(client->sock_event); + + /* close the socket */ + close(client->sock); + + /* remove traces of socket from client */ + client->sock = 0; + client->sock_event = 0; + + /* return error code */ + return -1; + } + + /* return success code */ + return 0; +} + + + +/* the on_stateChange function from the session handler */ +static void mw_session_stateChange(struct mwSession *session, + enum mwSessionState state, + gpointer info) { + + if(state == mwSession_STARTED) { + /* at this point the session is all ready to go. */ + printf("session fully started\n"); + } +} + + + +/* the session handler structure is where you should indicate what + functions will perform many of the functions necessary for the + session to operate. Among these, only io_write and io_close are + absolutely required. */ +static struct mwSessionHandler mw_session_handler = { + .io_write = mw_session_io_write, /**< handle session to socket */ + .io_close = mw_session_io_close, /**< handle session closing socket */ + .clear = NULL, /**< cleanup function */ + .on_stateChange = mw_session_stateChange, /**< session status changed */ + .on_setPrivacyInfo = NULL, /**< received privacy information */ + .on_setUserStatus = NULL, /**< received status information */ + .on_admin = NULL, /**< received an admin message */ +}; + + + +/** called from read_cb, attempts to read available data from sock and + pass it to the session. Returns zero when the socket has been + closed, less-than zero in the event of an error, and greater than + zero for success */ +static int read_recv(struct mwSession *session, int sock) { + guchar buf[BUF_LEN]; + int len; + + len = read(sock, buf, BUF_LEN); + if(len > 0) mwSession_recv(session, buf, len); + + return len; +} + + + +/** callback registerd via g_io_add_watch in main, watches the socket + for available data to be processed by the session */ +static gboolean read_cb(GIOChannel *chan, + GIOCondition cond, + gpointer data) { + + struct sample_client *client = data; + int ret = 0; + + if(cond & G_IO_IN) { + ret = read_recv(client->session, client->sock); + + /* successful operation ends here, as the read_recv function + should only return sero or lower in the event of a disconnect + or error */ + if(ret > 0) return TRUE; + } + + /* read problem occured if we're here, so we'll need to take care of + it and clean up internal state */ + if(client->sock) { + + /* stop watching the socket */ + g_source_remove(client->sock_event); + + /* close it */ + close(client->sock); + + /* don't reference the socket or its event now that they're gone */ + client->sock = 0; + client->sock_event = 0; + } + + return FALSE; +} + + + +/* address lookup used by init_sock */ +static void init_sockaddr(struct sockaddr_in *addr, + const char *host, int port) { + + struct hostent *hostinfo; + + addr->sin_family = AF_INET; + addr->sin_port = htons (port); + hostinfo = gethostbyname(host); + if(hostinfo == NULL) { + fprintf(stderr, "Unknown host %s.\n", host); + exit(1); + } + addr->sin_addr = *(struct in_addr *) hostinfo->h_addr; +} + + + +/* open and return a network socket fd connected to host:port */ +static int init_sock(const char *host, int port) { + struct sockaddr_in srvrname; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if(sock < 0) { + fprintf(stderr, "socket failure"); + exit(1); + } + + init_sockaddr(&srvrname, host, port); + connect(sock, (struct sockaddr *)&srvrname, sizeof(srvrname)); + + return sock; +} + + + +int main(int argc, char *argv[]) { + + /* the meanwhile session itself */ + struct mwSession *session; + + /* client program data */ + struct sample_client *client; + + /* something glib uses to watch the socket for available data */ + GIOChannel *io_chan; + + /* specify host, user, pass on the command line */ + if(argc != 4) { + fprintf(stderr, HELP, *argv); + return 1; + } + + /* create the session and set the user and password */ + session = mwSession_new(&mw_session_handler); + mwSession_setProperty(session, mwSession_AUTH_USER_ID, argv[2], NULL); + mwSession_setProperty(session, mwSession_AUTH_PASSWORD, argv[3], NULL); + + + /* create the client data. This is arbitrary data that a client will + want to store along with the session for its own use */ + client = g_new0(struct sample_client, 1); + client->session = session; + + /* associate the client data with the session, specifying an + optional cleanup function which will be called upon the data when + the session is free'd via mwSession_free */ + mwSession_setClientData(session, client, g_free); + + /* set up a connection to the host */ + client->sock = init_sock(argv[1], 1533); + + /* start the session. This will cause the session to send the + handshake message (using the io_write function specified in the + session handler). */ + mwSession_start(session); + + /* add a watch on the socket. Any data returning from the server + will trigger read_cb, which will in turn read the data and pass + it to the session for interpretation */ + io_chan = g_io_channel_unix_new(client->sock); + client->sock_event = g_io_add_watch(io_chan, G_IO_IN | G_IO_ERR | G_IO_HUP, + read_cb, client); + + /* Create a new main loop and start it. This will cause the above + watch to begin actually polling the socket. Use g_idle_add, + g_timeout_add to insert events into the event loop */ + g_main_loop_run(g_main_loop_new(NULL, FALSE)); + + /* this won't happen until after the main loop finally terminates */ + return 0; +} + + diff --git a/src/.cvsignore b/src/.cvsignore new file mode 100644 index 0000000..2be4547 --- /dev/null +++ b/src/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +lib* +mw_client.h diff --git a/src/CVS/Entries b/src/CVS/Entries new file mode 100644 index 0000000..f7e0da3 --- /dev/null +++ b/src/CVS/Entries @@ -0,0 +1,39 @@ +/.cvsignore/1.3/Tue Jan 11 06:24:45 2005//Tmeanwhile_v1_1_0 +/Makefile.am/1.27/Thu Dec 15 20:09:33 2005//Tmeanwhile_v1_1_0 +/channel.c/1.29/Fri Dec 9 17:03:41 2005//Tmeanwhile_v1_1_0 +/cipher.c/1.24.2.1/Thu Jan 3 20:42:22 2008//Tmeanwhile_v1_1_0 +/common.c/1.28.2.1/Thu Jan 3 20:42:22 2008//Tmeanwhile_v1_1_0 +/error.c/1.9/Mon Jun 27 05:20:24 2005//Tmeanwhile_v1_1_0 +/message.c/1.17/Thu Oct 13 02:47:47 2005//Tmeanwhile_v1_1_0 +/mw_channel.h/1.11/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_cipher.h/1.14.2.1/Thu Jan 3 20:42:22 2008//Tmeanwhile_v1_1_0 +/mw_common.h/1.30.2.1/Thu Jan 3 20:42:22 2008//Tmeanwhile_v1_1_0 +/mw_debug.c/1.14/Sat Dec 3 03:48:12 2005//Tmeanwhile_v1_1_0 +/mw_debug.h/1.15/Sat Dec 3 03:48:12 2005//Tmeanwhile_v1_1_0 +/mw_error.h/1.6/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_message.h/1.10/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_service.h/1.9/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_session.h/1.19/Fri Dec 9 17:03:43 2005//Tmeanwhile_v1_1_0 +/mw_srvc_aware.h/1.16/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_srvc_conf.h/1.15/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_srvc_dir.h/1.8/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_srvc_ft.h/1.13/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_srvc_im.h/1.16/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_srvc_place.h/1.10/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_srvc_resolve.h/1.12/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_srvc_store.h/1.8/Wed Dec 14 17:31:26 2005//Tmeanwhile_v1_1_0 +/mw_st_list.h/1.8.2.1/Thu Jan 3 20:42:22 2008//Tmeanwhile_v1_1_0 +/mw_util.c/1.2/Thu Dec 30 18:53:23 2004//Tmeanwhile_v1_1_0 +/mw_util.h/1.2/Thu Dec 30 18:53:23 2004//Tmeanwhile_v1_1_0 +/service.c/1.17/Fri Apr 29 00:20:52 2005//Tmeanwhile_v1_1_0 +/session.c/1.53/Thu Dec 15 20:15:57 2005//Tmeanwhile_v1_1_0 +/srvc_aware.c/1.48/Fri Dec 9 17:03:43 2005//Tmeanwhile_v1_1_0 +/srvc_conf.c/1.38.2.1/Thu Jan 3 20:42:23 2008//Tmeanwhile_v1_1_0 +/srvc_dir.c/1.6/Thu Sep 15 20:37:53 2005//Tmeanwhile_v1_1_0 +/srvc_ft.c/1.17/Thu Sep 15 20:37:53 2005//Tmeanwhile_v1_1_0 +/srvc_im.c/1.39/Tue Dec 27 19:07:14 2005//Tmeanwhile_v1_1_0 +/srvc_place.c/1.12/Mon Nov 21 18:30:46 2005//Tmeanwhile_v1_1_0 +/srvc_resolve.c/1.12/Mon Oct 24 06:30:46 2005//Tmeanwhile_v1_1_0 +/srvc_store.c/1.24/Tue Nov 8 18:53:38 2005//Tmeanwhile_v1_1_0 +/st_list.c/1.23/Fri Dec 9 17:03:43 2005//Tmeanwhile_v1_1_0 +D diff --git a/src/CVS/Entries.Log b/src/CVS/Entries.Log new file mode 100644 index 0000000..b69b670 --- /dev/null +++ b/src/CVS/Entries.Log @@ -0,0 +1,3 @@ +A D/mpi//// +A D/python//// +R D/python//// diff --git a/src/CVS/Repository b/src/CVS/Repository new file mode 100644 index 0000000..32a40c6 --- /dev/null +++ b/src/CVS/Repository @@ -0,0 +1 @@ +meanwhile/src diff --git a/src/CVS/Root b/src/CVS/Root new file mode 100644 index 0000000..7717627 --- /dev/null +++ b/src/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@meanwhile.cvs.sourceforge.net:/cvsroot/meanwhile diff --git a/src/CVS/Tag b/src/CVS/Tag new file mode 100644 index 0000000..112c41e --- /dev/null +++ b/src/CVS/Tag @@ -0,0 +1 @@ +Tmeanwhile_v1_1_0 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..f1a83ee --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,61 @@ + +SUBDIRS = mpi + +lib_LTLIBRARIES = libmeanwhile.la + +mwinclude_HEADERS = \ + mw_channel.h \ + mw_cipher.h \ + mw_common.h \ + mw_error.h \ + mw_message.h \ + mw_service.h \ + mw_session.h \ + mw_srvc_aware.h \ + mw_srvc_conf.h \ + mw_srvc_dir.h \ + mw_srvc_ft.h \ + mw_srvc_im.h \ + mw_srvc_place.h \ + mw_srvc_resolve.h \ + mw_srvc_store.h \ + mw_st_list.h + +noinst_HEADERS = \ + mw_debug.h \ + mw_util.h + +mwincludedir = $(includedir)/meanwhile + +libmeanwhile_la_CFLAGS = \ + $(DEBUG_CFLAGS) $(GLIB_CFLAGS) + +libmeanwhile_la_LDFLAGS = \ + @MW_SO_OS_FLAGS@ \ + -version-info @MW_SO_VERSION@ + +libmeanwhile_la_SOURCES = \ + channel.c \ + cipher.c \ + common.c \ + error.c \ + message.c \ + service.c \ + session.c \ + srvc_aware.c \ + srvc_conf.c \ + srvc_dir.c \ + srvc_ft.c \ + srvc_im.c \ + srvc_place.c \ + srvc_resolve.c \ + srvc_store.c \ + st_list.c \ + mw_debug.c \ + mw_util.c + +libmeanwhile_la_LIBADD = $(GLIB_LIBS) mpi/libmpi.la + +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"@PACKAGE@\" + diff --git a/src/channel.c b/src/channel.c new file mode 100644 index 0000000..d7215a9 --- /dev/null +++ b/src/channel.c @@ -0,0 +1,967 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include + +#include "mw_channel.h" +#include "mw_cipher.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_util.h" + + +/** @todo reorganize this file, stuff is just strewn about */ + + +struct mwChannel { + + /** session this channel belongs to */ + struct mwSession *session; + + enum mwChannelState state; + + /** creator for incoming channel, target for outgoing channel */ + struct mwLoginInfo user; + + /* similar to data from the CreateCnl message in 8.4.1.7 */ + guint32 reserved; /**< special, unknown meaning */ + guint32 id; /**< channel ID */ + guint32 service; /**< service ID */ + guint32 proto_type; /**< service protocol type */ + guint32 proto_ver; /**< service protocol version */ + guint32 options; /**< channel options */ + + struct mwOpaque addtl_create; + struct mwOpaque addtl_accept; + + /** all those supported ciphers */ + GHashTable *supported; + guint16 offered_policy; /**< @see enum mwEncryptPolicy */ + guint16 policy; /**< @see enum mwEncryptPolicy */ + + /** cipher information determined at channel acceptance */ + struct mwCipherInstance *cipher; + + /** statistics table */ + GHashTable *stats; + + GSList *outgoing_queue; /**< queued outgoing messages */ + GSList *incoming_queue; /**< queued incoming messages */ + + struct mw_datum srvc_data; /**< service-specific data */ +}; + + +struct mwChannelSet { + struct mwSession *session; /**< owning session */ + GHashTable *map; /**< map of all channels, by ID */ + guint32 counter; /**< counter for outgoing ID */ +}; + + +static void flush_channel(struct mwChannel *); + + +static const char *state_str(enum mwChannelState state) { + switch(state) { + case mwChannel_NEW: return "new"; + case mwChannel_INIT: return "initializing"; + case mwChannel_WAIT: return "waiting"; + case mwChannel_OPEN: return "open"; + case mwChannel_DESTROY: return "closing"; + case mwChannel_ERROR: return "error"; + + case mwChannel_UNKNOWN: /* fall through */ + default: return "UNKNOWN"; + } +} + + +static void state(struct mwChannel *chan, enum mwChannelState state, + guint32 err_code) { + + g_return_if_fail(chan != NULL); + + if(chan->state == state) return; + + chan->state = state; + + if(err_code) { + g_message("channel 0x%08x state: %s (0x%08x)", + chan->id, state_str(state), err_code); + } else { + g_message("channel 0x%08x state: %s", chan->id, state_str(state)); + } +} + + +static gpointer get_stat(struct mwChannel *chan, + enum mwChannelStatField field) { + + return g_hash_table_lookup(chan->stats, (gpointer) field); +} + + +static void set_stat(struct mwChannel *chan, enum mwChannelStatField field, + gpointer val) { + + g_hash_table_insert(chan->stats, (gpointer) field, val); +} + + +#define incr_stat(chan, field, incr) \ + set_stat(chan, field, get_stat(chan, field) + incr) + + +#define timestamp_stat(chan, field) \ + set_stat(chan, field, (gpointer) time(NULL)) + + +static void sup_free(gpointer a) { + mwCipherInstance_free(a); +} + + +static struct mwCipherInstance * +get_supported(struct mwChannel *chan, guint16 id) { + + guint32 cid = (guint32) id; + return g_hash_table_lookup(chan->supported, GUINT_TO_POINTER(cid)); +} + + +static void put_supported(struct mwChannel *chan, + struct mwCipherInstance *ci) { + + struct mwCipher *cipher = mwCipherInstance_getCipher(ci); + guint32 cid = (guint32) mwCipher_getType(cipher); + g_hash_table_insert(chan->supported, GUINT_TO_POINTER(cid), ci); +} + + +struct mwChannel *mwChannel_newIncoming(struct mwChannelSet *cs, guint32 id) { + struct mwChannel *chan; + + g_return_val_if_fail(cs != NULL, NULL); + g_return_val_if_fail(cs->session != NULL, NULL); + + chan = g_new0(struct mwChannel, 1); + chan->state = mwChannel_NEW; + chan->session = cs->session; + chan->id = id; + + chan->stats = g_hash_table_new(g_direct_hash, g_direct_equal); + + chan->supported = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, sup_free); + + g_hash_table_insert(cs->map, GUINT_TO_POINTER(id), chan); + + state(chan, mwChannel_WAIT, 0); + + return chan; +} + + +struct mwChannel *mwChannel_newOutgoing(struct mwChannelSet *cs) { + guint32 id; + struct mwChannel *chan; + + g_return_val_if_fail(cs != NULL, NULL); + g_return_val_if_fail(cs->map != NULL, NULL); + + /* grab the next id, and try to make sure there isn't already a + channel using it */ + do { + id = ++cs->counter; + } while(g_hash_table_lookup(cs->map, GUINT_TO_POINTER(id))); + + chan = mwChannel_newIncoming(cs, id); + state(chan, mwChannel_INIT, 0); + + return chan; +} + + +guint32 mwChannel_getId(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, 0); + return chan->id; +} + + +struct mwSession *mwChannel_getSession(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, NULL); + return chan->session; +} + + +guint32 mwChannel_getServiceId(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, 0); + return chan->service; +} + + +struct mwService *mwChannel_getService(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, NULL); + return mwSession_getService(chan->session, chan->service); +} + + +void mwChannel_setService(struct mwChannel *chan, struct mwService *srvc) { + g_return_if_fail(chan != NULL); + g_return_if_fail(srvc != NULL); + g_return_if_fail(chan->state == mwChannel_INIT); + chan->service = mwService_getType(srvc); +} + + +gpointer mwChannel_getServiceData(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, NULL); + return mw_datum_get(&chan->srvc_data); +} + + +void mwChannel_setServiceData(struct mwChannel *chan, + gpointer data, GDestroyNotify clean) { + + g_return_if_fail(chan != NULL); + mw_datum_set(&chan->srvc_data, data, clean); +} + + +void mwChannel_removeServiceData(struct mwChannel *chan) { + g_return_if_fail(chan != NULL); + mw_datum_clear(&chan->srvc_data); +} + + +guint32 mwChannel_getProtoType(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, 0x00); + return chan->proto_type; +} + + +void mwChannel_setProtoType(struct mwChannel *chan, guint32 proto_type) { + g_return_if_fail(chan != NULL); + g_return_if_fail(chan->state == mwChannel_INIT); + chan->proto_type = proto_type; +} + + +guint32 mwChannel_getProtoVer(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, 0x00); + return chan->proto_ver; +} + + +void mwChannel_setProtoVer(struct mwChannel *chan, guint32 proto_ver) { + g_return_if_fail(chan != NULL); + g_return_if_fail(chan->state == mwChannel_INIT); + chan->proto_ver = proto_ver; +} + + +guint16 mwChannel_getEncryptPolicy(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, 0x00); + return chan->policy; +} + + +guint32 mwChannel_getOptions(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, 0x00); + return chan->options; +} + + +void mwChannel_setOptions(struct mwChannel *chan, guint32 options) { + g_return_if_fail(chan != NULL); + g_return_if_fail(chan->state == mwChannel_INIT); + chan->options = options; +} + + +struct mwLoginInfo *mwChannel_getUser(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, NULL); + return &chan->user; +} + + +struct mwOpaque *mwChannel_getAddtlCreate(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, NULL); + return &chan->addtl_create; +} + + +struct mwOpaque *mwChannel_getAddtlAccept(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, NULL); + return &chan->addtl_accept; +} + + +struct mwCipherInstance * +mwChannel_getCipherInstance(struct mwChannel *chan) { + + g_return_val_if_fail(chan != NULL, NULL); + return chan->cipher; +} + + +enum mwChannelState mwChannel_getState(struct mwChannel *chan) { + g_return_val_if_fail(chan != NULL, mwChannel_UNKNOWN); + return chan->state; +} + + +gpointer mwChannel_getStatistic(struct mwChannel *chan, + enum mwChannelStatField stat) { + + g_return_val_if_fail(chan != NULL, 0); + g_return_val_if_fail(chan->stats != NULL, 0); + + return get_stat(chan, stat); +} + + +/* send a channel create message */ +int mwChannel_create(struct mwChannel *chan) { + struct mwMsgChannelCreate *msg; + GList *list, *l; + int ret; + + g_return_val_if_fail(chan != NULL, -1); + g_return_val_if_fail(chan->state == mwChannel_INIT, -1); + g_return_val_if_fail(mwChannel_isOutgoing(chan), -1); + + msg = (struct mwMsgChannelCreate *) + mwMessage_new(mwMessage_CHANNEL_CREATE); + + msg->channel = chan->id; + msg->target.user = g_strdup(chan->user.user_id); + msg->target.community = g_strdup(chan->user.community); + msg->service = chan->service; + msg->proto_type = chan->proto_type; + msg->proto_ver = chan->proto_ver; + msg->options = chan->options; + mwOpaque_clone(&msg->addtl, &chan->addtl_create); + + list = mwChannel_getSupportedCipherInstances(chan); + if(list) { + /* offer what we have */ + for(l = list; l; l = l->next) { + struct mwEncryptItem *ei = mwCipherInstance_offer(l->data); + msg->encrypt.items = g_list_append(msg->encrypt.items, ei); + } + + /* we're easy to get along with */ + chan->offered_policy = mwEncrypt_WHATEVER; + g_list_free(list); + + } else { + /* we apparently don't support anything */ + chan->offered_policy = mwEncrypt_NONE; + } + + msg->encrypt.mode = chan->offered_policy; + msg->encrypt.extra = chan->offered_policy; + + ret = mwSession_send(chan->session, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); + + state(chan, (ret)? mwChannel_ERROR: mwChannel_WAIT, ret); + + return ret; +} + + +static void channel_open(struct mwChannel *chan) { + state(chan, mwChannel_OPEN, 0); + timestamp_stat(chan, mwChannelStat_OPENED_AT); + flush_channel(chan); +} + + +int mwChannel_accept(struct mwChannel *chan) { + struct mwSession *session; + struct mwMsgChannelAccept *msg; + struct mwCipherInstance *ci; + + int ret; + + g_return_val_if_fail(chan != NULL, -1); + g_return_val_if_fail(mwChannel_isIncoming(chan), -1); + g_return_val_if_fail(chan->state == mwChannel_WAIT, -1); + + session = chan->session; + g_return_val_if_fail(session != NULL, -1); + + msg = (struct mwMsgChannelAccept *) + mwMessage_new(mwMessage_CHANNEL_ACCEPT); + + msg->head.channel = chan->id; + msg->service = chan->service; + msg->proto_type = chan->proto_type; + msg->proto_ver = chan->proto_ver; + mwOpaque_clone(&msg->addtl, &chan->addtl_accept); + + ci = chan->cipher; + + if(! ci) { + /* automatically select a cipher if one hasn't been already */ + + switch(chan->offered_policy) { + case mwEncrypt_NONE: + mwChannel_selectCipherInstance(chan, NULL); + break; + + case mwEncrypt_RC2_40: + ci = get_supported(chan, mwCipher_RC2_40); + mwChannel_selectCipherInstance(chan, ci); + break; + + case mwEncrypt_RC2_128: + ci = get_supported(chan, mwCipher_RC2_128); + mwChannel_selectCipherInstance(chan, ci); + break; + + case mwEncrypt_WHATEVER: + case mwEncrypt_ALL: + default: + { + GList *l, *ll; + + l = mwChannel_getSupportedCipherInstances(chan); + if(l) { + /* nobody selected a cipher, so we'll just pick the last in + the list of available ones */ + for(ll = l; ll->next; ll = ll->next); + ci = ll->data; + g_list_free(l); + + mwChannel_selectCipherInstance(chan, ci); + + } else { + /* this may cause breakage, but there's really nothing else + we can do. They want something we can't provide. If they + don't like it, then they'll error the channel out */ + mwChannel_selectCipherInstance(chan, NULL); + } + } + } + } + + msg->encrypt.mode = chan->policy; /* set in selectCipherInstance */ + msg->encrypt.extra = chan->offered_policy; + + if(chan->cipher) { + msg->encrypt.item = mwCipherInstance_accept(chan->cipher); + } + + ret = mwSession_send(session, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); + + if(ret) { + state(chan, mwChannel_ERROR, ret); + } else { + channel_open(chan); + } + + return ret; +} + + +static void channel_free(struct mwChannel *chan) { + struct mwSession *s; + struct mwMessage *msg; + GSList *l; + + /* maybe no warning in the future */ + g_return_if_fail(chan != NULL); + + s = chan->session; + + mwLoginInfo_clear(&chan->user); + mwOpaque_clear(&chan->addtl_create); + mwOpaque_clear(&chan->addtl_accept); + + if(chan->supported) { + g_hash_table_destroy(chan->supported); + chan->supported = NULL; + } + + if(chan->stats) { + g_hash_table_destroy(chan->stats); + chan->stats = NULL; + } + + mwCipherInstance_free(chan->cipher); + + /* clean up the outgoing queue */ + for(l = chan->outgoing_queue; l; l = l->next) { + msg = (struct mwMessage *) l->data; + l->data = NULL; + mwMessage_free(msg); + } + g_slist_free(chan->outgoing_queue); + + /* clean up the incoming queue */ + for(l = chan->incoming_queue; l; l = l->next) { + msg = (struct mwMessage *) l->data; + l->data = NULL; + mwMessage_free(msg); + } + g_slist_free(chan->incoming_queue); + + g_free(chan); +} + + +int mwChannel_destroy(struct mwChannel *chan, + guint32 reason, struct mwOpaque *info) { + + struct mwMsgChannelDestroy *msg; + struct mwSession *session; + struct mwChannelSet *cs; + int ret; + + /* may make this not a warning in the future */ + g_return_val_if_fail(chan != NULL, 0); + + state(chan, reason? mwChannel_ERROR: mwChannel_DESTROY, reason); + + session = chan->session; + g_return_val_if_fail(session != NULL, -1); + + cs = mwSession_getChannels(session); + g_return_val_if_fail(cs != NULL, -1); + + /* compose the message */ + msg = (struct mwMsgChannelDestroy *) + mwMessage_new(mwMessage_CHANNEL_DESTROY); + msg->head.channel = chan->id; + msg->reason = reason; + if(info) mwOpaque_clone(&msg->data, info); + + /* remove the channel from the channel set */ + g_hash_table_remove(cs->map, GUINT_TO_POINTER(chan->id)); + + /* send the message */ + ret = mwSession_send(session, (struct mwMessage *) msg); + mwMessage_free(MW_MESSAGE(msg)); + + return ret; +} + + +static void queue_outgoing(struct mwChannel *chan, + struct mwMsgChannelSend *msg) { + + g_info("queue_outgoing, channel 0x%08x", chan->id); + chan->outgoing_queue = g_slist_append(chan->outgoing_queue, msg); +} + + +static int channel_send(struct mwChannel *chan, + struct mwMsgChannelSend *msg) { + + int ret = 0; + + /* if the channel is open, send and free the message. Otherwise, + queue the message to be sent once the channel is finally + opened */ + + if(chan->state == mwChannel_OPEN) { + ret = mwSession_send(chan->session, (struct mwMessage *) msg); + mwMessage_free(MW_MESSAGE(msg)); + + } else { + queue_outgoing(chan, msg); + } + + return ret; +} + + +int mwChannel_sendEncrypted(struct mwChannel *chan, + guint32 type, struct mwOpaque *data, + gboolean encrypt) { + + struct mwMsgChannelSend *msg; + + g_return_val_if_fail(chan != NULL, -1); + + msg = (struct mwMsgChannelSend *) mwMessage_new(mwMessage_CHANNEL_SEND); + msg->head.channel = chan->id; + msg->type = type; + + mwOpaque_clone(&msg->data, data); + + if(encrypt && chan->cipher) { + msg->head.options = mwMessageOption_ENCRYPT; + mwCipherInstance_encrypt(chan->cipher, &msg->data); + } + + return channel_send(chan, msg); +} + + +int mwChannel_send(struct mwChannel *chan, guint32 type, + struct mwOpaque *data) { + + return mwChannel_sendEncrypted(chan, type, data, TRUE); +} + + +static void queue_incoming(struct mwChannel *chan, + struct mwMsgChannelSend *msg) { + + /* we clone the message, because session_process will clear it once + we return */ + + struct mwMsgChannelSend *m = g_new0(struct mwMsgChannelSend, 1); + m->head.type = msg->head.type; + m->head.options = msg->head.options; + m->head.channel = msg->head.channel; + mwOpaque_clone(&m->head.attribs, &msg->head.attribs); + + m->type = msg->type; + mwOpaque_clone(&m->data, &msg->data); + + g_info("queue_incoming, channel 0x%08x", chan->id); + chan->incoming_queue = g_slist_append(chan->incoming_queue, m); +} + + +static void channel_recv(struct mwChannel *chan, + struct mwMsgChannelSend *msg) { + + struct mwService *srvc; + srvc = mwChannel_getService(chan); + + incr_stat(chan, mwChannelStat_MSG_RECV, 1); + + if(msg->head.options & mwMessageOption_ENCRYPT) { + struct mwOpaque data = { 0, 0 }; + mwOpaque_clone(&data, &msg->data); + + mwCipherInstance_decrypt(chan->cipher, &data); + mwService_recv(srvc, chan, msg->type, &data); + mwOpaque_clear(&data); + + } else { + mwService_recv(srvc, chan, msg->type, &msg->data); + } +} + + +static void flush_channel(struct mwChannel *chan) { + GSList *l; + + for(l = chan->incoming_queue; l; l = l->next) { + struct mwMsgChannelSend *msg = (struct mwMsgChannelSend *) l->data; + l->data = NULL; + + channel_recv(chan, msg); + mwMessage_free(MW_MESSAGE(msg)); + } + g_slist_free(chan->incoming_queue); + chan->incoming_queue = NULL; + + for(l = chan->outgoing_queue; l; l = l->next) { + struct mwMessage *msg = (struct mwMessage *) l->data; + l->data = NULL; + + mwSession_send(chan->session, msg); + mwMessage_free(msg); + } + g_slist_free(chan->outgoing_queue); + chan->outgoing_queue = NULL; +} + + +void mwChannel_recv(struct mwChannel *chan, struct mwMsgChannelSend *msg) { + if(chan->state == mwChannel_OPEN) { + channel_recv(chan, msg); + + } else { + queue_incoming(chan, msg); + } +} + + +struct mwChannel *mwChannel_find(struct mwChannelSet *cs, guint32 chan) { + g_return_val_if_fail(cs != NULL, NULL); + g_return_val_if_fail(cs->map != NULL, NULL); + return g_hash_table_lookup(cs->map, GUINT_TO_POINTER(chan)); +} + + +void mwChannelSet_free(struct mwChannelSet *cs) { + if(! cs) return; + if(cs->map) g_hash_table_destroy(cs->map); + g_free(cs); +} + + +struct mwChannelSet *mwChannelSet_new(struct mwSession *s) { + struct mwChannelSet *cs = g_new0(struct mwChannelSet, 1); + cs->session = s; + + /* for some reason, g_int_hash/g_int_equal cause a SIGSEGV */ + cs->map = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) channel_free); + return cs; +} + + +void mwChannel_recvCreate(struct mwChannel *chan, + struct mwMsgChannelCreate *msg) { + + struct mwSession *session; + GList *list; + struct mwService *srvc; + + g_return_if_fail(chan != NULL); + g_return_if_fail(msg != NULL); + g_return_if_fail(chan->id == msg->channel); + + session = chan->session; + g_return_if_fail(session != NULL); + + if(mwChannel_isOutgoing(chan)) { + g_warning("channel 0x%08x not an incoming channel", chan->id); + mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL); + return; + } + + chan->offered_policy = msg->encrypt.mode; + g_message("channel offered with encrypt policy 0x%04x", chan->policy); + + for(list = msg->encrypt.items; list; list = list->next) { + struct mwEncryptItem *ei = list->data; + struct mwCipher *cipher; + struct mwCipherInstance *ci; + + g_message("channel offered cipher id 0x%04x", ei->id); + cipher = mwSession_getCipher(session, ei->id); + if(! cipher) { + g_message("no such cipher found in session"); + continue; + } + + ci = mwCipher_newInstance(cipher, chan); + mwCipherInstance_offered(ci, ei); + mwChannel_addSupportedCipherInstance(chan, ci); + } + + mwLoginInfo_clone(&chan->user, &msg->creator); + chan->service = msg->service; + chan->proto_type = msg->proto_type; + chan->proto_ver = msg->proto_ver; + + srvc = mwSession_getService(session, msg->service); + if(srvc) { + mwService_recvCreate(srvc, chan, msg); + + } else { + mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL); + } +} + + +void mwChannel_recvAccept(struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + + struct mwService *srvc; + + g_return_if_fail(chan != NULL); + g_return_if_fail(msg != NULL); + g_return_if_fail(chan->id == msg->head.channel); + + if(mwChannel_isIncoming(chan)) { + g_warning("channel 0x%08x not an outgoing channel", chan->id); + mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL); + return; + } + + if(chan->state != mwChannel_WAIT) { + g_warning("channel 0x%08x state not WAIT: %s", + chan->id, state_str(chan->state)); + mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL); + return; + } + + mwLoginInfo_clone(&chan->user, &msg->acceptor); + + srvc = mwSession_getService(chan->session, chan->service); + if(! srvc) { + g_warning("no service: 0x%08x", chan->service); + mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL); + return; + } + + chan->policy = msg->encrypt.mode; + g_message("channel accepted with encrypt policy 0x%04x", chan->policy); + + if(! msg->encrypt.mode || ! msg->encrypt.item) { + /* no mode or no item means no encryption */ + mwChannel_selectCipherInstance(chan, NULL); + + } else { + guint16 cid = msg->encrypt.item->id; + struct mwCipherInstance *ci = get_supported(chan, cid); + + if(! ci) { + g_warning("not an offered cipher: 0x%04x", cid); + mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL); + return; + } + + mwCipherInstance_accepted(ci, msg->encrypt.item); + mwChannel_selectCipherInstance(chan, ci); + } + + /* mark it as open for the service */ + state(chan, mwChannel_OPEN, 0); + + /* let the service know */ + mwService_recvAccept(srvc, chan, msg); + + /* flush it if the service didn't just immediately close it */ + if(mwChannel_isState(chan, mwChannel_OPEN)) { + channel_open(chan); + } +} + + +void mwChannel_recvDestroy(struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + + struct mwChannelSet *cs; + struct mwService *srvc; + + g_return_if_fail(chan != NULL); + g_return_if_fail(msg != NULL); + g_return_if_fail(chan->id == msg->head.channel); + + state(chan, msg->reason? mwChannel_ERROR: mwChannel_DESTROY, msg->reason); + + srvc = mwChannel_getService(chan); + if(srvc) mwService_recvDestroy(srvc, chan, msg); + + cs = mwSession_getChannels(chan->session); + g_return_if_fail(cs != NULL); + g_return_if_fail(cs->map != NULL); + + g_hash_table_remove(cs->map, GUINT_TO_POINTER(chan->id)); +} + + +void mwChannel_populateSupportedCipherInstances(struct mwChannel *chan) { + struct mwSession *session; + GList *list; + + g_return_if_fail(chan != NULL); + + session = chan->session; + g_return_if_fail(session != NULL); + + for(list = mwSession_getCiphers(session); list; list = list->next) { + struct mwCipherInstance *ci = mwCipher_newInstance(list->data, chan); + if(! ci) continue; + put_supported(chan, ci); + } +} + + +void mwChannel_addSupportedCipherInstance(struct mwChannel *chan, + struct mwCipherInstance *ci) { + g_return_if_fail(chan != NULL); + g_message("channel 0x%08x added cipher %s", chan->id, + NSTR(mwCipher_getName(mwCipherInstance_getCipher(ci)))); + put_supported(chan, ci); +} + + +static void collect(gpointer a, gpointer b, gpointer c) { + GList **list = c; + *list = g_list_append(*list, b); +} + + +GList *mwChannel_getSupportedCipherInstances(struct mwChannel *chan) { + GList *list = NULL; + + g_return_val_if_fail(chan != NULL, NULL); + g_hash_table_foreach(chan->supported, collect, &list); + + return list; +} + + +void mwChannel_selectCipherInstance(struct mwChannel *chan, + struct mwCipherInstance *ci) { + struct mwCipher *c; + + g_return_if_fail(chan != NULL); + g_return_if_fail(chan->supported != NULL); + + chan->cipher = ci; + if(ci) { + guint cid; + + c = mwCipherInstance_getCipher(ci); + cid = mwCipher_getType(c); + + g_hash_table_steal(chan->supported, GUINT_TO_POINTER(cid)); + + switch(mwCipher_getType(c)) { + case mwCipher_RC2_40: + chan->policy = mwEncrypt_RC2_40; + break; + + case mwCipher_RC2_128: + chan->policy = mwEncrypt_RC2_128; + break; + + default: + /* unsure if this is bad */ + chan->policy = mwEncrypt_WHATEVER; + } + + g_message("channel 0x%08x selected cipher %s", + chan->id, NSTR(mwCipher_getName(c))); + + } else { + + chan->policy = mwEncrypt_NONE; + g_message("channel 0x%08x selected no cipher", chan->id); + } + + g_hash_table_destroy(chan->supported); + chan->supported = NULL; +} + + diff --git a/src/cipher.c b/src/cipher.c new file mode 100644 index 0000000..d2ffcb8 --- /dev/null +++ b/src/cipher.c @@ -0,0 +1,968 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include + +#include "mpi/mpi.h" + +#include "mw_channel.h" +#include "mw_cipher.h" +#include "mw_debug.h" +#include "mw_session.h" + + +struct mwMpi { + mw_mp_int i; +}; + + +/** From RFC2268 */ +static guchar PT[] = { + 0xD9, 0x78, 0xF9, 0xC4, 0x19, 0xDD, 0xB5, 0xED, + 0x28, 0xE9, 0xFD, 0x79, 0x4A, 0xA0, 0xD8, 0x9D, + 0xC6, 0x7E, 0x37, 0x83, 0x2B, 0x76, 0x53, 0x8E, + 0x62, 0x4C, 0x64, 0x88, 0x44, 0x8B, 0xFB, 0xA2, + 0x17, 0x9A, 0x59, 0xF5, 0x87, 0xB3, 0x4F, 0x13, + 0x61, 0x45, 0x6D, 0x8D, 0x09, 0x81, 0x7D, 0x32, + 0xBD, 0x8F, 0x40, 0xEB, 0x86, 0xB7, 0x7B, 0x0B, + 0xF0, 0x95, 0x21, 0x22, 0x5C, 0x6B, 0x4E, 0x82, + 0x54, 0xD6, 0x65, 0x93, 0xCE, 0x60, 0xB2, 0x1C, + 0x73, 0x56, 0xC0, 0x14, 0xA7, 0x8C, 0xF1, 0xDC, + 0x12, 0x75, 0xCA, 0x1F, 0x3B, 0xBE, 0xE4, 0xD1, + 0x42, 0x3D, 0xD4, 0x30, 0xA3, 0x3C, 0xB6, 0x26, + 0x6F, 0xBF, 0x0E, 0xDA, 0x46, 0x69, 0x07, 0x57, + 0x27, 0xF2, 0x1D, 0x9B, 0xBC, 0x94, 0x43, 0x03, + 0xF8, 0x11, 0xC7, 0xF6, 0x90, 0xEF, 0x3E, 0xE7, + 0x06, 0xC3, 0xD5, 0x2F, 0xC8, 0x66, 0x1E, 0xD7, + 0x08, 0xE8, 0xEA, 0xDE, 0x80, 0x52, 0xEE, 0xF7, + 0x84, 0xAA, 0x72, 0xAC, 0x35, 0x4D, 0x6A, 0x2A, + 0x96, 0x1A, 0xD2, 0x71, 0x5A, 0x15, 0x49, 0x74, + 0x4B, 0x9F, 0xD0, 0x5E, 0x04, 0x18, 0xA4, 0xEC, + 0xC2, 0xE0, 0x41, 0x6E, 0x0F, 0x51, 0xCB, 0xCC, + 0x24, 0x91, 0xAF, 0x50, 0xA1, 0xF4, 0x70, 0x39, + 0x99, 0x7C, 0x3A, 0x85, 0x23, 0xB8, 0xB4, 0x7A, + 0xFC, 0x02, 0x36, 0x5B, 0x25, 0x55, 0x97, 0x31, + 0x2D, 0x5D, 0xFA, 0x98, 0xE3, 0x8A, 0x92, 0xAE, + 0x05, 0xDF, 0x29, 0x10, 0x67, 0x6C, 0xBA, 0xC9, + 0xD3, 0x00, 0xE6, 0xCF, 0xE1, 0x9E, 0xA8, 0x2C, + 0x63, 0x16, 0x01, 0x3F, 0x58, 0xE2, 0x89, 0xA9, + 0x0D, 0x38, 0x34, 0x1B, 0xAB, 0x33, 0xFF, 0xB0, + 0xBB, 0x48, 0x0C, 0x5F, 0xB9, 0xB1, 0xCD, 0x2E, + 0xC5, 0xF3, 0xDB, 0x47, 0xE5, 0xA5, 0x9C, 0x77, + 0x0A, 0xA6, 0x20, 0x68, 0xFE, 0x7F, 0xC1, 0xAD +}; + + +/** prime number used in DH exchange */ +static guchar dh_prime[] = { + 0xCF, 0x84, 0xAF, 0xCE, 0x86, 0xDD, 0xFA, 0x52, + 0x7F, 0x13, 0x6D, 0x10, 0x35, 0x75, 0x28, 0xEE, + 0xFB, 0xA0, 0xAF, 0xEF, 0x80, 0x8F, 0x29, 0x17, + 0x4E, 0x3B, 0x6A, 0x9E, 0x97, 0x00, 0x01, 0x71, + 0x7C, 0x8F, 0x10, 0x6C, 0x41, 0xC1, 0x61, 0xA6, + 0xCE, 0x91, 0x05, 0x7B, 0x34, 0xDA, 0x62, 0xCB, + 0xB8, 0x7B, 0xFD, 0xC1, 0xB3, 0x5C, 0x1B, 0x91, + 0x0F, 0xEA, 0x72, 0x24, 0x9D, 0x56, 0x6B, 0x9F +}; + + +/** base used in DH exchange */ +#define DH_BASE 3 + + +struct mwMpi *mwMpi_new() { + struct mwMpi *i; + i = g_new0(struct mwMpi, 1); + mw_mp_init(&i->i); + return i; +} + + +void mwMpi_free(struct mwMpi *i) { + if(! i) return; + mw_mp_clear(&i->i); + g_free(i); +} + + +static void mwInitDHPrime(mw_mp_int *i) { + mw_mp_init(i); + mw_mp_read_unsigned_bin(i, dh_prime, 64); +} + + +void mwMpi_setDHPrime(struct mwMpi *i) { + g_return_if_fail(i != NULL); + mw_mp_read_unsigned_bin(&i->i, dh_prime, 64); +} + + +static void mwInitDHBase(mw_mp_int *i) { + mw_mp_init(i); + mw_mp_set_int(i, DH_BASE); +} + + +void mwMpi_setDHBase(struct mwMpi *i) { + g_return_if_fail(i != NULL); + mw_mp_set_int(&i->i, DH_BASE); +} + + +static void mw_mp_set_rand(mw_mp_int *i, guint bits) { + size_t len, l; + guchar *buf; + + l = len = (bits / 8) + 1; + buf = g_malloc(len); + + srand(time(NULL)); + while(l--) buf[l] = rand() & 0xff; + + buf[0] &= (0xff >> (8 - (bits % 8))); + + mw_mp_read_unsigned_bin(i, buf, len); + g_free(buf); +} + + +static void mwDHRandKeypair(mw_mp_int *private_key, mw_mp_int *public_key) { + mw_mp_int prime, base; + + mwInitDHPrime(&prime); + mwInitDHBase(&base); + + mw_mp_set_rand(private_key, 512); + mw_mp_exptmod(&base, private_key, &prime, public_key); + + mw_mp_clear(&prime); + mw_mp_clear(&base); +} + + +void mwMpi_randDHKeypair(struct mwMpi *private_key, struct mwMpi *public_key) { + g_return_if_fail(private_key != NULL); + g_return_if_fail(public_key != NULL); + + mwDHRandKeypair(&private_key->i, &public_key->i); +} + + +static void mwDHCalculateShared(mw_mp_int *shared_key, mw_mp_int *remote_key, + mw_mp_int *private_key) { + mw_mp_int prime; + + mwInitDHPrime(&prime); + mw_mp_exptmod(remote_key, private_key, &prime, shared_key); + mw_mp_clear(&prime); +} + + +void mwMpi_calculateDHShared(struct mwMpi *shared_key, struct mwMpi *remote_key, + struct mwMpi *private_key) { + + g_return_if_fail(shared_key != NULL); + g_return_if_fail(remote_key != NULL); + g_return_if_fail(private_key != NULL); + + mwDHCalculateShared(&shared_key->i, &remote_key->i, &private_key->i); +} + + +static void mwDHImportKey(mw_mp_int *key, struct mwOpaque *o) { + mw_mp_read_unsigned_bin(key, o->data, o->len); +} + + +void mwMpi_import(struct mwMpi *i, struct mwOpaque *o) { + g_return_if_fail(i != NULL); + g_return_if_fail(o != NULL); + + mwDHImportKey(&i->i, o); +} + + +static void mwDHExportKey(mw_mp_int *key, struct mwOpaque *o) { + o->len = mw_mp_unsigned_bin_size(key); + o->data = g_malloc0(o->len); + mw_mp_to_unsigned_bin(key, o->data); +} + + +void mwMpi_export(struct mwMpi *i, struct mwOpaque *o) { + g_return_if_fail(i != NULL); + g_return_if_fail(o != NULL); + + mwDHExportKey(&i->i, o); +} + + +void mwKeyRandom(guchar *key, gsize keylen) { + g_return_if_fail(key != NULL); + + srand(time(NULL)); + while(keylen--) key[keylen] = rand() & 0xff; +} + + +void mwIV_init(guchar *iv) { + iv[0] = 0x01; + iv[1] = 0x23; + iv[2] = 0x45; + iv[3] = 0x67; + iv[4] = 0x89; + iv[5] = 0xab; + iv[6] = 0xcd; + iv[7] = 0xef; +} + + +/* This does not seem to produce the same results as normal RC2 key + expansion would, but it works, so eh. It might be smart to farm + this out to mozilla or openssl */ +void mwKeyExpand(int *ekey, const guchar *key, gsize keylen) { + guchar tmp[128]; + int i, j; + + g_return_if_fail(keylen > 0); + g_return_if_fail(key != NULL); + + if(keylen > 128) keylen = 128; + + /* fill the first chunk with what key bytes we have */ + for(i = keylen; i--; tmp[i] = key[i]); + /* memcpy(tmp, key, keylen); */ + + /* build the remaining key from the given data */ + for(i = 0; keylen < 128; i++) { + tmp[keylen] = PT[ (tmp[keylen - 1] + tmp[i]) & 0xff ]; + keylen++; + } + + tmp[0] = PT[ tmp[0] & 0xff ]; + + for(i = 0, j = 0; i < 64; i++) { + ekey[i] = (tmp[j] & 0xff) | (tmp[j+1] << 8); + j += 2; + } +} + + +/* normal RC2 encryption given a full 128-byte (as 64 ints) key */ +static void mwEncryptBlock(const int *ekey, guchar *out) { + + int a, b, c, d; + int i, j; + + a = (out[7] << 8) | (out[6] & 0xff); + b = (out[5] << 8) | (out[4] & 0xff); + c = (out[3] << 8) | (out[2] & 0xff); + d = (out[1] << 8) | (out[0] & 0xff); + + for(i = 0; i < 16; i++) { + j = i * 4; + + d += ((c & (a ^ 0xffff)) + (b & a) + ekey[j++]); + d = (d << 1) | (d >> 15 & 0x0001); + + c += ((b & (d ^ 0xffff)) + (a & d) + ekey[j++]); + c = (c << 2) | (c >> 14 & 0x0003); + + b += ((a & (c ^ 0xffff)) + (d & c) + ekey[j++]); + b = (b << 3) | (b >> 13 & 0x0007); + + a += ((d & (b ^ 0xffff)) + (c & b) + ekey[j++]); + a = (a << 5) | (a >> 11 & 0x001f); + + if(i == 4 || i == 10) { + d += ekey[a & 0x003f]; + c += ekey[d & 0x003f]; + b += ekey[c & 0x003f]; + a += ekey[b & 0x003f]; + } + } + + *out++ = d & 0xff; + *out++ = (d >> 8) & 0xff; + *out++ = c & 0xff; + *out++ = (c >> 8) & 0xff; + *out++ = b & 0xff; + *out++ = (b >> 8) & 0xff; + *out++ = a & 0xff; + *out++ = (a >> 8) & 0xff; +} + + +void mwEncryptExpanded(const int *ekey, guchar *iv, + struct mwOpaque *in_data, + struct mwOpaque *out_data) { + + guchar *i = in_data->data; + gsize i_len = in_data->len; + + guchar *o; + gsize o_len; + + int x, y; + + /* pad upwards to a multiple of 8 */ + /* o_len = (i_len & -8) + 8; */ + o_len = i_len + (8 - (i_len % 8)); + o = g_malloc(o_len); + + out_data->data = o; + out_data->len = o_len; + + /* figure out the amount of padding */ + y = o_len - i_len; + + /* copy in to out, and write padding bytes */ + for(x = i_len; x--; o[x] = i[x]); + for(x = i_len; x < o_len; o[x++] = y); + /* memcpy(o, i, i_len); + memset(o + i_len, y, y); */ + + /* encrypt in blocks */ + for(x = o_len; x > 0; x -= 8) { + for(y = 8; y--; o[y] ^= iv[y]); + mwEncryptBlock(ekey, o); + for(y = 8; y--; iv[y] = o[y]); + /* memcpy(iv, o, 8); */ + o += 8; + } +} + + +void mwEncrypt(const guchar *key, gsize keylen, guchar *iv, + struct mwOpaque *in, struct mwOpaque *out) { + + int ekey[64]; + mwKeyExpand(ekey, key, keylen); + mwEncryptExpanded(ekey, iv, in, out); +} + + +static void mwDecryptBlock(const int *ekey, guchar *out) { + + int a, b, c, d; + int i, j; + + a = (out[7] << 8) | (out[6] & 0xff); + b = (out[5] << 8) | (out[4] & 0xff); + c = (out[3] << 8) | (out[2] & 0xff); + d = (out[1] << 8) | (out[0] & 0xff); + + for(i = 16; i--; ) { + j = i * 4 + 3; + + a = (a << 11) | (a >> 5 & 0x07ff); + a -= ((d & (b ^ 0xffff)) + (c & b) + ekey[j--]); + + b = (b << 13) | (b >> 3 & 0x1fff); + b -= ((a & (c ^ 0xffff)) + (d & c) + ekey[j--]); + + c = (c << 14) | (c >> 2 & 0x3fff); + c -= ((b & (d ^ 0xffff)) + (a & d) + ekey[j--]); + + d = (d << 15) | (d >> 1 & 0x7fff); + d -= ((c & (a ^ 0xffff)) + (b & a) + ekey[j--]); + + if(i == 5 || i == 11) { + a -= ekey[b & 0x003f]; + b -= ekey[c & 0x003f]; + c -= ekey[d & 0x003f]; + d -= ekey[a & 0x003f]; + } + } + + *out++ = d & 0xff; + *out++ = (d >> 8) & 0xff; + *out++ = c & 0xff; + *out++ = (c >> 8) & 0xff; + *out++ = b & 0xff; + *out++ = (b >> 8) & 0xff; + *out++ = a & 0xff; + *out++ = (a >> 8) & 0xff; +} + + +void mwDecryptExpanded(const int *ekey, guchar *iv, + struct mwOpaque *in_data, + struct mwOpaque *out_data) { + + guchar *i = in_data->data; + gsize i_len = in_data->len; + + guchar *o; + gsize o_len; + + int x, y; + + if(i_len % 8) { + /* this doesn't check to ensure that in_data->len is a multiple of + 8, which is damn well ought to be. */ + g_warning("attempting decryption of mis-sized data, %u bytes", + (guint) i_len); + } + + o = g_malloc(i_len); + o_len = i_len; + for(x = i_len; x--; o[x] = i[x]); + /* memcpy(o, i, i_len); */ + + out_data->data = o; + out_data->len = o_len; + + for(x = o_len; x > 0; x -= 8) { + /* decrypt a block */ + mwDecryptBlock(ekey, o); + + /* modify the initialization vector */ + for(y = 8; y--; o[y] ^= iv[y]); + for(y = 8; y--; iv[y] = i[y]); + /* memcpy(iv, i, 8); */ + i += 8; + o += 8; + } + + /* shorten the length by the value of the filler in the padding + bytes */ + out_data->len -= *(o - 1); +} + + +void mwDecrypt(const guchar *key, gsize keylen, guchar *iv, + struct mwOpaque *in, struct mwOpaque *out) { + + int ekey[64]; + mwKeyExpand(ekey, key, keylen); + mwDecryptExpanded(ekey, iv, in, out); +} + + + +struct mwCipher_RC2_40 { + struct mwCipher cipher; + int session_key[64]; + gboolean ready; +}; + + +struct mwCipherInstance_RC2_40 { + struct mwCipherInstance instance; + int incoming_key[64]; + guchar outgoing_iv[8]; + guchar incoming_iv[8]; +}; + + +static const char *get_name_RC2_40() { + return "RC2/40 Cipher"; +} + + +static const char *get_desc_RC2_40() { + return "RC2, 40-bit effective key"; +} + + +static int encrypt_RC2_40(struct mwCipherInstance *ci, + struct mwOpaque *data) { + + struct mwCipherInstance_RC2_40 *cir; + struct mwCipher_RC2_40 *cr; + struct mwOpaque o = { 0, 0 }; + + cir = (struct mwCipherInstance_RC2_40 *) ci; + cr = (struct mwCipher_RC2_40 *) ci->cipher; + + mwEncryptExpanded(cr->session_key, cir->outgoing_iv, data, &o); + + mwOpaque_clear(data); + data->data = o.data; + data->len = o.len; + + return 0; +} + + +static int decrypt_RC2_40(struct mwCipherInstance *ci, + struct mwOpaque *data) { + + struct mwCipherInstance_RC2_40 *cir; + struct mwCipher_RC2_40 *cr; + struct mwOpaque o = { 0, 0 }; + + cir = (struct mwCipherInstance_RC2_40 *) ci; + cr = (struct mwCipher_RC2_40 *) ci->cipher; + + mwDecryptExpanded(cir->incoming_key, cir->incoming_iv, data, &o); + + mwOpaque_clear(data); + data->data = o.data; + data->len = o.len; + + return 0; +} + + +static struct mwCipherInstance * +new_instance_RC2_40(struct mwCipher *cipher, + struct mwChannel *chan) { + + struct mwCipher_RC2_40 *cr; + struct mwCipherInstance_RC2_40 *cir; + struct mwCipherInstance *ci; + + cr = (struct mwCipher_RC2_40 *) cipher; + + /* a bit of lazy initialization here */ + if(! cr->ready) { + struct mwLoginInfo *info = mwSession_getLoginInfo(cipher->session); + mwKeyExpand(cr->session_key, (guchar *) info->login_id, 5); + cr->ready = TRUE; + } + + cir = g_new0(struct mwCipherInstance_RC2_40, 1); + ci = &cir->instance; + + ci->cipher = cipher; + ci->channel = chan; + + mwIV_init(cir->incoming_iv); + mwIV_init(cir->outgoing_iv); + + return ci; +} + + +static struct mwEncryptItem *new_item_RC2_40(struct mwCipherInstance *ci) { + struct mwEncryptItem *e; + + e = g_new0(struct mwEncryptItem, 1); + e->id = mwCipher_RC2_40; + return e; +} + + +static struct mwEncryptItem * +offer_RC2_40(struct mwCipherInstance *ci) { + return new_item_RC2_40(ci); +} + + +static void accepted_RC2_40(struct mwCipherInstance *ci, + struct mwEncryptItem *item) { + + struct mwCipherInstance_RC2_40 *cir; + struct mwLoginInfo *info; + + cir = (struct mwCipherInstance_RC2_40 *) ci; + info = mwChannel_getUser(ci->channel); + + if(info->login_id) { + mwKeyExpand(cir->incoming_key, (guchar *) info->login_id, 5); + } +} + + +static struct mwEncryptItem * +accept_RC2_40(struct mwCipherInstance *ci) { + + accepted_RC2_40(ci, NULL); + return new_item_RC2_40(ci); +} + + +struct mwCipher *mwCipher_new_RC2_40(struct mwSession *s) { + struct mwCipher_RC2_40 *cr = g_new0(struct mwCipher_RC2_40, 1); + struct mwCipher *c = &cr->cipher; + + c->session = s; + c->type = mwCipher_RC2_40; + c->get_name = get_name_RC2_40; + c->get_desc = get_desc_RC2_40; + c->new_instance = new_instance_RC2_40; + + c->offer = offer_RC2_40; + + c->accepted = accepted_RC2_40; + c->accept = accept_RC2_40; + + c->encrypt = encrypt_RC2_40; + c->decrypt = decrypt_RC2_40; + + return c; +} + + +struct mwCipher_RC2_128 { + struct mwCipher cipher; + mw_mp_int private_key; + struct mwOpaque public_key; +}; + + +struct mwCipherInstance_RC2_128 { + struct mwCipherInstance instance; + int shared[64]; /* shared secret determined via DH exchange */ + guchar outgoing_iv[8]; + guchar incoming_iv[8]; +}; + + +static const char *get_name_RC2_128() { + return "RC2/128 Cipher"; +} + + +static const char *get_desc_RC2_128() { + return "RC2, DH shared secret key"; +} + + +static struct mwCipherInstance * +new_instance_RC2_128(struct mwCipher *cipher, + struct mwChannel *chan) { + + struct mwCipher_RC2_128 *cr; + struct mwCipherInstance_RC2_128 *cir; + struct mwCipherInstance *ci; + + cr = (struct mwCipher_RC2_128 *) cipher; + + cir = g_new0(struct mwCipherInstance_RC2_128, 1); + ci = &cir->instance; + + ci->cipher = cipher; + ci->channel = chan; + + mwIV_init(cir->incoming_iv); + mwIV_init(cir->outgoing_iv); + + return ci; +} + + +static void offered_RC2_128(struct mwCipherInstance *ci, + struct mwEncryptItem *item) { + + mw_mp_int remote_key; + mw_mp_int shared; + struct mwOpaque sho = { 0, 0 }; + + struct mwCipher *c; + struct mwCipher_RC2_128 *cr; + struct mwCipherInstance_RC2_128 *cir; + + c = ci->cipher; + cr = (struct mwCipher_RC2_128 *) c; + cir = (struct mwCipherInstance_RC2_128 *) ci; + + mw_mp_init(&remote_key); + mw_mp_init(&shared); + + mwDHImportKey(&remote_key, &item->info); + mwDHCalculateShared(&shared, &remote_key, &cr->private_key); + mwDHExportKey(&shared, &sho); + + /* key expanded from the last 16 bytes of the DH shared secret. This + took me forever to figure out. 16 bytes is 128 bit. */ + /* the sh_len-16 is important, because the key len could + hypothetically start with 8bits or more unset, meaning the + exported key might be less than 64 bytes in length */ + mwKeyExpand(cir->shared, sho.data+(sho.len-16), 16); + + mw_mp_clear(&remote_key); + mw_mp_clear(&shared); + mwOpaque_clear(&sho); +} + + +static struct mwEncryptItem * +offer_RC2_128(struct mwCipherInstance *ci) { + + struct mwCipher *c; + struct mwCipher_RC2_128 *cr; + struct mwEncryptItem *ei; + + c = ci->cipher; + cr = (struct mwCipher_RC2_128 *) c; + + ei = g_new0(struct mwEncryptItem, 1); + ei->id = mwCipher_RC2_128; + mwOpaque_clone(&ei->info, &cr->public_key); + + return ei; +} + + +static void accepted_RC2_128(struct mwCipherInstance *ci, + struct mwEncryptItem *item) { + + offered_RC2_128(ci, item); +} + + +static struct mwEncryptItem * +accept_RC2_128(struct mwCipherInstance *ci) { + + return offer_RC2_128(ci); +} + + +static int encrypt_RC2_128(struct mwCipherInstance *ci, + struct mwOpaque *data) { + + struct mwCipherInstance_RC2_128 *cir; + struct mwOpaque o = { 0, 0 }; + + cir = (struct mwCipherInstance_RC2_128 *) ci; + + mwEncryptExpanded(cir->shared, cir->outgoing_iv, data, &o); + + mwOpaque_clear(data); + data->data = o.data; + data->len = o.len; + + return 0; +} + + +static int decrypt_RC2_128(struct mwCipherInstance *ci, + struct mwOpaque *data) { + + struct mwCipherInstance_RC2_128 *cir; + struct mwOpaque o = { 0, 0 }; + + cir = (struct mwCipherInstance_RC2_128 *) ci; + + mwDecryptExpanded(cir->shared, cir->incoming_iv, data, &o); + + mwOpaque_clear(data); + data->data = o.data; + data->len = o.len; + + return 0; +} + + +static void clear_RC2_128(struct mwCipher *c) { + struct mwCipher_RC2_128 *cr; + cr = (struct mwCipher_RC2_128 *) c; + + mw_mp_clear(&cr->private_key); + mwOpaque_clear(&cr->public_key); +} + + +struct mwCipher *mwCipher_new_RC2_128(struct mwSession *s) { + struct mwCipher_RC2_128 *cr; + struct mwCipher *c; + + mw_mp_int pubkey; + + cr = g_new0(struct mwCipher_RC2_128, 1); + c = &cr->cipher; + + c->session = s; + c->type = mwCipher_RC2_128; + c->get_name = get_name_RC2_128; + c->get_desc = get_desc_RC2_128; + c->new_instance = new_instance_RC2_128; + + c->offered = offered_RC2_128; + c->offer = offer_RC2_128; + + c->accepted = accepted_RC2_128; + c->accept = accept_RC2_128; + + c->encrypt = encrypt_RC2_128; + c->decrypt = decrypt_RC2_128; + + c->clear = clear_RC2_128; + + mw_mp_init(&cr->private_key); + mw_mp_init(&pubkey); + mwDHRandKeypair(&cr->private_key, &pubkey); + mwDHExportKey(&pubkey, &cr->public_key); + mw_mp_clear(&pubkey); + + return c; +} + + +struct mwSession *mwCipher_getSession(struct mwCipher *cipher) { + g_return_val_if_fail(cipher != NULL, NULL); + return cipher->session; +} + + +guint16 mwCipher_getType(struct mwCipher *cipher) { + /* oh man, this is a bad failover... who the hell decided to make + zero a real cipher id? */ + g_return_val_if_fail(cipher != NULL, 0xffff); + return cipher->type; +} + + +const char *mwCipher_getName(struct mwCipher *cipher) { + g_return_val_if_fail(cipher != NULL, NULL); + g_return_val_if_fail(cipher->get_name != NULL, NULL); + return cipher->get_name(); +} + + +const char *mwCipher_getDesc(struct mwCipher *cipher) { + g_return_val_if_fail(cipher != NULL, NULL); + g_return_val_if_fail(cipher->get_desc != NULL, NULL); + return cipher->get_desc(); +} + + +void mwCipher_free(struct mwCipher *cipher) { + if(! cipher) return; + + if(cipher->clear) + cipher->clear(cipher); + + g_free(cipher); +} + + +struct mwCipherInstance *mwCipher_newInstance(struct mwCipher *cipher, + struct mwChannel *chan) { + g_return_val_if_fail(cipher != NULL, NULL); + g_return_val_if_fail(chan != NULL, NULL); + g_return_val_if_fail(cipher->new_instance != NULL, NULL); + return cipher->new_instance(cipher, chan); +} + + +struct mwCipher *mwCipherInstance_getCipher(struct mwCipherInstance *ci) { + g_return_val_if_fail(ci != NULL, NULL); + return ci->cipher; +} + + +struct mwChannel *mwCipherInstance_getChannel(struct mwCipherInstance *ci) { + g_return_val_if_fail(ci != NULL, NULL); + return ci->channel; +} + + +void mwCipherInstance_offered(struct mwCipherInstance *ci, + struct mwEncryptItem *item) { + struct mwCipher *cipher; + + g_return_if_fail(ci != NULL); + + cipher = ci->cipher; + g_return_if_fail(cipher != NULL); + + if(cipher->offered) cipher->offered(ci, item); +} + + +struct mwEncryptItem * +mwCipherInstance_offer(struct mwCipherInstance *ci) { + struct mwCipher *cipher; + + g_return_val_if_fail(ci != NULL, NULL); + + cipher = ci->cipher; + g_return_val_if_fail(cipher != NULL, NULL); + + return cipher->offer(ci); +} + + +void mwCipherInstance_accepted(struct mwCipherInstance *ci, + struct mwEncryptItem *item) { + struct mwCipher *cipher; + + g_return_if_fail(ci != NULL); + + cipher = ci->cipher; + g_return_if_fail(cipher != NULL); + + if(cipher->accepted) cipher->accepted(ci, item); +} + + +struct mwEncryptItem * +mwCipherInstance_accept(struct mwCipherInstance *ci) { + struct mwCipher *cipher; + + g_return_val_if_fail(ci != NULL, NULL); + + cipher = ci->cipher; + g_return_val_if_fail(cipher != NULL, NULL); + + return cipher->accept(ci); +} + + +int mwCipherInstance_encrypt(struct mwCipherInstance *ci, + struct mwOpaque *data) { + struct mwCipher *cipher; + + g_return_val_if_fail(data != NULL, 0); + + if(! ci) return 0; + cipher = ci->cipher; + + g_return_val_if_fail(cipher != NULL, -1); + + return (cipher->encrypt)? + cipher->encrypt(ci, data): 0; +} + + +int mwCipherInstance_decrypt(struct mwCipherInstance *ci, + struct mwOpaque *data) { + struct mwCipher *cipher; + + g_return_val_if_fail(data != NULL, 0); + + if(! ci) return 0; + cipher = ci->cipher; + + g_return_val_if_fail(cipher != NULL, -1); + + return (cipher->decrypt)? + cipher->decrypt(ci, data): 0; +} + + +void mwCipherInstance_free(struct mwCipherInstance *ci) { + struct mwCipher *cipher; + + if(! ci) return; + + cipher = ci->cipher; + + if(cipher && cipher->clear_instance) + cipher->clear_instance(ci); + + g_free(ci); +} + diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..c8f78ca --- /dev/null +++ b/src/common.c @@ -0,0 +1,928 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include + +#include "mw_common.h" + + +/** @todo the *_get functions should make sure to clear their + structures in the event of failure, to prevent memory leaks */ + + +#define MW16_PUT(b, val) \ + *(b)++ = ((val) >> 0x08) & 0xff; \ + *(b)++ = (val) & 0xff; + + +#define MW16_GET(b, val) \ + val = (*(b)++ & 0xff) << 8; \ + val = val | (*(b)++ & 0xff); + + +#define MW32_PUT(b, val) \ + *(b)++ = ((val) >> 0x18) & 0xff; \ + *(b)++ = ((val) >> 0x10) & 0xff; \ + *(b)++ = ((val) >> 0x08) & 0xff; \ + *(b)++ = (val) & 0xff; + + +#define MW32_GET(b, val) \ + val = (*(b)++ & 0xff) << 0x18; \ + val = val | (*(b)++ & 0xff) << 0x10; \ + val = val | (*(b)++ & 0xff) << 0x08; \ + val = val | (*(b)++ & 0xff); + + +struct mwPutBuffer { + guchar *buf; /**< head of buffer */ + gsize len; /**< length of buffer */ + + guchar *ptr; /**< offset to first unused byte */ + gsize rem; /**< count of unused bytes remaining */ +}; + + +struct mwGetBuffer { + guchar *buf; /**< head of buffer */ + gsize len; /**< length of buffer */ + + guchar *ptr; /**< offset to first unused byte */ + gsize rem; /**< count of unused bytes remaining */ + + gboolean wrap; /**< TRUE to indicate buf shouldn't be freed */ + gboolean error; /**< TRUE to indicate an error */ +}; + + +#define BUFFER_USED(buffer) \ + ((buffer)->len - (buffer)->rem) + + +/** ensure that there's at least enough space remaining in the put + buffer to fit needed. */ +static void ensure_buffer(struct mwPutBuffer *b, gsize needed) { + if(b->rem < needed) { + gsize len = b->len, use = BUFFER_USED(b); + guchar *buf; + + /* newly created buffers are empty until written to, and then they + have 1024 available */ + if(! len) len = 1024; + + /* double len until it's large enough to fit needed */ + while( (len - use) < needed ) len = len << 1; + + /* create the new buffer. if there was anything in the old buffer, + copy it into the new buffer and free the old copy */ + buf = g_malloc(len); + if(b->buf) { + memcpy(buf, b->buf, use); + g_free(b->buf); + } + + /* put the new buffer into b */ + b->buf = buf; + b->len = len; + b->ptr = buf + use; + b->rem = len - use; + } +} + + +/** determine if there are at least needed bytes available in the + buffer. sets the error flag if there's not at least needed bytes + left in the buffer + + @returns true if there's enough data, false if not */ +static gboolean check_buffer(struct mwGetBuffer *b, gsize needed) { + if(! b->error) b->error = (b->rem < needed); + return ! b->error; +} + + +struct mwPutBuffer *mwPutBuffer_new() { + return g_new0(struct mwPutBuffer, 1); +} + + +void mwPutBuffer_write(struct mwPutBuffer *b, gpointer data, gsize len) { + g_return_if_fail(b != NULL); + g_return_if_fail(data != NULL); + + if(! len) return; + + ensure_buffer(b, len); + memcpy(b->ptr, data, len); + b->ptr += len; + b->rem -= len; +} + + +void mwPutBuffer_free(struct mwPutBuffer *b) { + if(! b) return; + g_free(b->buf); + g_free(b); +} + + +void mwPutBuffer_finalize(struct mwOpaque *to, struct mwPutBuffer *from) { + g_return_if_fail(to != NULL); + g_return_if_fail(from != NULL); + + to->len = BUFFER_USED(from); + to->data = from->buf; + + g_free(from); +} + + +struct mwGetBuffer *mwGetBuffer_new(struct mwOpaque *o) { + struct mwGetBuffer *b = g_new0(struct mwGetBuffer, 1); + + if(o && o->len) { + b->buf = b->ptr = g_memdup(o->data, o->len); + b->len = b->rem = o->len; + } + + return b; +} + + +struct mwGetBuffer *mwGetBuffer_wrap(const struct mwOpaque *o) { + struct mwGetBuffer *b = g_new0(struct mwGetBuffer, 1); + + if(o && o->len) { + b->buf = b->ptr = o->data; + b->len = b->rem = o->len; + } + b->wrap = TRUE; + + return b; +} + + +gsize mwGetBuffer_read(struct mwGetBuffer *b, gpointer data, gsize len) { + g_return_val_if_fail(b != NULL, 0); + g_return_val_if_fail(data != NULL, 0); + + if(b->error) return 0; + if(! len) return 0; + + if(b->rem < len) + len = b->rem; + + memcpy(data, b->ptr, len); + b->ptr += len; + b->rem -= len; + + return len; +} + + +gsize mwGetBuffer_advance(struct mwGetBuffer *b, gsize len) { + g_return_val_if_fail(b != NULL, 0); + + if(b->error) return 0; + if(! len) return 0; + + if(b->rem < len) + len = b->rem; + + b->ptr += len; + b->rem -= len; + + return len; +} + + +void mwGetBuffer_reset(struct mwGetBuffer *b) { + g_return_if_fail(b != NULL); + + b->rem = b->len; + b->ptr = b->buf; + b->error = FALSE; +} + + +gsize mwGetBuffer_remaining(struct mwGetBuffer *b) { + g_return_val_if_fail(b != NULL, 0); + return b->rem; +} + + +gboolean mwGetBuffer_error(struct mwGetBuffer *b) { + g_return_val_if_fail(b != NULL, TRUE); + return b->error; +} + + +void mwGetBuffer_free(struct mwGetBuffer *b) { + if(! b) return; + if(! b->wrap) g_free(b->buf); + g_free(b); +} + + +#define guint16_buflen() 2 + + +void guint16_put(struct mwPutBuffer *b, guint16 val) { + g_return_if_fail(b != NULL); + + ensure_buffer(b, guint16_buflen()); + MW16_PUT(b->ptr, val); + b->rem -= guint16_buflen(); +} + + +void guint16_get(struct mwGetBuffer *b, guint16 *val) { + g_return_if_fail(b != NULL); + + if(b->error) return; + g_return_if_fail(check_buffer(b, guint16_buflen())); + + MW16_GET(b->ptr, *val); + b->rem -= guint16_buflen(); +} + + +guint16 guint16_peek(struct mwGetBuffer *b) { + guchar *buf = b->buf; + guint16 r = 0; + + if(b->rem >= guint16_buflen()) + MW16_GET(buf, r); + + return r; +} + + +#define guint32_buflen() 4 + + +void guint32_put(struct mwPutBuffer *b, guint32 val) { + g_return_if_fail(b != NULL); + + ensure_buffer(b, guint32_buflen()); + MW32_PUT(b->ptr, val); + b->rem -= guint32_buflen(); +} + + +void guint32_get(struct mwGetBuffer *b, guint32 *val) { + g_return_if_fail(b != NULL); + + if(b->error) return; + g_return_if_fail(check_buffer(b, guint32_buflen())); + + MW32_GET(b->ptr, *val); + b->rem -= guint32_buflen(); +} + + +guint32 guint32_peek(struct mwGetBuffer *b) { + guchar *buf = b->buf; + guint32 r = 0; + + if(b->rem >= guint32_buflen()) + MW32_GET(buf, r); + + return r; +} + + +#define gboolean_buflen() 1 + + +void gboolean_put(struct mwPutBuffer *b, gboolean val) { + g_return_if_fail(b != NULL); + + ensure_buffer(b, gboolean_buflen()); + *(b->ptr) = !! val; + b->ptr++; + b->rem--; +} + + +void gboolean_get(struct mwGetBuffer *b, gboolean *val) { + g_return_if_fail(b != NULL); + + if(b->error) return; + g_return_if_fail(check_buffer(b, gboolean_buflen())); + + *val = !! *(b->ptr); + b->ptr++; + b->rem--; +} + + +gboolean gboolean_peek(struct mwGetBuffer *b) { + gboolean v = FALSE; + + if(b->rem >= gboolean_buflen()) + v = !! *(b->ptr); + + return v; +} + + +static gboolean mw_streq(const char *a, const char *b) { + return (a == b) || (a && b && !strcmp(a, b)); +} + + +void mwString_put(struct mwPutBuffer *b, const char *val) { + gsize len = 0; + + g_return_if_fail(b != NULL); + + if(val) len = strlen(val); + + guint16_put(b, (guint16) len); + + if(len) { + ensure_buffer(b, len); + memcpy(b->ptr, val, len); + b->ptr += len; + b->rem -= len; + } +} + + +void mwString_get(struct mwGetBuffer *b, char **val) { + guint16 len = 0; + + g_return_if_fail(b != NULL); + g_return_if_fail(val != NULL); + + *val = NULL; + + if(b->error) return; + guint16_get(b, &len); + + g_return_if_fail(check_buffer(b, (gsize) len)); + + if(len) { + *val = g_malloc0(len + 1); + memcpy(*val, b->ptr, len); + b->ptr += len; + b->rem -= len; + } +} + + +void mwOpaque_put(struct mwPutBuffer *b, const struct mwOpaque *o) { + gsize len; + + g_return_if_fail(b != NULL); + + if(! o) { + guint32_put(b, 0x00); + return; + } + + len = o->len; + if(len) + g_return_if_fail(o->data != NULL); + + guint32_put(b, (guint32) len); + + if(len) { + ensure_buffer(b, len); + memcpy(b->ptr, o->data, len); + b->ptr += len; + b->rem -= len; + } +} + + +void mwOpaque_get(struct mwGetBuffer *b, struct mwOpaque *o) { + guint32 tmp = 0; + + g_return_if_fail(b != NULL); + g_return_if_fail(o != NULL); + + o->len = 0; + o->data = NULL; + + if(b->error) return; + guint32_get(b, &tmp); + + g_return_if_fail(check_buffer(b, (gsize) tmp)); + + o->len = (gsize) tmp; + if(tmp > 0) { + o->data = g_memdup(b->ptr, tmp); + b->ptr += tmp; + b->rem -= tmp; + } +} + + +void mwOpaque_clear(struct mwOpaque *o) { + if(! o) return; + g_free(o->data); + o->data = NULL; + o->len = 0; +} + + +void mwOpaque_free(struct mwOpaque *o) { + if(! o) return; + g_free(o->data); + g_free(o); +} + + +void mwOpaque_clone(struct mwOpaque *to, const struct mwOpaque *from) { + g_return_if_fail(to != NULL); + + to->len = 0; + to->data = NULL; + + if(from) { + to->len = from->len; + if(to->len) + to->data = g_memdup(from->data, to->len); + } +} + + +/* 8.2 Common Structures */ +/* 8.2.1 Login Info block */ + + +void mwLoginInfo_put(struct mwPutBuffer *b, const struct mwLoginInfo *login) { + g_return_if_fail(b != NULL); + g_return_if_fail(login != NULL); + + mwString_put(b, login->login_id); + guint16_put(b, login->type); + mwString_put(b, login->user_id); + mwString_put(b, login->user_name); + mwString_put(b, login->community); + gboolean_put(b, login->full); + + if(login->full) { + mwString_put(b, login->desc); + guint32_put(b, login->ip_addr); + mwString_put(b, login->server_id); + } +} + + +void mwLoginInfo_get(struct mwGetBuffer *b, struct mwLoginInfo *login) { + g_return_if_fail(b != NULL); + g_return_if_fail(login != NULL); + + if(b->error) return; + + mwString_get(b, &login->login_id); + guint16_get(b, &login->type); + mwString_get(b, &login->user_id); + mwString_get(b, &login->user_name); + mwString_get(b, &login->community); + gboolean_get(b, &login->full); + + if(login->full) { + mwString_get(b, &login->desc); + guint32_get(b, &login->ip_addr); + mwString_get(b, &login->server_id); + } +} + + +void mwLoginInfo_clear(struct mwLoginInfo *login) { + if(! login) return; + + g_free(login->login_id); + g_free(login->user_id); + g_free(login->user_name); + g_free(login->community); + g_free(login->desc); + g_free(login->server_id); + + memset(login, 0x00, sizeof(struct mwLoginInfo)); +} + + +void mwLoginInfo_clone(struct mwLoginInfo *to, + const struct mwLoginInfo *from) { + + g_return_if_fail(to != NULL); + g_return_if_fail(from != NULL); + + to->login_id= g_strdup(from->login_id); + to->type = from->type; + to->user_id = g_strdup(from->user_id); + to->user_name = g_strdup(from->user_name); + to->community = g_strdup(from->community); + + if( (to->full = from->full) ) { + to->desc = g_strdup(from->desc); + to->ip_addr = from->ip_addr; + to->server_id = g_strdup(from->server_id); + } +} + + +/* 8.2.2 Private Info Block */ + + +void mwUserItem_put(struct mwPutBuffer *b, const struct mwUserItem *user) { + g_return_if_fail(b != NULL); + g_return_if_fail(user != NULL); + + gboolean_put(b, user->full); + mwString_put(b, user->id); + mwString_put(b, user->community); + + if(user->full) + mwString_put(b, user->name); +} + + +void mwUserItem_get(struct mwGetBuffer *b, struct mwUserItem *user) { + g_return_if_fail(b != NULL); + g_return_if_fail(user != NULL); + + if(b->error) return; + + gboolean_get(b, &user->full); + mwString_get(b, &user->id); + mwString_get(b, &user->community); + + if(user->full) + mwString_get(b, &user->name); +} + + +void mwUserItem_clear(struct mwUserItem *user) { + if(! user) return; + + g_free(user->id); + g_free(user->community); + g_free(user->name); + + memset(user, 0x00, sizeof(struct mwUserItem)); +} + + +void mwUserItem_clone(struct mwUserItem *to, + const struct mwUserItem *from) { + + g_return_if_fail(to != NULL); + g_return_if_fail(from != NULL); + + to->full = from->full; + to->id = g_strdup(from->id); + to->community = g_strdup(from->community); + to->name = (to->full)? g_strdup(from->name): NULL; +} + + +void mwPrivacyInfo_put(struct mwPutBuffer *b, + const struct mwPrivacyInfo *info) { + guint32 c; + + g_return_if_fail(b != NULL); + g_return_if_fail(info != NULL); + + gboolean_put(b, info->deny); + guint32_put(b, info->count); + + for(c = info->count; c--; ) mwUserItem_put(b, info->users + c); +} + + +void mwPrivacyInfo_get(struct mwGetBuffer *b, struct mwPrivacyInfo *info) { + g_return_if_fail(b != NULL); + g_return_if_fail(info != NULL); + + if(b->error) return; + + gboolean_get(b, &info->deny); + guint32_get(b, &info->count); + + if(info->count) { + guint32 c = info->count; + info->users = g_new0(struct mwUserItem, c); + while(c--) mwUserItem_get(b, info->users + c); + } +} + + +void mwPrivacyInfo_clone(struct mwPrivacyInfo *to, + const struct mwPrivacyInfo *from) { + + guint32 c; + + g_return_if_fail(to != NULL); + g_return_if_fail(from != NULL); + + to->deny = from->deny; + c = to->count = from->count; + + to->users = g_new0(struct mwUserItem, c); + while(c--) mwUserItem_clone(to->users+c, from->users+c); +} + + +void mwPrivacyInfo_clear(struct mwPrivacyInfo *info) { + struct mwUserItem *u; + guint32 c; + + g_return_if_fail(info != NULL); + + u = info->users; + c = info->count; + + while(c--) mwUserItem_clear(u + c); + g_free(u); + + info->count = 0; + info->users = NULL; +} + + +/* 8.2.3 User Status Block */ + + +void mwUserStatus_put(struct mwPutBuffer *b, + const struct mwUserStatus *stat) { + + g_return_if_fail(b != NULL); + g_return_if_fail(stat != NULL); + + guint16_put(b, stat->status); + guint32_put(b, stat->time); + mwString_put(b, stat->desc); +} + + +void mwUserStatus_get(struct mwGetBuffer *b, struct mwUserStatus *stat) { + g_return_if_fail(b != NULL); + g_return_if_fail(stat != NULL); + + if(b->error) return; + + guint16_get(b, &stat->status); + guint32_get(b, &stat->time); + mwString_get(b, &stat->desc); +} + + +void mwUserStatus_clear(struct mwUserStatus *stat) { + if(! stat) return; + g_free(stat->desc); + memset(stat, 0x00, sizeof(struct mwUserStatus)); +} + + +void mwUserStatus_clone(struct mwUserStatus *to, + const struct mwUserStatus *from) { + + g_return_if_fail(to != NULL); + g_return_if_fail(from != NULL); + + to->status = from->status; + to->time = from->time; + to->desc = g_strdup(from->desc); +} + + +/* 8.2.4 ID Block */ + + +void mwIdBlock_put(struct mwPutBuffer *b, const struct mwIdBlock *id) { + g_return_if_fail(b != NULL); + g_return_if_fail(id != NULL); + + mwString_put(b, id->user); + mwString_put(b, id->community); +} + + +void mwIdBlock_get(struct mwGetBuffer *b, struct mwIdBlock *id) { + g_return_if_fail(b != NULL); + g_return_if_fail(id != NULL); + + if(b->error) return; + + mwString_get(b, &id->user); + mwString_get(b, &id->community); +} + + +void mwIdBlock_clear(struct mwIdBlock *id) { + if(! id) return; + + g_free(id->user); + id->user = NULL; + + g_free(id->community); + id->community = NULL; +} + + +void mwIdBlock_clone(struct mwIdBlock *to, const struct mwIdBlock *from) { + g_return_if_fail(to != NULL); + g_return_if_fail(from != NULL); + + to->user = g_strdup(from->user); + to->community = g_strdup(from->community); +} + + +guint mwIdBlock_hash(const struct mwIdBlock *idb) { + return (idb)? g_str_hash(idb->user): 0; +} + + +gboolean mwIdBlock_equal(const struct mwIdBlock *a, + const struct mwIdBlock *b) { + + g_return_val_if_fail(a != NULL, FALSE); + g_return_val_if_fail(b != NULL, FALSE); + + return ( mw_streq(a->user, b->user) && + mw_streq(a->community, b->community) ); +} + + +/* 8.2.5 Encryption Block */ + +/** @todo I think this can be put into cipher */ + +void mwEncryptItem_put(struct mwPutBuffer *b, + const struct mwEncryptItem *ei) { + + g_return_if_fail(b != NULL); + g_return_if_fail(ei != NULL); + + guint16_put(b, ei->id); + mwOpaque_put(b, &ei->info); + +} + + +void mwEncryptItem_get(struct mwGetBuffer *b, struct mwEncryptItem *ei) { + g_return_if_fail(b != NULL); + g_return_if_fail(ei != NULL); + + if(b->error) return; + + guint16_get(b, &ei->id); + mwOpaque_get(b, &ei->info); +} + + +void mwEncryptItem_clear(struct mwEncryptItem *ei) { + if(! ei) return; + ei->id = 0x0000; + mwOpaque_clear(&ei->info); +} + + +void mwEncryptItem_free(struct mwEncryptItem *ei) { + mwEncryptItem_clear(ei); + g_free(ei); +} + + +/* 8.4.2.1 Awareness ID Block */ + + +/** @todo move this into srvc_aware */ + +void mwAwareIdBlock_put(struct mwPutBuffer *b, + const struct mwAwareIdBlock *idb) { + + g_return_if_fail(b != NULL); + g_return_if_fail(idb != NULL); + + guint16_put(b, idb->type); + mwString_put(b, idb->user); + mwString_put(b, idb->community); +} + + +void mwAwareIdBlock_get(struct mwGetBuffer *b, struct mwAwareIdBlock *idb) { + g_return_if_fail(b != NULL); + g_return_if_fail(idb != NULL); + + if(b->error) return; + + guint16_get(b, &idb->type); + mwString_get(b, &idb->user); + mwString_get(b, &idb->community); +} + + +void mwAwareIdBlock_clone(struct mwAwareIdBlock *to, + const struct mwAwareIdBlock *from) { + + g_return_if_fail(to != NULL); + g_return_if_fail(from != NULL); + + to->type = from->type; + to->user = g_strdup(from->user); + to->community = g_strdup(from->community); +} + + +void mwAwareIdBlock_clear(struct mwAwareIdBlock *idb) { + if(! idb) return; + g_free(idb->user); + g_free(idb->community); + memset(idb, 0x00, sizeof(struct mwAwareIdBlock)); +} + + +guint mwAwareIdBlock_hash(const struct mwAwareIdBlock *a) { + return (a)? g_str_hash(a->user): 0; +} + + +gboolean mwAwareIdBlock_equal(const struct mwAwareIdBlock *a, + const struct mwAwareIdBlock *b) { + + g_return_val_if_fail(a != NULL, FALSE); + g_return_val_if_fail(b != NULL, FALSE); + + return ( (a->type == b->type) && + mw_streq(a->user, b->user) && + mw_streq(a->community, b->community) ); +} + + +/* 8.4.2.4 Snapshot */ + +void mwAwareSnapshot_get(struct mwGetBuffer *b, struct mwAwareSnapshot *idb) { + guint32 end_of_block; + + g_return_if_fail(b != NULL); + g_return_if_fail(idb != NULL); + + guint32_get(b, &end_of_block); + mwAwareIdBlock_get(b, &idb->id); + mwString_get(b, &idb->group); + gboolean_get(b, &idb->online); + + if(idb->online) { + mwString_get(b, &idb->alt_id); + mwUserStatus_get(b, &idb->status); + mwString_get(b, &idb->name); + } + + if(b->ptr < b->buf + end_of_block) { + mwGetBuffer_advance(b, b->buf + end_of_block - b->ptr); + } +} + + +void mwAwareSnapshot_clone(struct mwAwareSnapshot *to, + const struct mwAwareSnapshot *from) { + + g_return_if_fail(to != NULL); + g_return_if_fail(from != NULL); + + mwAwareIdBlock_clone(&to->id, &from->id); + if( (to->online = from->online) ) { + to->alt_id = g_strdup(from->alt_id); + mwUserStatus_clone(&to->status, &from->status); + to->name = g_strdup(from->name); + to->group = g_strdup(from->group); + } +} + + +void mwAwareSnapshot_clear(struct mwAwareSnapshot *idb) { + if(! idb) return; + mwAwareIdBlock_clear(&idb->id); + mwUserStatus_clear(&idb->status); + g_free(idb->alt_id); + g_free(idb->name); + g_free(idb->group); + memset(idb, 0x00, sizeof(struct mwAwareSnapshot)); +} + diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..23c9559 --- /dev/null +++ b/src/error.c @@ -0,0 +1,97 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include + +#include "mw_error.h" + + +static char *err_to_str(guint32 code) { + static char b[11]; /* 0x12345678 + NULL terminator */ + sprintf((char *) b, "0x%08x", code); + b[10] = '\0'; + return b; +} + + +#define CASE(val, str) \ +case val: \ + m = str; \ + break; + + +char* mwError(guint32 code) { + const char *m; + + switch(code) { + + /* 8.3.1.1 General error/success codes */ + CASE(ERR_SUCCESS, "Success"); + CASE(ERR_FAILURE, "General failure"); + CASE(ERR_REQUEST_DELAY, "Request delayed"); + CASE(ERR_REQUEST_INVALID, "Request is invalid"); + CASE(ERR_NOT_AUTHORIZED, "Not authorized"); + CASE(ERR_NO_USER, "User is not online"); + CASE(ERR_CHANNEL_NO_SUPPORT, "Requested channel is not supported"); + CASE(ERR_CHANNEL_EXISTS, "Requested channel already exists"); + CASE(ERR_SERVICE_NO_SUPPORT, "Requested service is not supported"); + CASE(ERR_PROTOCOL_NO_SUPPORT, "Requested protocol is not supported"); + CASE(ERR_VERSION_NO_SUPPORT, "Version is not supported"); + CASE(ERR_USER_SKETCHY, "User is invalid or not trusted"); + CASE(ERR_ALREADY_INITIALIZED, "Already initialized"); + CASE(ERR_ENCRYPT_NO_SUPPORT, "Encryption method not supported"); + CASE(ERR_NO_COMMON_ENCRYPT, "No common encryption method"); + + /* 8.3.1.2 Connection/disconnection errors */ + CASE(VERSION_MISMATCH, "Version mismatch"); + CASE(FAT_MESSAGE, "Message is too large"); + CASE(CONNECTION_BROKEN, "Connection broken"); + CASE(CONNECTION_ABORTED, "Connection aborted"); + CASE(CONNECTION_REFUSED, "Connection refused"); + CASE(CONNECTION_RESET, "Connection reset"); + CASE(CONNECTION_TIMED, "Connection timed out"); + CASE(CONNECTION_CLOSED, "Connection closed"); + CASE(INCORRECT_LOGIN, "Incorrect Username/Password"); + CASE(VERIFICATION_DOWN, "Login verification down or unavailable"); + CASE(GUEST_IN_USE, "The guest name is currently being used"); + CASE(MULTI_SERVER_LOGIN, "Login to two different servers concurrently"); + CASE(MULTI_SERVER_LOGIN2, "Login to two different servers concurrently"); + CASE(SERVER_BROKEN, "Server misconfiguration"); + + /* 8.3.1.3 Client error codes */ + CASE(ERR_CLIENT_USER_GONE, "User is not online"); + CASE(ERR_CLIENT_USER_DND, "User is in Do Not Disturb mode"); + CASE(ERR_CLIENT_USER_ELSEWHERE, "Already logged in elsewhere"); + + /* 8.3.1.4 IM error codes */ + CASE(ERR_IM_COULDNT_REGISTER, "Cannot register a reserved type"); + CASE(ERR_IM_ALREADY_REGISTERED, "Requested type is already registered"); + CASE(ERR_IM_NOT_REGISTERED, "Requested type is not registered"); + + default: + m = err_to_str(code); + } + + return g_strdup(m); +} + + +#undef CASE diff --git a/src/message.c b/src/message.c new file mode 100644 index 0000000..f9afec4 --- /dev/null +++ b/src/message.c @@ -0,0 +1,853 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#include "mw_debug.h" +#include "mw_message.h" + + +/* 7.1 Layering and message encapsulation */ +/* 7.1.1 The Sametime Message Header */ + + +static void mwMessageHead_put(struct mwPutBuffer *b, struct mwMessage *msg) { + guint16_put(b, msg->type); + guint16_put(b, msg->options); + guint32_put(b, msg->channel); + + if(msg->options & mwMessageOption_HAS_ATTRIBS) + mwOpaque_put(b, &msg->attribs); +} + + +static void mwMessageHead_get(struct mwGetBuffer *b, struct mwMessage *msg) { + + if(mwGetBuffer_error(b)) return; + + guint16_get(b, &msg->type); + guint16_get(b, &msg->options); + guint32_get(b, &msg->channel); + + if(msg->options & mwMessageOption_HAS_ATTRIBS) + mwOpaque_get(b, &msg->attribs); +} + + +static void mwMessageHead_clone(struct mwMessage *to, + struct mwMessage *from) { + + to->type = from->type; + to->options = from->options; + to->channel = from->channel; + mwOpaque_clone(&to->attribs, &from->attribs); +} + + +static void mwMessageHead_clear(struct mwMessage *msg) { + mwOpaque_clear(&msg->attribs); +} + + +/* 8.4 Messages */ +/* 8.4.1 Basic Community Messages */ +/* 8.4.1.1 Handshake */ + + +static void HANDSHAKE_put(struct mwPutBuffer *b, struct mwMsgHandshake *msg) { + guint16_put(b, msg->major); + guint16_put(b, msg->minor); + guint32_put(b, msg->head.channel); + guint32_put(b, msg->srvrcalc_addr); + guint16_put(b, msg->login_type); + guint32_put(b, msg->loclcalc_addr); + + if(msg->major >= 0x001e && msg->minor >= 0x001d) { + guint16_put(b, msg->unknown_a); + guint32_put(b, msg->unknown_b); + mwString_put(b, msg->local_host); + } +} + + +static void HANDSHAKE_get(struct mwGetBuffer *b, struct mwMsgHandshake *msg) { + if(mwGetBuffer_error(b)) return; + + guint16_get(b, &msg->major); + guint16_get(b, &msg->minor); + guint32_get(b, &msg->head.channel); + guint32_get(b, &msg->srvrcalc_addr); + guint16_get(b, &msg->login_type); + guint32_get(b, &msg->loclcalc_addr); + + if(msg->major >= 0x001e && msg->minor >= 0x001d) { + guint16_get(b, &msg->unknown_a); + guint32_get(b, &msg->unknown_b); + mwString_get(b, &msg->local_host); + } +} + + +static void HANDSHAKE_clear(struct mwMsgHandshake *msg) { + ; /* nothing to clean up */ +} + + +/* 8.4.1.2 HandshakeAck */ + + +static void HANDSHAKE_ACK_get(struct mwGetBuffer *b, + struct mwMsgHandshakeAck *msg) { + + if(mwGetBuffer_error(b)) return; + + guint16_get(b, &msg->major); + guint16_get(b, &msg->minor); + guint32_get(b, &msg->srvrcalc_addr); + + /** @todo: get a better handle on what versions support what parts + of this message. eg: minor version 0x0018 doesn't send the + following */ + if(msg->major >= 0x1e && msg->minor > 0x18) { + guint32_get(b, &msg->magic); + mwOpaque_get(b, &msg->data); + } +} + + +static void HANDSHAKE_ACK_put(struct mwPutBuffer *b, + struct mwMsgHandshakeAck *msg) { + + guint16_put(b, msg->major); + guint16_put(b, msg->minor); + guint32_put(b, msg->srvrcalc_addr); + + if(msg->major >= 0x1e && msg->minor > 0x18) { + guint32_put(b, msg->magic); + mwOpaque_put(b, &msg->data); + } +} + + +static void HANDSHAKE_ACK_clear(struct mwMsgHandshakeAck *msg) { + mwOpaque_clear(&msg->data); +} + + +/* 8.4.1.3 Login */ + + +static void LOGIN_put(struct mwPutBuffer *b, struct mwMsgLogin *msg) { + guint16_put(b, msg->login_type); + mwString_put(b, msg->name); + + /* ordering reversed from houri draft?? */ + mwOpaque_put(b, &msg->auth_data); + guint16_put(b, msg->auth_type); + + guint16_put(b, 0x0000); /* unknown */ +} + + +static void LOGIN_get(struct mwGetBuffer *b, struct mwMsgLogin *msg) { + if(mwGetBuffer_error(b)) return; + + guint16_get(b, &msg->login_type); + mwString_get(b, &msg->name); + mwOpaque_get(b, &msg->auth_data); + guint16_get(b, &msg->auth_type); +} + + +static void LOGIN_clear(struct mwMsgLogin *msg) { + g_free(msg->name); msg->name = NULL; + mwOpaque_clear(&msg->auth_data); +} + + +/* 8.4.1.4 LoginAck */ + + +static void LOGIN_ACK_get(struct mwGetBuffer *b, struct mwMsgLoginAck *msg) { + guint16 junk; + + if(mwGetBuffer_error(b)) return; + + mwLoginInfo_get(b, &msg->login); + guint16_get(b, &junk); + mwPrivacyInfo_get(b, &msg->privacy); + mwUserStatus_get(b, &msg->status); +} + + +static void LOGIN_ACK_clear(struct mwMsgLoginAck *msg) { + mwLoginInfo_clear(&msg->login); + mwPrivacyInfo_clear(&msg->privacy); + mwUserStatus_clear(&msg->status); +} + + +/* 8.4.1.5 LoginCont */ + + +static void LOGIN_CONTINUE_put(struct mwPutBuffer *b, + struct mwMsgLoginContinue *msg) { + + ; /* nothing but a message header */ +} + + +static void LOGIN_CONTINUE_get(struct mwGetBuffer *b, + struct mwMsgLoginContinue *msg) { + + ; /* nothing but a message header */ +} + + +static void LOGIN_CONTINUE_clear(struct mwMsgLoginContinue *msg) { + ; /* this is a very simple message */ +} + + +/* 8.4.1.6 AuthPassed */ + + +static void LOGIN_REDIRECT_get(struct mwGetBuffer *b, + struct mwMsgLoginRedirect *msg) { + + if(mwGetBuffer_error(b)) return; + mwString_get(b, &msg->host); + mwString_get(b, &msg->server_id); +} + + +static void LOGIN_REDIRECT_put(struct mwPutBuffer *b, + struct mwMsgLoginRedirect *msg) { + mwString_put(b, msg->host); + mwString_put(b, msg->server_id); +} + + +static void LOGIN_REDIRECT_clear(struct mwMsgLoginRedirect *msg) { + g_free(msg->host); + msg->host = NULL; + + g_free(msg->server_id); + msg->server_id = NULL; +} + + +/* 8.4.1.7 CreateCnl */ + + +static void enc_offer_put(struct mwPutBuffer *b, struct mwEncryptOffer *enc) { + guint16_put(b, enc->mode); + + if(enc->items) { + guint32 count; + struct mwPutBuffer *p; + struct mwOpaque o; + GList *list; + + /* write the count, items, extra, and flag into a tmp buffer, + render that buffer into an opaque, and write it into b */ + + count = g_list_length(enc->items); + p = mwPutBuffer_new(); + + guint32_put(p, count); + for(list = enc->items; list; list = list->next) { + mwEncryptItem_put(p, list->data); + } + + guint16_put(p, enc->extra); + gboolean_put(p, enc->flag); + + mwPutBuffer_finalize(&o, p); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + } +} + + +static void CHANNEL_CREATE_put(struct mwPutBuffer *b, + struct mwMsgChannelCreate *msg) { + + guint32_put(b, msg->reserved); + guint32_put(b, msg->channel); + mwIdBlock_put(b, &msg->target); + guint32_put(b, msg->service); + guint32_put(b, msg->proto_type); + guint32_put(b, msg->proto_ver); + guint32_put(b, msg->options); + mwOpaque_put(b, &msg->addtl); + gboolean_put(b, msg->creator_flag); + + if(msg->creator_flag) + mwLoginInfo_put(b, &msg->creator); + + enc_offer_put(b, &msg->encrypt); + + guint32_put(b, 0x00); + guint32_put(b, 0x00); + guint16_put(b, 0x07); +} + + +static void enc_offer_get(struct mwGetBuffer *b, + struct mwEncryptOffer *enc) { + guint32 skip; + + if(mwGetBuffer_error(b)) return; + + guint16_get(b, &enc->mode); + guint32_get(b, &skip); + + if(skip >= 7) { + guint32 count; + + guint32_get(b, &count); + + while(count-- && (! mwGetBuffer_error(b))) { + struct mwEncryptItem *ei = g_new0(struct mwEncryptItem, 1); + mwEncryptItem_get(b, ei); + enc->items = g_list_append(enc->items, ei); + } + + guint16_get(b, &enc->extra); + gboolean_get(b, &enc->flag); + } +} + + +static void CHANNEL_CREATE_get(struct mwGetBuffer *b, + struct mwMsgChannelCreate *msg) { + + if(mwGetBuffer_error(b)) return; + + guint32_get(b, &msg->reserved); + guint32_get(b, &msg->channel); + mwIdBlock_get(b, &msg->target); + guint32_get(b, &msg->service); + guint32_get(b, &msg->proto_type); + guint32_get(b, &msg->proto_ver); + guint32_get(b, &msg->options); + mwOpaque_get(b, &msg->addtl); + gboolean_get(b, &msg->creator_flag); + + if(msg->creator_flag) + mwLoginInfo_get(b, &msg->creator); + + enc_offer_get(b, &msg->encrypt); +} + + +static void CHANNEL_CREATE_clear(struct mwMsgChannelCreate *msg) { + GList *list; + + mwIdBlock_clear(&msg->target); + mwOpaque_clear(&msg->addtl); + mwLoginInfo_clear(&msg->creator); + + for(list = msg->encrypt.items; list; list = list->next) { + mwEncryptItem_clear(list->data); + g_free(list->data); + } + g_list_free(msg->encrypt.items); +} + + +/* 8.4.1.8 AcceptCnl */ + + +static void enc_accept_put(struct mwPutBuffer *b, + struct mwEncryptAccept *enc) { + + guint16_put(b, enc->mode); + + if(enc->item) { + struct mwPutBuffer *p; + struct mwOpaque o; + + p = mwPutBuffer_new(); + + mwEncryptItem_put(p, enc->item); + guint16_put(p, enc->extra); + gboolean_put(p, enc->flag); + + mwPutBuffer_finalize(&o, p); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + } +} + + +static void CHANNEL_ACCEPT_put(struct mwPutBuffer *b, + struct mwMsgChannelAccept *msg) { + + guint32_put(b, msg->service); + guint32_put(b, msg->proto_type); + guint32_put(b, msg->proto_ver); + mwOpaque_put(b, &msg->addtl); + gboolean_put(b, msg->acceptor_flag); + + if(msg->acceptor_flag) + mwLoginInfo_put(b, &msg->acceptor); + + enc_accept_put(b, &msg->encrypt); + + guint32_put(b, 0x00); + guint32_put(b, 0x00); + guint16_put(b, 0x07); +} + + +static void enc_accept_get(struct mwGetBuffer *b, + struct mwEncryptAccept *enc) { + guint32 skip; + + if(mwGetBuffer_error(b)) return; + + guint16_get(b, &enc->mode); + guint32_get(b, &skip); + + if(skip >= 6) { + enc->item = g_new0(struct mwEncryptItem, 1); + mwEncryptItem_get(b, enc->item); + } + + if(skip >= 9) { + guint16_get(b, &enc->extra); + gboolean_get(b, &enc->flag); + } +} + + +static void CHANNEL_ACCEPT_get(struct mwGetBuffer *b, + struct mwMsgChannelAccept *msg) { + + if(mwGetBuffer_error(b)) return; + + guint32_get(b, &msg->service); + guint32_get(b, &msg->proto_type); + guint32_get(b, &msg->proto_ver); + mwOpaque_get(b, &msg->addtl); + gboolean_get(b, &msg->acceptor_flag); + + if(msg->acceptor_flag) + mwLoginInfo_get(b, &msg->acceptor); + + enc_accept_get(b, &msg->encrypt); +} + + +static void CHANNEL_ACCEPT_clear(struct mwMsgChannelAccept *msg) { + mwOpaque_clear(&msg->addtl); + mwLoginInfo_clear(&msg->acceptor); + + if(msg->encrypt.item) { + mwEncryptItem_clear(msg->encrypt.item); + g_free(msg->encrypt.item); + } +} + + +/* 8.4.1.9 SendOnCnl */ + + +static void CHANNEL_SEND_put(struct mwPutBuffer *b, + struct mwMsgChannelSend *msg) { + + guint16_put(b, msg->type); + mwOpaque_put(b, &msg->data); +} + + +static void CHANNEL_SEND_get(struct mwGetBuffer *b, + struct mwMsgChannelSend *msg) { + + if(mwGetBuffer_error(b)) return; + + guint16_get(b, &msg->type); + mwOpaque_get(b, &msg->data); +} + + +static void CHANNEL_SEND_clear(struct mwMsgChannelSend *msg) { + mwOpaque_clear(&msg->data); +} + + +/* 8.4.1.10 DestroyCnl */ + + +static void CHANNEL_DESTROY_put(struct mwPutBuffer *b, + struct mwMsgChannelDestroy *msg) { + guint32_put(b, msg->reason); + mwOpaque_put(b, &msg->data); +} + + +static void CHANNEL_DESTROY_get(struct mwGetBuffer *b, + struct mwMsgChannelDestroy *msg) { + + if(mwGetBuffer_error(b)) return; + + guint32_get(b, &msg->reason); + mwOpaque_get(b, &msg->data); +} + + +static void CHANNEL_DESTROY_clear(struct mwMsgChannelDestroy *msg) { + mwOpaque_clear(&msg->data); +} + + +/* 8.4.1.11 SetUserStatus */ + + +static void SET_USER_STATUS_put(struct mwPutBuffer *b, + struct mwMsgSetUserStatus *msg) { + mwUserStatus_put(b, &msg->status); +} + + +static void SET_USER_STATUS_get(struct mwGetBuffer *b, + struct mwMsgSetUserStatus *msg) { + + if(mwGetBuffer_error(b)) return; + mwUserStatus_get(b, &msg->status); +} + + +static void SET_USER_STATUS_clear(struct mwMsgSetUserStatus *msg) { + mwUserStatus_clear(&msg->status); +} + + +/* 8.4.1.12 SetPrivacyList */ + + +static void SET_PRIVACY_LIST_put(struct mwPutBuffer *b, + struct mwMsgSetPrivacyList *msg) { + mwPrivacyInfo_put(b, &msg->privacy); +} + + +static void SET_PRIVACY_LIST_get(struct mwGetBuffer *b, + struct mwMsgSetPrivacyList *msg) { + + if(mwGetBuffer_error(b)) return; + mwPrivacyInfo_get(b, &msg->privacy); +} + + +static void SET_PRIVACY_LIST_clear(struct mwMsgSetPrivacyList *msg) { + mwPrivacyInfo_clear(&msg->privacy); +} + + +/* Sense Service messages */ + + +static void SENSE_SERVICE_put(struct mwPutBuffer *b, + struct mwMsgSenseService *msg) { + guint32_put(b, msg->service); +} + + +static void SENSE_SERVICE_get(struct mwGetBuffer *b, + struct mwMsgSenseService *msg) { + + if(mwGetBuffer_error(b)) return; + guint32_get(b, &msg->service); +} + + +static void SENSE_SERVICE_clear(struct mwMsgSenseService *msg) { + ; +} + + +/* Admin messages */ + + +static void ADMIN_get(struct mwGetBuffer *b, struct mwMsgAdmin *msg) { + mwString_get(b, &msg->text); +} + + +static void ADMIN_clear(struct mwMsgAdmin *msg) { + g_free(msg->text); + msg->text = NULL; +} + + +/* Announcement messages */ + + +static void ANNOUNCE_get(struct mwGetBuffer *b, struct mwMsgAnnounce *msg) { + struct mwOpaque o = { 0, 0 }; + struct mwGetBuffer *gb; + guint32 count; + + gboolean_get(b, &msg->sender_present); + if(msg->sender_present) + mwLoginInfo_get(b, &msg->sender); + guint16_get(b, &msg->unknown_a); + + mwOpaque_get(b, &o); + gb = mwGetBuffer_wrap(&o); + + gboolean_get(gb, &msg->may_reply); + mwString_get(gb, &msg->text); + + mwGetBuffer_free(gb); + mwOpaque_clear(&o); + + guint32_get(b, &count); + while(count--) { + char *r = NULL; + mwString_get(b, &r); + msg->recipients = g_list_prepend(msg->recipients, r); + } +} + + +static void ANNOUNCE_put(struct mwPutBuffer *b, struct mwMsgAnnounce *msg) { + struct mwOpaque o = { 0, 0 }; + struct mwPutBuffer *pb; + GList *l; + + gboolean_put(b, msg->sender_present); + if(msg->sender_present) + mwLoginInfo_put(b, &msg->sender); + guint16_put(b, msg->unknown_a); + + pb = mwPutBuffer_new(); + + gboolean_put(pb, msg->may_reply); + mwString_put(pb, msg->text); + + mwPutBuffer_finalize(&o, pb); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + + guint32_put(b, g_list_length(msg->recipients)); + for(l = msg->recipients; l; l = l->next) { + mwString_put(b, l->data); + } +} + + +static void ANNOUNCE_clear(struct mwMsgAnnounce *msg) { + mwLoginInfo_clear(&msg->sender); + + g_free(msg->text); + msg->text = NULL; + + while(msg->recipients) { + g_free(msg->recipients->data); + msg->recipients = g_list_delete_link(msg->recipients, msg->recipients); + } +} + + +/* general functions */ + + +#define CASE(v, t) \ +case mwMessage_ ## v: \ + msg = (struct mwMessage *) g_new0(struct t, 1); \ + msg->type = type; \ + break; + + +struct mwMessage *mwMessage_new(enum mwMessageType type) { + struct mwMessage *msg = NULL; + + switch(type) { + CASE(HANDSHAKE, mwMsgHandshake); + CASE(HANDSHAKE_ACK, mwMsgHandshakeAck); + CASE(LOGIN, mwMsgLogin); + CASE(LOGIN_REDIRECT, mwMsgLoginRedirect); + CASE(LOGIN_CONTINUE, mwMsgLoginContinue); + CASE(LOGIN_ACK, mwMsgLoginAck); + CASE(CHANNEL_CREATE, mwMsgChannelCreate); + CASE(CHANNEL_DESTROY, mwMsgChannelDestroy); + CASE(CHANNEL_SEND, mwMsgChannelSend); + CASE(CHANNEL_ACCEPT, mwMsgChannelAccept); + CASE(SET_USER_STATUS, mwMsgSetUserStatus); + CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList); + CASE(SENSE_SERVICE, mwMsgSenseService); + CASE(ADMIN, mwMsgAdmin); + CASE(ANNOUNCE, mwMsgAnnounce); + + default: + g_warning("unknown message type 0x%02x\n", type); + } + + return msg; +} + + +#undef CASE + + +/* each type needs to be passed to a specially named _get functions, + and cast to a specific subclass of mwMessage. */ +#define CASE(v, t) \ +case mwMessage_ ## v: \ + msg = (struct mwMessage *) g_new0(struct t, 1); \ + mwMessageHead_clone(msg, &head); \ + v ## _get(b, (struct t *) msg); \ + break; + + +struct mwMessage *mwMessage_get(struct mwGetBuffer *b) { + struct mwMessage *msg = NULL; + struct mwMessage head; + + g_return_val_if_fail(b != NULL, NULL); + + head.attribs.len = 0; + head.attribs.data = NULL; + + /* attempt to read the header first */ + mwMessageHead_get(b, &head); + + if(mwGetBuffer_error(b)) { + mwMessageHead_clear(&head); + g_warning("problem parsing message head from buffer"); + return NULL; + } + + /* load the rest of the message depending on the header type */ + switch(head.type) { + CASE(HANDSHAKE, mwMsgHandshake); + CASE(HANDSHAKE_ACK, mwMsgHandshakeAck); + CASE(LOGIN, mwMsgLogin); + CASE(LOGIN_REDIRECT, mwMsgLoginRedirect); + CASE(LOGIN_CONTINUE, mwMsgLoginContinue); + CASE(LOGIN_ACK, mwMsgLoginAck); + CASE(CHANNEL_CREATE, mwMsgChannelCreate); + CASE(CHANNEL_DESTROY, mwMsgChannelDestroy); + CASE(CHANNEL_SEND, mwMsgChannelSend); + CASE(CHANNEL_ACCEPT, mwMsgChannelAccept); + CASE(SET_USER_STATUS, mwMsgSetUserStatus); + CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList); + CASE(SENSE_SERVICE, mwMsgSenseService); + CASE(ADMIN, mwMsgAdmin); + CASE(ANNOUNCE, mwMsgAnnounce); + + default: + g_warning("unknown message type 0x%02x, no parse handler", head.type); + } + + if(mwGetBuffer_error(b)) { + g_warning("problem parsing message type 0x%02x, not enough data", + head.type); + } + + mwMessageHead_clear(&head); + + return msg; +} + + +#undef CASE + + +#define CASE(v, t) \ +case mwMessage_ ## v: \ + v ## _put(b, (struct t *) msg); \ + break; + + +void mwMessage_put(struct mwPutBuffer *b, struct mwMessage *msg) { + + g_return_if_fail(b != NULL); + g_return_if_fail(msg != NULL); + + mwMessageHead_put(b, msg); + + switch(msg->type) { + CASE(HANDSHAKE, mwMsgHandshake); + CASE(HANDSHAKE_ACK, mwMsgHandshakeAck); + CASE(LOGIN, mwMsgLogin); + CASE(LOGIN_REDIRECT, mwMsgLoginRedirect); + CASE(LOGIN_CONTINUE, mwMsgLoginContinue); + CASE(CHANNEL_CREATE, mwMsgChannelCreate); + CASE(CHANNEL_DESTROY, mwMsgChannelDestroy); + CASE(CHANNEL_SEND, mwMsgChannelSend); + CASE(CHANNEL_ACCEPT, mwMsgChannelAccept); + CASE(SET_USER_STATUS, mwMsgSetUserStatus); + CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList); + CASE(SENSE_SERVICE, mwMsgSenseService); + CASE(ANNOUNCE, mwMsgAnnounce); + + default: + ; /* hrm. */ + } +} + + +#undef CASE + + +#define CASE(v, t) \ +case mwMessage_ ## v: \ + v ## _clear((struct t *) msg); \ + break; + + +void mwMessage_free(struct mwMessage *msg) { + if(! msg) return; + + mwMessageHead_clear(msg); + + switch(msg->type) { + CASE(HANDSHAKE, mwMsgHandshake); + CASE(HANDSHAKE_ACK, mwMsgHandshakeAck); + CASE(LOGIN, mwMsgLogin); + CASE(LOGIN_REDIRECT, mwMsgLoginRedirect); + CASE(LOGIN_CONTINUE, mwMsgLoginContinue); + CASE(LOGIN_ACK, mwMsgLoginAck); + CASE(CHANNEL_CREATE, mwMsgChannelCreate); + CASE(CHANNEL_DESTROY, mwMsgChannelDestroy); + CASE(CHANNEL_SEND, mwMsgChannelSend); + CASE(CHANNEL_ACCEPT, mwMsgChannelAccept); + CASE(SET_USER_STATUS, mwMsgSetUserStatus); + CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList); + CASE(SENSE_SERVICE, mwMsgSenseService); + CASE(ADMIN, mwMsgAdmin); + CASE(ANNOUNCE, mwMsgAnnounce); + + default: + ; /* hrm. */ + } + + g_free(msg); +} + + +#undef CASE + + diff --git a/src/mpi/.cvsignore b/src/mpi/.cvsignore new file mode 100644 index 0000000..3eef396 --- /dev/null +++ b/src/mpi/.cvsignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +lib* +*o diff --git a/src/mpi/CVS/Entries b/src/mpi/CVS/Entries new file mode 100644 index 0000000..97f0a5c --- /dev/null +++ b/src/mpi/CVS/Entries @@ -0,0 +1,7 @@ +/.cvsignore/1.1/Sat Nov 19 04:15:46 2005//Tmeanwhile_v1_1_0 +/Makefile.am/1.3/Tue Dec 27 19:07:14 2005//Tmeanwhile_v1_1_0 +/mpi-config.h/1.2/Thu Dec 15 18:39:53 2005//Tmeanwhile_v1_1_0 +/mpi-types.h/1.2/Thu Dec 15 18:39:53 2005//Tmeanwhile_v1_1_0 +/mpi.c/1.2/Thu Dec 15 18:39:53 2005//Tmeanwhile_v1_1_0 +/mpi.h/1.2/Thu Dec 15 18:39:53 2005//Tmeanwhile_v1_1_0 +D diff --git a/src/mpi/CVS/Repository b/src/mpi/CVS/Repository new file mode 100644 index 0000000..8219100 --- /dev/null +++ b/src/mpi/CVS/Repository @@ -0,0 +1 @@ +meanwhile/src/mpi diff --git a/src/mpi/CVS/Root b/src/mpi/CVS/Root new file mode 100644 index 0000000..7717627 --- /dev/null +++ b/src/mpi/CVS/Root @@ -0,0 +1 @@ +:pserver:anonymous@meanwhile.cvs.sourceforge.net:/cvsroot/meanwhile diff --git a/src/mpi/CVS/Tag b/src/mpi/CVS/Tag new file mode 100644 index 0000000..112c41e --- /dev/null +++ b/src/mpi/CVS/Tag @@ -0,0 +1 @@ +Tmeanwhile_v1_1_0 diff --git a/src/mpi/Makefile.am b/src/mpi/Makefile.am new file mode 100644 index 0000000..60428e0 --- /dev/null +++ b/src/mpi/Makefile.am @@ -0,0 +1,11 @@ + +noinst_LTLIBRARIES = libmpi.la + +libmpi_la_CFLAGS = $(GLIB_CFLAGS) + +libmpi_la_LDFLAGS = $(GLIB_LIBS) $(LIBM) + +libmpi_la_SOURCES = mpi.c + +noinst_HEADERS = mpi-config.h mpi.h mpi-types.h + diff --git a/src/mpi/mpi-config.h b/src/mpi/mpi-config.h new file mode 100644 index 0000000..6972c88 --- /dev/null +++ b/src/mpi/mpi-config.h @@ -0,0 +1,84 @@ +/* Default configuration for MPI library */ + +#ifndef MPI_CONFIG_H_ +#define MPI_CONFIG_H_ + +/* + For boolean options, + 0 = no + 1 = yes + + Other options are documented individually. + + */ + +#ifndef MP_IOFUNC +#define MP_IOFUNC 0 /* include mp_print() ? */ +#endif + +#ifndef MP_MODARITH +#define MP_MODARITH 1 /* include modular arithmetic ? */ +#endif + +#ifndef MP_NUMTH +#define MP_NUMTH 1 /* include number theoretic functions? */ +#endif + +#ifndef MP_LOGTAB +#define MP_LOGTAB 0 /* use table of logs instead of log()? */ +#endif + +#ifndef MP_MEMSET +#define MP_MEMSET 1 /* use memset() to zero buffers? */ +#endif + +#ifndef MP_MEMCPY +#define MP_MEMCPY 1 /* use memcpy() to copy buffers? */ +#endif + +#ifndef MP_CRYPTO +#define MP_CRYPTO 0 /* erase memory on free? */ +#endif + +#ifndef MP_ARGCHK +/* + 0 = no parameter checks + 1 = runtime checks, continue execution and return an error to caller + 2 = assertions; dump core on parameter errors + */ +#define MP_ARGCHK 2 /* how to check input arguments */ +#endif + +#ifndef MP_DEBUG +#define MP_DEBUG 0 /* print diagnostic output? */ +#endif + +#ifndef MP_DEFPREC +#define MP_DEFPREC 32 /* default precision, in digits */ +#endif + +#ifndef MP_MACRO +#define MP_MACRO 1 /* use macros for frequent calls? */ +#endif + +#ifndef MP_SQUARE +#define MP_SQUARE 1 /* use separate squaring code? */ +#endif + +#ifndef MP_PTAB_SIZE +/* + When building mpprime.c, we build in a table of small prime + values to use for primality testing. The more you include, + the more space they take up. See primes.c for the possible + values (currently 16, 32, 64, 128, 256, and 6542) + */ +#define MP_PTAB_SIZE 128 /* how many built-in primes? */ +#endif + +#ifndef MP_COMPAT_MACROS +#define MP_COMPAT_MACROS 0 /* define compatibility macros? */ +#endif + +#endif /* ifndef MPI_CONFIG_H_ */ + + diff --git a/src/mpi/mpi-types.h b/src/mpi/mpi-types.h new file mode 100644 index 0000000..137047c --- /dev/null +++ b/src/mpi/mpi-types.h @@ -0,0 +1,18 @@ + +#include + +typedef gchar mw_mp_sign; +typedef guint16 mw_mp_digit; /* 2 byte type */ +typedef guint32 mw_mp_word; /* 4 byte type */ +typedef gsize mw_mp_size; +typedef gint mw_mp_err; + +#define MP_DIGIT_BIT 16 +#define MP_DIGIT_MAX G_MAXUINT16 +#define MP_WORD_BIT 32 +#define MP_WORD_MAX G_MAXUINT32 + +#define RADIX (MP_DIGIT_MAX+1) + +#define MP_DIGIT_SIZE 2 +#define DIGIT_FMT "%04X" diff --git a/src/mpi/mpi.c b/src/mpi/mpi.c new file mode 100644 index 0000000..177f80e --- /dev/null +++ b/src/mpi/mpi.c @@ -0,0 +1,4001 @@ +/* + mpi.c + + by Michael J. Fromberger + Copyright (C) 1998 Michael J. Fromberger, All Rights Reserved + + Arbitrary precision integer arithmetic library + + modified for use in Meanwhile as a convenience library +*/ + +#include "mpi.h" +#include +#include +#include + +#if MP_DEBUG +#include + +#define DIAG(T,V) {fprintf(stderr,T);mw_mp_print(V,stderr);fputc('\n',stderr);} +#else +#define DIAG(T,V) +#endif + +/* + If MP_LOGTAB is not defined, use the math library to compute the + logarithms on the fly. Otherwise, use the static table below. + Pick which works best for your system. + */ +#if MP_LOGTAB + +/* {{{ s_logv_2[] - log table for 2 in various bases */ + +/* + A table of the logs of 2 for various bases (the 0 and 1 entries of + this table are meaningless and should not be referenced). + + This table is used to compute output lengths for the mw_mp_toradix() + function. Since a number n in radix r takes up about log_r(n) + digits, we estimate the output size by taking the least integer + greater than log_r(n), where: + + log_r(n) = log_2(n) * log_r(2) + + This table, therefore, is a table of log_r(2) for 2 <= r <= 36, + which are the output bases supported. + */ + +#include "logtab.h" + +/* }}} */ +#define LOG_V_2(R) s_logv_2[(R)] + +#else + +#include +#define LOG_V_2(R) (log(2.0)/log(R)) + +#endif + +/* Default precision for newly created mw_mp_int's */ +static unsigned int s_mw_mp_defprec = MP_DEFPREC; + +/* {{{ Digit arithmetic macros */ + +/* + When adding and multiplying digits, the results can be larger than + can be contained in an mw_mp_digit. Thus, an mw_mp_word is used. These + macros mask off the upper and lower digits of the mw_mp_word (the + mw_mp_word may be more than 2 mw_mp_digits wide, but we only concern + ourselves with the low-order 2 mw_mp_digits) + + If your mw_mp_word DOES have more than 2 mw_mp_digits, you need to + uncomment the first line, and comment out the second. + */ + +/* #define CARRYOUT(W) (((W)>>DIGIT_BIT)&MP_DIGIT_MAX) */ +#define CARRYOUT(W) ((W)>>DIGIT_BIT) +#define ACCUM(W) ((W)&MP_DIGIT_MAX) + +/* }}} */ + +/* {{{ Comparison constants */ + +#define MP_LT -1 +#define MP_EQ 0 +#define MP_GT 1 + +/* }}} */ + +/* {{{ Constant strings */ + +/* Constant strings returned by mw_mp_strerror() */ +static const char *mw_mp_err_string[] = { + "unknown result code", /* say what? */ + "boolean true", /* MP_OKAY, MP_YES */ + "boolean false", /* MP_NO */ + "out of memory", /* MP_MEM */ + "argument out of range", /* MP_RANGE */ + "invalid input parameter", /* MP_BADARG */ + "result is undefined" /* MP_UNDEF */ +}; + +/* Value to digit maps for radix conversion */ + +/* s_dmap_1 - standard digits and letters */ +static const char *s_dmap_1 = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"; + +#if 0 +/* s_dmap_2 - base64 ordering for digits */ +static const char *s_dmap_2 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +#endif + +/* }}} */ + +/* {{{ Static function declarations */ + +/* + If MP_MACRO is false, these will be defined as actual functions; + otherwise, suitable macro definitions will be used. This works + around the fact that ANSI C89 doesn't support an 'inline' keyword + (although I hear C9x will ... about bloody time). At present, the + macro definitions are identical to the function bodies, but they'll + expand in place, instead of generating a function call. + + I chose these particular functions to be made into macros because + some profiling showed they are called a lot on a typical workload, + and yet they are primarily housekeeping. + */ +#if MP_MACRO == 0 + void s_mw_mp_setz(mw_mp_digit *dp, mw_mp_size count); /* zero digits */ + void s_mw_mp_copy(mw_mp_digit *sp, mw_mp_digit *dp, mw_mp_size count); /* copy */ + void *s_mw_mp_alloc(size_t nb, size_t ni); /* general allocator */ + void s_mw_mp_free(void *ptr); /* general free function */ +#else + + /* Even if these are defined as macros, we need to respect the settings + of the MP_MEMSET and MP_MEMCPY configuration options... + */ + #if MP_MEMSET == 0 + #define s_mw_mp_setz(dp, count) \ + {int ix;for(ix=0;ix<(count);ix++)(dp)[ix]=0;} + #else + #define s_mw_mp_setz(dp, count) memset(dp, 0, (count) * sizeof(mw_mp_digit)) + #endif /* MP_MEMSET */ + + #if MP_MEMCPY == 0 + #define s_mw_mp_copy(sp, dp, count) \ + {int ix;for(ix=0;ix<(count);ix++)(dp)[ix]=(sp)[ix];} + #else + #define s_mw_mp_copy(sp, dp, count) memcpy(dp, sp, (count) * sizeof(mw_mp_digit)) + #endif /* MP_MEMCPY */ + + #define s_mw_mp_alloc(nb, ni) calloc(nb, ni) + #define s_mw_mp_free(ptr) {if(ptr) free(ptr);} +#endif /* MP_MACRO */ + +mw_mp_err s_mw_mp_grow(mw_mp_int *mp, mw_mp_size min); /* increase allocated size */ +mw_mp_err s_mw_mp_pad(mw_mp_int *mp, mw_mp_size min); /* left pad with zeroes */ + +void s_mw_mp_clamp(mw_mp_int *mp); /* clip leading zeroes */ + +void s_mw_mp_exch(mw_mp_int *a, mw_mp_int *b); /* swap a and b in place */ + +mw_mp_err s_mw_mp_lshd(mw_mp_int *mp, mw_mp_size p); /* left-shift by p digits */ +void s_mw_mp_rshd(mw_mp_int *mp, mw_mp_size p); /* right-shift by p digits */ +void s_mw_mp_div_2d(mw_mp_int *mp, mw_mp_digit d); /* divide by 2^d in place */ +void s_mw_mp_mod_2d(mw_mp_int *mp, mw_mp_digit d); /* modulo 2^d in place */ +mw_mp_err s_mw_mp_mul_2d(mw_mp_int *mp, mw_mp_digit d); /* multiply by 2^d in place*/ +void s_mw_mp_div_2(mw_mp_int *mp); /* divide by 2 in place */ +mw_mp_err s_mw_mp_mul_2(mw_mp_int *mp); /* multiply by 2 in place */ +mw_mp_digit s_mw_mp_norm(mw_mp_int *a, mw_mp_int *b); /* normalize for division */ +mw_mp_err s_mw_mp_add_d(mw_mp_int *mp, mw_mp_digit d); /* unsigned digit addition */ +mw_mp_err s_mw_mp_sub_d(mw_mp_int *mp, mw_mp_digit d); /* unsigned digit subtract */ +mw_mp_err s_mw_mp_mul_d(mw_mp_int *mp, mw_mp_digit d); /* unsigned digit multiply */ +mw_mp_err s_mw_mp_div_d(mw_mp_int *mp, mw_mp_digit d, mw_mp_digit *r); + /* unsigned digit divide */ +mw_mp_err s_mw_mp_reduce(mw_mp_int *x, mw_mp_int *m, mw_mp_int *mu); + /* Barrett reduction */ +mw_mp_err s_mw_mp_add(mw_mp_int *a, mw_mp_int *b); /* magnitude addition */ +mw_mp_err s_mw_mp_sub(mw_mp_int *a, mw_mp_int *b); /* magnitude subtract */ +mw_mp_err s_mw_mp_mul(mw_mp_int *a, mw_mp_int *b); /* magnitude multiply */ +#if 0 +void s_mw_mp_kmul(mw_mp_digit *a, mw_mp_digit *b, mw_mp_digit *out, mw_mp_size len); + /* multiply buffers in place */ +#endif +#if MP_SQUARE +mw_mp_err s_mw_mp_sqr(mw_mp_int *a); /* magnitude square */ +#else +#define s_mw_mp_sqr(a) s_mw_mp_mul(a, a) +#endif +mw_mp_err s_mw_mp_div(mw_mp_int *a, mw_mp_int *b); /* magnitude divide */ +mw_mp_err s_mw_mp_2expt(mw_mp_int *a, mw_mp_digit k); /* a = 2^k */ +int s_mw_mp_cmp(mw_mp_int *a, mw_mp_int *b); /* magnitude comparison */ +int s_mw_mp_cmw_mp_d(mw_mp_int *a, mw_mp_digit d); /* magnitude digit compare */ +int s_mw_mp_ispow2(mw_mp_int *v); /* is v a power of 2? */ +int s_mw_mp_ispow2d(mw_mp_digit d); /* is d a power of 2? */ + +int s_mw_mp_tovalue(char ch, int r); /* convert ch to value */ +char s_mw_mp_todigit(int val, int r, int low); /* convert val to digit */ +int s_mw_mp_outlen(int bits, int r); /* output length in bytes */ + +/* }}} */ + +/* {{{ Default precision manipulation */ + +unsigned int mw_mp_get_prec(void) +{ + return s_mw_mp_defprec; + +} /* end mw_mp_get_prec() */ + +void mw_mp_set_prec(unsigned int prec) +{ + if(prec == 0) + s_mw_mp_defprec = MP_DEFPREC; + else + s_mw_mp_defprec = prec; + +} /* end mw_mp_set_prec() */ + +/* }}} */ + +/*------------------------------------------------------------------------*/ +/* {{{ mw_mp_init(mp) */ + +/* + mw_mp_init(mp) + + Initialize a new zero-valued mw_mp_int. Returns MP_OKAY if successful, + MP_MEM if memory could not be allocated for the structure. + */ + +mw_mp_err mw_mp_init(mw_mp_int *mp) +{ + return mw_mp_init_size(mp, s_mw_mp_defprec); + +} /* end mw_mp_init() */ + +/* }}} */ + +/* {{{ mw_mp_init_array(mp[], count) */ + +mw_mp_err mw_mp_init_array(mw_mp_int mp[], int count) +{ + mw_mp_err res; + int pos; + + ARGCHK(mp !=NULL && count > 0, MP_BADARG); + + for(pos = 0; pos < count; ++pos) { + if((res = mw_mp_init(&mp[pos])) != MP_OKAY) + goto CLEANUP; + } + + return MP_OKAY; + + CLEANUP: + while(--pos >= 0) + mw_mp_clear(&mp[pos]); + + return res; + +} /* end mw_mp_init_array() */ + +/* }}} */ + +/* {{{ mw_mp_init_size(mp, prec) */ + +/* + mw_mp_init_size(mp, prec) + + Initialize a new zero-valued mw_mp_int with at least the given + precision; returns MP_OKAY if successful, or MP_MEM if memory could + not be allocated for the structure. + */ + +mw_mp_err mw_mp_init_size(mw_mp_int *mp, mw_mp_size prec) +{ + ARGCHK(mp != NULL && prec > 0, MP_BADARG); + + if((DIGITS(mp) = s_mw_mp_alloc(prec, sizeof(mw_mp_digit))) == NULL) + return MP_MEM; + + SIGN(mp) = MP_ZPOS; + USED(mp) = 1; + ALLOC(mp) = prec; + + return MP_OKAY; + +} /* end mw_mp_init_size() */ + +/* }}} */ + +/* {{{ mw_mp_init_copy(mp, from) */ + +/* + mw_mp_init_copy(mp, from) + + Initialize mp as an exact copy of from. Returns MP_OKAY if + successful, MP_MEM if memory could not be allocated for the new + structure. + */ + +mw_mp_err mw_mp_init_copy(mw_mp_int *mp, mw_mp_int *from) +{ + ARGCHK(mp != NULL && from != NULL, MP_BADARG); + + if(mp == from) + return MP_OKAY; + + if((DIGITS(mp) = s_mw_mp_alloc(USED(from), sizeof(mw_mp_digit))) == NULL) + return MP_MEM; + + s_mw_mp_copy(DIGITS(from), DIGITS(mp), USED(from)); + USED(mp) = USED(from); + ALLOC(mp) = USED(from); + SIGN(mp) = SIGN(from); + + return MP_OKAY; + +} /* end mw_mp_init_copy() */ + +/* }}} */ + +/* {{{ mw_mp_copy(from, to) */ + +/* + mw_mp_copy(from, to) + + Copies the mw_mp_int 'from' to the mw_mp_int 'to'. It is presumed that + 'to' has already been initialized (if not, use mw_mp_init_copy() + instead). If 'from' and 'to' are identical, nothing happens. + */ + +mw_mp_err mw_mp_copy(mw_mp_int *from, mw_mp_int *to) +{ + ARGCHK(from != NULL && to != NULL, MP_BADARG); + + if(from == to) + return MP_OKAY; + + { /* copy */ + mw_mp_digit *tmp; + + /* + If the allocated buffer in 'to' already has enough space to hold + all the used digits of 'from', we'll re-use it to avoid hitting + the memory allocater more than necessary; otherwise, we'd have + to grow anyway, so we just allocate a hunk and make the copy as + usual + */ + if(ALLOC(to) >= USED(from)) { + s_mw_mp_setz(DIGITS(to) + USED(from), ALLOC(to) - USED(from)); + s_mw_mp_copy(DIGITS(from), DIGITS(to), USED(from)); + + } else { + if((tmp = s_mw_mp_alloc(USED(from), sizeof(mw_mp_digit))) == NULL) + return MP_MEM; + + s_mw_mp_copy(DIGITS(from), tmp, USED(from)); + + if(DIGITS(to) != NULL) { +#if MP_CRYPTO + s_mw_mp_setz(DIGITS(to), ALLOC(to)); +#endif + s_mw_mp_free(DIGITS(to)); + } + + DIGITS(to) = tmp; + ALLOC(to) = USED(from); + } + + /* Copy the precision and sign from the original */ + USED(to) = USED(from); + SIGN(to) = SIGN(from); + } /* end copy */ + + return MP_OKAY; + +} /* end mw_mp_copy() */ + +/* }}} */ + +/* {{{ mw_mp_exch(mp1, mp2) */ + +/* + mw_mp_exch(mp1, mp2) + + Exchange mp1 and mp2 without allocating any intermediate memory + (well, unless you count the stack space needed for this call and the + locals it creates...). This cannot fail. + */ + +void mw_mp_exch(mw_mp_int *mp1, mw_mp_int *mp2) +{ +#if MP_ARGCHK == 2 + assert(mp1 != NULL && mp2 != NULL); +#else + if(mp1 == NULL || mp2 == NULL) + return; +#endif + + s_mw_mp_exch(mp1, mp2); + +} /* end mw_mp_exch() */ + +/* }}} */ + +/* {{{ mw_mp_clear(mp) */ + +/* + mw_mp_clear(mp) + + Release the storage used by an mw_mp_int, and void its fields so that + if someone calls mw_mp_clear() again for the same int later, we won't + get tollchocked. + */ + +void mw_mp_clear(mw_mp_int *mp) +{ + if(mp == NULL) + return; + + if(DIGITS(mp) != NULL) { +#if MP_CRYPTO + s_mw_mp_setz(DIGITS(mp), ALLOC(mp)); +#endif + s_mw_mp_free(DIGITS(mp)); + DIGITS(mp) = NULL; + } + + USED(mp) = 0; + ALLOC(mp) = 0; + +} /* end mw_mp_clear() */ + +/* }}} */ + +/* {{{ mw_mp_clear_array(mp[], count) */ + +void mw_mp_clear_array(mw_mp_int mp[], int count) +{ + ARGCHK(mp != NULL && count > 0, MP_BADARG); + + while(--count >= 0) + mw_mp_clear(&mp[count]); + +} /* end mw_mp_clear_array() */ + +/* }}} */ + +/* {{{ mw_mp_zero(mp) */ + +/* + mw_mp_zero(mp) + + Set mp to zero. Does not change the allocated size of the structure, + and therefore cannot fail (except on a bad argument, which we ignore) + */ +void mw_mp_zero(mw_mp_int *mp) +{ + if(mp == NULL) + return; + + s_mw_mp_setz(DIGITS(mp), ALLOC(mp)); + USED(mp) = 1; + SIGN(mp) = MP_ZPOS; + +} /* end mw_mp_zero() */ + +/* }}} */ + +/* {{{ mw_mp_set(mp, d) */ + +void mw_mp_set(mw_mp_int *mp, mw_mp_digit d) +{ + if(mp == NULL) + return; + + mw_mp_zero(mp); + DIGIT(mp, 0) = d; + +} /* end mw_mp_set() */ + +/* }}} */ + +/* {{{ mw_mp_set_int(mp, z) */ + +mw_mp_err mw_mp_set_int(mw_mp_int *mp, long z) +{ + int ix; + unsigned long v = abs(z); + mw_mp_err res; + + ARGCHK(mp != NULL, MP_BADARG); + + mw_mp_zero(mp); + if(z == 0) + return MP_OKAY; /* shortcut for zero */ + + for(ix = sizeof(long) - 1; ix >= 0; ix--) { + + if((res = s_mw_mp_mul_2d(mp, CHAR_BIT)) != MP_OKAY) + return res; + + res = s_mw_mp_add_d(mp, + (mw_mp_digit)((v >> (ix * CHAR_BIT)) & UCHAR_MAX)); + if(res != MP_OKAY) + return res; + + } + + if(z < 0) + SIGN(mp) = MP_NEG; + + return MP_OKAY; + +} /* end mw_mp_set_int() */ + +/* }}} */ + +/*------------------------------------------------------------------------*/ +/* {{{ Digit arithmetic */ + +/* {{{ mw_mp_add_d(a, d, b) */ + +/* + mw_mp_add_d(a, d, b) + + Compute the sum b = a + d, for a single digit d. Respects the sign of + its primary addend (single digits are unsigned anyway). + */ + +mw_mp_err mw_mp_add_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *b) +{ + mw_mp_err res = MP_OKAY; + + ARGCHK(a != NULL && b != NULL, MP_BADARG); + + if((res = mw_mp_copy(a, b)) != MP_OKAY) + return res; + + if(SIGN(b) == MP_ZPOS) { + res = s_mw_mp_add_d(b, d); + } else if(s_mw_mp_cmw_mp_d(b, d) >= 0) { + res = s_mw_mp_sub_d(b, d); + } else { + SIGN(b) = MP_ZPOS; + + DIGIT(b, 0) = d - DIGIT(b, 0); + } + + return res; + +} /* end mw_mp_add_d() */ + +/* }}} */ + +/* {{{ mw_mp_sub_d(a, d, b) */ + +/* + mw_mp_sub_d(a, d, b) + + Compute the difference b = a - d, for a single digit d. Respects the + sign of its subtrahend (single digits are unsigned anyway). + */ + +mw_mp_err mw_mp_sub_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *b) +{ + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL, MP_BADARG); + + if((res = mw_mp_copy(a, b)) != MP_OKAY) + return res; + + if(SIGN(b) == MP_NEG) { + if((res = s_mw_mp_add_d(b, d)) != MP_OKAY) + return res; + + } else if(s_mw_mp_cmw_mp_d(b, d) >= 0) { + if((res = s_mw_mp_sub_d(b, d)) != MP_OKAY) + return res; + + } else { + mw_mp_neg(b, b); + + DIGIT(b, 0) = d - DIGIT(b, 0); + SIGN(b) = MP_NEG; + } + + if(s_mw_mp_cmw_mp_d(b, 0) == 0) + SIGN(b) = MP_ZPOS; + + return MP_OKAY; + +} /* end mw_mp_sub_d() */ + +/* }}} */ + +/* {{{ mw_mp_mul_d(a, d, b) */ + +/* + mw_mp_mul_d(a, d, b) + + Compute the product b = a * d, for a single digit d. Respects the sign + of its multiplicand (single digits are unsigned anyway) + */ + +mw_mp_err mw_mp_mul_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *b) +{ + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL, MP_BADARG); + + if(d == 0) { + mw_mp_zero(b); + return MP_OKAY; + } + + if((res = mw_mp_copy(a, b)) != MP_OKAY) + return res; + + res = s_mw_mp_mul_d(b, d); + + return res; + +} /* end mw_mp_mul_d() */ + +/* }}} */ + +/* {{{ mw_mp_mul_2(a, c) */ + +mw_mp_err mw_mp_mul_2(mw_mp_int *a, mw_mp_int *c) +{ + mw_mp_err res; + + ARGCHK(a != NULL && c != NULL, MP_BADARG); + + if((res = mw_mp_copy(a, c)) != MP_OKAY) + return res; + + return s_mw_mp_mul_2(c); + +} /* end mw_mp_mul_2() */ + +/* }}} */ + +/* {{{ mw_mp_div_d(a, d, q, r) */ + +/* + mw_mp_div_d(a, d, q, r) + + Compute the quotient q = a / d and remainder r = a mod d, for a + single digit d. Respects the sign of its divisor (single digits are + unsigned anyway). + */ + +mw_mp_err mw_mp_div_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *q, mw_mp_digit *r) +{ + mw_mp_err res; + mw_mp_digit rem; + int pow; + + ARGCHK(a != NULL, MP_BADARG); + + if(d == 0) + return MP_RANGE; + + /* Shortcut for powers of two ... */ + if((pow = s_mw_mp_ispow2d(d)) >= 0) { + mw_mp_digit mask; + + mask = (1 << pow) - 1; + rem = DIGIT(a, 0) & mask; + + if(q) { + mw_mp_copy(a, q); + s_mw_mp_div_2d(q, pow); + } + + if(r) + *r = rem; + + return MP_OKAY; + } + + /* + If the quotient is actually going to be returned, we'll try to + avoid hitting the memory allocator by copying the dividend into it + and doing the division there. This can't be any _worse_ than + always copying, and will sometimes be better (since it won't make + another copy) + + If it's not going to be returned, we need to allocate a temporary + to hold the quotient, which will just be discarded. + */ + if(q) { + if((res = mw_mp_copy(a, q)) != MP_OKAY) + return res; + + res = s_mw_mp_div_d(q, d, &rem); + if(s_mw_mp_cmw_mp_d(q, 0) == MP_EQ) + SIGN(q) = MP_ZPOS; + + } else { + mw_mp_int qp; + + if((res = mw_mp_init_copy(&qp, a)) != MP_OKAY) + return res; + + res = s_mw_mp_div_d(&qp, d, &rem); + if(s_mw_mp_cmw_mp_d(&qp, 0) == 0) + SIGN(&qp) = MP_ZPOS; + + mw_mp_clear(&qp); + } + + if(r) + *r = rem; + + return res; + +} /* end mw_mp_div_d() */ + +/* }}} */ + +/* {{{ mw_mp_div_2(a, c) */ + +/* + mw_mp_div_2(a, c) + + Compute c = a / 2, disregarding the remainder. + */ + +mw_mp_err mw_mp_div_2(mw_mp_int *a, mw_mp_int *c) +{ + mw_mp_err res; + + ARGCHK(a != NULL && c != NULL, MP_BADARG); + + if((res = mw_mp_copy(a, c)) != MP_OKAY) + return res; + + s_mw_mp_div_2(c); + + return MP_OKAY; + +} /* end mw_mp_div_2() */ + +/* }}} */ + +/* {{{ mw_mp_expt_d(a, d, b) */ + +mw_mp_err mw_mp_expt_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *c) +{ + mw_mp_int s, x; + mw_mp_err res; + mw_mp_sign cs = MP_ZPOS; + + ARGCHK(a != NULL && c != NULL, MP_BADARG); + + if((res = mw_mp_init(&s)) != MP_OKAY) + return res; + if((res = mw_mp_init_copy(&x, a)) != MP_OKAY) + goto X; + + DIGIT(&s, 0) = 1; + + if((d % 2) == 1) + cs = SIGN(a); + + while(d != 0) { + if(d & 1) { + if((res = s_mw_mp_mul(&s, &x)) != MP_OKAY) + goto CLEANUP; + } + + d >>= 1; + + if((res = s_mw_mp_sqr(&x)) != MP_OKAY) + goto CLEANUP; + } + + SIGN(&s) = cs; + + s_mw_mp_exch(&s, c); + +CLEANUP: + mw_mp_clear(&x); +X: + mw_mp_clear(&s); + + return res; + +} /* end mw_mp_expt_d() */ + +/* }}} */ + +/* }}} */ + +/*------------------------------------------------------------------------*/ +/* {{{ Full arithmetic */ + +/* {{{ mw_mp_abs(a, b) */ + +/* + mw_mp_abs(a, b) + + Compute b = |a|. 'a' and 'b' may be identical. + */ + +mw_mp_err mw_mp_abs(mw_mp_int *a, mw_mp_int *b) +{ + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL, MP_BADARG); + + if((res = mw_mp_copy(a, b)) != MP_OKAY) + return res; + + SIGN(b) = MP_ZPOS; + + return MP_OKAY; + +} /* end mw_mp_abs() */ + +/* }}} */ + +/* {{{ mw_mp_neg(a, b) */ + +/* + mw_mp_neg(a, b) + + Compute b = -a. 'a' and 'b' may be identical. + */ + +mw_mp_err mw_mp_neg(mw_mp_int *a, mw_mp_int *b) +{ + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL, MP_BADARG); + + if((res = mw_mp_copy(a, b)) != MP_OKAY) + return res; + + if(s_mw_mp_cmw_mp_d(b, 0) == MP_EQ) + SIGN(b) = MP_ZPOS; + else + SIGN(b) = (SIGN(b) == MP_NEG) ? MP_ZPOS : MP_NEG; + + return MP_OKAY; + +} /* end mw_mp_neg() */ + +/* }}} */ + +/* {{{ mw_mp_add(a, b, c) */ + +/* + mw_mp_add(a, b, c) + + Compute c = a + b. All parameters may be identical. + */ + +mw_mp_err mw_mp_add(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c) +{ + mw_mp_err res; + int cmp; + + ARGCHK(a != NULL && b != NULL && c != NULL, MP_BADARG); + + if(SIGN(a) == SIGN(b)) { /* same sign: add values, keep sign */ + + /* Commutativity of addition lets us do this in either order, + so we avoid having to use a temporary even if the result + is supposed to replace the output + */ + if(c == b) { + if((res = s_mw_mp_add(c, a)) != MP_OKAY) + return res; + } else { + if(c != a && (res = mw_mp_copy(a, c)) != MP_OKAY) + return res; + + if((res = s_mw_mp_add(c, b)) != MP_OKAY) + return res; + } + + } else if((cmp = s_mw_mp_cmp(a, b)) > 0) { /* different sign: a > b */ + + /* If the output is going to be clobbered, we will use a temporary + variable; otherwise, we'll do it without touching the memory + allocator at all, if possible + */ + if(c == b) { + mw_mp_int tmp; + + if((res = mw_mp_init_copy(&tmp, a)) != MP_OKAY) + return res; + if((res = s_mw_mp_sub(&tmp, b)) != MP_OKAY) { + mw_mp_clear(&tmp); + return res; + } + + s_mw_mp_exch(&tmp, c); + mw_mp_clear(&tmp); + + } else { + + if(c != a && (res = mw_mp_copy(a, c)) != MP_OKAY) + return res; + if((res = s_mw_mp_sub(c, b)) != MP_OKAY) + return res; + + } + + } else if(cmp == 0) { /* different sign, a == b */ + + mw_mp_zero(c); + return MP_OKAY; + + } else { /* different sign: a < b */ + + /* See above... */ + if(c == a) { + mw_mp_int tmp; + + if((res = mw_mp_init_copy(&tmp, b)) != MP_OKAY) + return res; + if((res = s_mw_mp_sub(&tmp, a)) != MP_OKAY) { + mw_mp_clear(&tmp); + return res; + } + + s_mw_mp_exch(&tmp, c); + mw_mp_clear(&tmp); + + } else { + + if(c != b && (res = mw_mp_copy(b, c)) != MP_OKAY) + return res; + if((res = s_mw_mp_sub(c, a)) != MP_OKAY) + return res; + + } + } + + if(USED(c) == 1 && DIGIT(c, 0) == 0) + SIGN(c) = MP_ZPOS; + + return MP_OKAY; + +} /* end mw_mp_add() */ + +/* }}} */ + +/* {{{ mw_mp_sub(a, b, c) */ + +/* + mw_mp_sub(a, b, c) + + Compute c = a - b. All parameters may be identical. + */ + +mw_mp_err mw_mp_sub(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c) +{ + mw_mp_err res; + int cmp; + + ARGCHK(a != NULL && b != NULL && c != NULL, MP_BADARG); + + if(SIGN(a) != SIGN(b)) { + if(c == a) { + if((res = s_mw_mp_add(c, b)) != MP_OKAY) + return res; + } else { + if(c != b && ((res = mw_mp_copy(b, c)) != MP_OKAY)) + return res; + if((res = s_mw_mp_add(c, a)) != MP_OKAY) + return res; + SIGN(c) = SIGN(a); + } + + } else if((cmp = s_mw_mp_cmp(a, b)) > 0) { /* Same sign, a > b */ + if(c == b) { + mw_mp_int tmp; + + if((res = mw_mp_init_copy(&tmp, a)) != MP_OKAY) + return res; + if((res = s_mw_mp_sub(&tmp, b)) != MP_OKAY) { + mw_mp_clear(&tmp); + return res; + } + s_mw_mp_exch(&tmp, c); + mw_mp_clear(&tmp); + + } else { + if(c != a && ((res = mw_mp_copy(a, c)) != MP_OKAY)) + return res; + + if((res = s_mw_mp_sub(c, b)) != MP_OKAY) + return res; + } + + } else if(cmp == 0) { /* Same sign, equal magnitude */ + mw_mp_zero(c); + return MP_OKAY; + + } else { /* Same sign, b > a */ + if(c == a) { + mw_mp_int tmp; + + if((res = mw_mp_init_copy(&tmp, b)) != MP_OKAY) + return res; + + if((res = s_mw_mp_sub(&tmp, a)) != MP_OKAY) { + mw_mp_clear(&tmp); + return res; + } + s_mw_mp_exch(&tmp, c); + mw_mp_clear(&tmp); + + } else { + if(c != b && ((res = mw_mp_copy(b, c)) != MP_OKAY)) + return res; + + if((res = s_mw_mp_sub(c, a)) != MP_OKAY) + return res; + } + + SIGN(c) = !SIGN(b); + } + + if(USED(c) == 1 && DIGIT(c, 0) == 0) + SIGN(c) = MP_ZPOS; + + return MP_OKAY; + +} /* end mw_mp_sub() */ + +/* }}} */ + +/* {{{ mw_mp_mul(a, b, c) */ + +/* + mw_mp_mul(a, b, c) + + Compute c = a * b. All parameters may be identical. + */ + +mw_mp_err mw_mp_mul(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c) +{ + mw_mp_err res; + mw_mp_sign sgn; + + ARGCHK(a != NULL && b != NULL && c != NULL, MP_BADARG); + + sgn = (SIGN(a) == SIGN(b)) ? MP_ZPOS : MP_NEG; + + if(c == b) { + if((res = s_mw_mp_mul(c, a)) != MP_OKAY) + return res; + + } else { + if((res = mw_mp_copy(a, c)) != MP_OKAY) + return res; + + if((res = s_mw_mp_mul(c, b)) != MP_OKAY) + return res; + } + + if(sgn == MP_ZPOS || s_mw_mp_cmw_mp_d(c, 0) == MP_EQ) + SIGN(c) = MP_ZPOS; + else + SIGN(c) = sgn; + + return MP_OKAY; + +} /* end mw_mp_mul() */ + +/* }}} */ + +/* {{{ mw_mp_mul_2d(a, d, c) */ + +/* + mw_mp_mul_2d(a, d, c) + + Compute c = a * 2^d. a may be the same as c. + */ + +mw_mp_err mw_mp_mul_2d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *c) +{ + mw_mp_err res; + + ARGCHK(a != NULL && c != NULL, MP_BADARG); + + if((res = mw_mp_copy(a, c)) != MP_OKAY) + return res; + + if(d == 0) + return MP_OKAY; + + return s_mw_mp_mul_2d(c, d); + +} /* end mw_mp_mul() */ + +/* }}} */ + +/* {{{ mw_mp_sqr(a, b) */ + +#if MP_SQUARE +mw_mp_err mw_mp_sqr(mw_mp_int *a, mw_mp_int *b) +{ + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL, MP_BADARG); + + if((res = mw_mp_copy(a, b)) != MP_OKAY) + return res; + + if((res = s_mw_mp_sqr(b)) != MP_OKAY) + return res; + + SIGN(b) = MP_ZPOS; + + return MP_OKAY; + +} /* end mw_mp_sqr() */ +#endif + +/* }}} */ + +/* {{{ mw_mp_div(a, b, q, r) */ + +/* + mw_mp_div(a, b, q, r) + + Compute q = a / b and r = a mod b. Input parameters may be re-used + as output parameters. If q or r is NULL, that portion of the + computation will be discarded (although it will still be computed) + + Pay no attention to the hacker behind the curtain. + */ + +mw_mp_err mw_mp_div(mw_mp_int *a, mw_mp_int *b, mw_mp_int *q, mw_mp_int *r) +{ + mw_mp_err res; + mw_mp_int qtmp, rtmp; + int cmp; + + ARGCHK(a != NULL && b != NULL, MP_BADARG); + + if(mw_mp_cmw_mp_z(b) == MP_EQ) + return MP_RANGE; + + /* If a <= b, we can compute the solution without division, and + avoid any memory allocation + */ + if((cmp = s_mw_mp_cmp(a, b)) < 0) { + if(r) { + if((res = mw_mp_copy(a, r)) != MP_OKAY) + return res; + } + + if(q) + mw_mp_zero(q); + + return MP_OKAY; + + } else if(cmp == 0) { + + /* Set quotient to 1, with appropriate sign */ + if(q) { + int qneg = (SIGN(a) != SIGN(b)); + + mw_mp_set(q, 1); + if(qneg) + SIGN(q) = MP_NEG; + } + + if(r) + mw_mp_zero(r); + + return MP_OKAY; + } + + /* If we get here, it means we actually have to do some division */ + + /* Set up some temporaries... */ + if((res = mw_mp_init_copy(&qtmp, a)) != MP_OKAY) + return res; + if((res = mw_mp_init_copy(&rtmp, b)) != MP_OKAY) + goto CLEANUP; + + if((res = s_mw_mp_div(&qtmp, &rtmp)) != MP_OKAY) + goto CLEANUP; + + /* Compute the signs for the output */ + SIGN(&rtmp) = SIGN(a); /* Sr = Sa */ + if(SIGN(a) == SIGN(b)) + SIGN(&qtmp) = MP_ZPOS; /* Sq = MP_ZPOS if Sa = Sb */ + else + SIGN(&qtmp) = MP_NEG; /* Sq = MP_NEG if Sa != Sb */ + + if(s_mw_mp_cmw_mp_d(&qtmp, 0) == MP_EQ) + SIGN(&qtmp) = MP_ZPOS; + if(s_mw_mp_cmw_mp_d(&rtmp, 0) == MP_EQ) + SIGN(&rtmp) = MP_ZPOS; + + /* Copy output, if it is needed */ + if(q) + s_mw_mp_exch(&qtmp, q); + + if(r) + s_mw_mp_exch(&rtmp, r); + +CLEANUP: + mw_mp_clear(&rtmp); + mw_mp_clear(&qtmp); + + return res; + +} /* end mw_mp_div() */ + +/* }}} */ + +/* {{{ mw_mp_div_2d(a, d, q, r) */ + +mw_mp_err mw_mp_div_2d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *q, mw_mp_int *r) +{ + mw_mp_err res; + + ARGCHK(a != NULL, MP_BADARG); + + if(q) { + if((res = mw_mp_copy(a, q)) != MP_OKAY) + return res; + + s_mw_mp_div_2d(q, d); + } + + if(r) { + if((res = mw_mp_copy(a, r)) != MP_OKAY) + return res; + + s_mw_mp_mod_2d(r, d); + } + + return MP_OKAY; + +} /* end mw_mp_div_2d() */ + +/* }}} */ + +/* {{{ mw_mp_expt(a, b, c) */ + +/* + mw_mp_expt(a, b, c) + + Compute c = a ** b, that is, raise a to the b power. Uses a + standard iterative square-and-multiply technique. + */ + +mw_mp_err mw_mp_expt(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c) +{ + mw_mp_int s, x; + mw_mp_err res; + mw_mp_digit d; + int dig, bit; + + ARGCHK(a != NULL && b != NULL && c != NULL, MP_BADARG); + + if(mw_mp_cmw_mp_z(b) < 0) + return MP_RANGE; + + if((res = mw_mp_init(&s)) != MP_OKAY) + return res; + + mw_mp_set(&s, 1); + + if((res = mw_mp_init_copy(&x, a)) != MP_OKAY) + goto X; + + /* Loop over low-order digits in ascending order */ + for(dig = 0; dig < (USED(b) - 1); dig++) { + d = DIGIT(b, dig); + + /* Loop over bits of each non-maximal digit */ + for(bit = 0; bit < DIGIT_BIT; bit++) { + if(d & 1) { + if((res = s_mw_mp_mul(&s, &x)) != MP_OKAY) + goto CLEANUP; + } + + d >>= 1; + + if((res = s_mw_mp_sqr(&x)) != MP_OKAY) + goto CLEANUP; + } + } + + /* Consider now the last digit... */ + d = DIGIT(b, dig); + + while(d) { + if(d & 1) { + if((res = s_mw_mp_mul(&s, &x)) != MP_OKAY) + goto CLEANUP; + } + + d >>= 1; + + if((res = s_mw_mp_sqr(&x)) != MP_OKAY) + goto CLEANUP; + } + + if(mw_mp_iseven(b)) + SIGN(&s) = SIGN(a); + + res = mw_mp_copy(&s, c); + +CLEANUP: + mw_mp_clear(&x); +X: + mw_mp_clear(&s); + + return res; + +} /* end mw_mp_expt() */ + +/* }}} */ + +/* {{{ mw_mp_2expt(a, k) */ + +/* Compute a = 2^k */ + +mw_mp_err mw_mp_2expt(mw_mp_int *a, mw_mp_digit k) +{ + ARGCHK(a != NULL, MP_BADARG); + + return s_mw_mp_2expt(a, k); + +} /* end mw_mp_2expt() */ + +/* }}} */ + +/* {{{ mw_mp_mod(a, m, c) */ + +/* + mw_mp_mod(a, m, c) + + Compute c = a (mod m). Result will always be 0 <= c < m. + */ + +mw_mp_err mw_mp_mod(mw_mp_int *a, mw_mp_int *m, mw_mp_int *c) +{ + mw_mp_err res; + int mag; + + ARGCHK(a != NULL && m != NULL && c != NULL, MP_BADARG); + + if(SIGN(m) == MP_NEG) + return MP_RANGE; + + /* + If |a| > m, we need to divide to get the remainder and take the + absolute value. + + If |a| < m, we don't need to do any division, just copy and adjust + the sign (if a is negative). + + If |a| == m, we can simply set the result to zero. + + This order is intended to minimize the average path length of the + comparison chain on common workloads -- the most frequent cases are + that |a| != m, so we do those first. + */ + if((mag = s_mw_mp_cmp(a, m)) > 0) { + if((res = mw_mp_div(a, m, NULL, c)) != MP_OKAY) + return res; + + if(SIGN(c) == MP_NEG) { + if((res = mw_mp_add(c, m, c)) != MP_OKAY) + return res; + } + + } else if(mag < 0) { + if((res = mw_mp_copy(a, c)) != MP_OKAY) + return res; + + if(mw_mp_cmw_mp_z(a) < 0) { + if((res = mw_mp_add(c, m, c)) != MP_OKAY) + return res; + + } + + } else { + mw_mp_zero(c); + + } + + return MP_OKAY; + +} /* end mw_mp_mod() */ + +/* }}} */ + +/* {{{ mw_mp_mod_d(a, d, c) */ + +/* + mw_mp_mod_d(a, d, c) + + Compute c = a (mod d). Result will always be 0 <= c < d + */ +mw_mp_err mw_mp_mod_d(mw_mp_int *a, mw_mp_digit d, mw_mp_digit *c) +{ + mw_mp_err res; + mw_mp_digit rem; + + ARGCHK(a != NULL && c != NULL, MP_BADARG); + + if(s_mw_mp_cmw_mp_d(a, d) > 0) { + if((res = mw_mp_div_d(a, d, NULL, &rem)) != MP_OKAY) + return res; + + } else { + if(SIGN(a) == MP_NEG) + rem = d - DIGIT(a, 0); + else + rem = DIGIT(a, 0); + } + + if(c) + *c = rem; + + return MP_OKAY; + +} /* end mw_mp_mod_d() */ + +/* }}} */ + +/* {{{ mw_mp_sqrt(a, b) */ + +/* + mw_mp_sqrt(a, b) + + Compute the integer square root of a, and store the result in b. + Uses an integer-arithmetic version of Newton's iterative linear + approximation technique to determine this value; the result has the + following two properties: + + b^2 <= a + (b+1)^2 >= a + + It is a range error to pass a negative value. + */ +mw_mp_err mw_mp_sqrt(mw_mp_int *a, mw_mp_int *b) +{ + mw_mp_int x, t; + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL, MP_BADARG); + + /* Cannot take square root of a negative value */ + if(SIGN(a) == MP_NEG) + return MP_RANGE; + + /* Special cases for zero and one, trivial */ + if(mw_mp_cmw_mp_d(a, 0) == MP_EQ || mw_mp_cmw_mp_d(a, 1) == MP_EQ) + return mw_mp_copy(a, b); + + /* Initialize the temporaries we'll use below */ + if((res = mw_mp_init_size(&t, USED(a))) != MP_OKAY) + return res; + + /* Compute an initial guess for the iteration as a itself */ + if((res = mw_mp_init_copy(&x, a)) != MP_OKAY) + goto X; + + for(;;) { + /* t = (x * x) - a */ + mw_mp_copy(&x, &t); /* can't fail, t is big enough for original x */ + if((res = mw_mp_sqr(&t, &t)) != MP_OKAY || + (res = mw_mp_sub(&t, a, &t)) != MP_OKAY) + goto CLEANUP; + + /* t = t / 2x */ + s_mw_mp_mul_2(&x); + if((res = mw_mp_div(&t, &x, &t, NULL)) != MP_OKAY) + goto CLEANUP; + s_mw_mp_div_2(&x); + + /* Terminate the loop, if the quotient is zero */ + if(mw_mp_cmw_mp_z(&t) == MP_EQ) + break; + + /* x = x - t */ + if((res = mw_mp_sub(&x, &t, &x)) != MP_OKAY) + goto CLEANUP; + + } + + /* Copy result to output parameter */ + mw_mp_sub_d(&x, 1, &x); + s_mw_mp_exch(&x, b); + + CLEANUP: + mw_mp_clear(&x); + X: + mw_mp_clear(&t); + + return res; + +} /* end mw_mp_sqrt() */ + +/* }}} */ + +/* }}} */ + +/*------------------------------------------------------------------------*/ +/* {{{ Modular arithmetic */ + +#if MP_MODARITH +/* {{{ mw_mp_addmod(a, b, m, c) */ + +/* + mw_mp_addmod(a, b, m, c) + + Compute c = (a + b) mod m + */ + +mw_mp_err mw_mp_addmod(mw_mp_int *a, mw_mp_int *b, mw_mp_int *m, mw_mp_int *c) +{ + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL && m != NULL && c != NULL, MP_BADARG); + + if((res = mw_mp_add(a, b, c)) != MP_OKAY) + return res; + if((res = mw_mp_mod(c, m, c)) != MP_OKAY) + return res; + + return MP_OKAY; + +} + +/* }}} */ + +/* {{{ mw_mp_submod(a, b, m, c) */ + +/* + mw_mp_submod(a, b, m, c) + + Compute c = (a - b) mod m + */ + +mw_mp_err mw_mp_submod(mw_mp_int *a, mw_mp_int *b, mw_mp_int *m, mw_mp_int *c) +{ + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL && m != NULL && c != NULL, MP_BADARG); + + if((res = mw_mp_sub(a, b, c)) != MP_OKAY) + return res; + if((res = mw_mp_mod(c, m, c)) != MP_OKAY) + return res; + + return MP_OKAY; + +} + +/* }}} */ + +/* {{{ mw_mp_mulmod(a, b, m, c) */ + +/* + mw_mp_mulmod(a, b, m, c) + + Compute c = (a * b) mod m + */ + +mw_mp_err mw_mp_mulmod(mw_mp_int *a, mw_mp_int *b, mw_mp_int *m, mw_mp_int *c) +{ + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL && m != NULL && c != NULL, MP_BADARG); + + if((res = mw_mp_mul(a, b, c)) != MP_OKAY) + return res; + if((res = mw_mp_mod(c, m, c)) != MP_OKAY) + return res; + + return MP_OKAY; + +} + +/* }}} */ + +/* {{{ mw_mp_sqrmod(a, m, c) */ + +#if MP_SQUARE +mw_mp_err mw_mp_sqrmod(mw_mp_int *a, mw_mp_int *m, mw_mp_int *c) +{ + mw_mp_err res; + + ARGCHK(a != NULL && m != NULL && c != NULL, MP_BADARG); + + if((res = mw_mp_sqr(a, c)) != MP_OKAY) + return res; + if((res = mw_mp_mod(c, m, c)) != MP_OKAY) + return res; + + return MP_OKAY; + +} /* end mw_mp_sqrmod() */ +#endif + +/* }}} */ + +/* {{{ mw_mp_exptmod(a, b, m, c) */ + +/* + mw_mp_exptmod(a, b, m, c) + + Compute c = (a ** b) mod m. Uses a standard square-and-multiply + method with modular reductions at each step. (This is basically the + same code as mw_mp_expt(), except for the addition of the reductions) + + The modular reductions are done using Barrett's algorithm (see + s_mw_mp_reduce() below for details) + */ + +mw_mp_err mw_mp_exptmod(mw_mp_int *a, mw_mp_int *b, mw_mp_int *m, mw_mp_int *c) +{ + mw_mp_int s, x, mu; + mw_mp_err res; + mw_mp_digit d, *db = DIGITS(b); + mw_mp_size ub = USED(b); + int dig, bit; + + ARGCHK(a != NULL && b != NULL && c != NULL, MP_BADARG); + + if(mw_mp_cmw_mp_z(b) < 0 || mw_mp_cmw_mp_z(m) <= 0) + return MP_RANGE; + + if((res = mw_mp_init(&s)) != MP_OKAY) + return res; + if((res = mw_mp_init_copy(&x, a)) != MP_OKAY) + goto X; + if((res = mw_mp_mod(&x, m, &x)) != MP_OKAY || + (res = mw_mp_init(&mu)) != MP_OKAY) + goto MU; + + mw_mp_set(&s, 1); + + /* mu = b^2k / m */ + s_mw_mp_add_d(&mu, 1); + s_mw_mp_lshd(&mu, 2 * USED(m)); + if((res = mw_mp_div(&mu, m, &mu, NULL)) != MP_OKAY) + goto CLEANUP; + + /* Loop over digits of b in ascending order, except highest order */ + for(dig = 0; dig < (ub - 1); dig++) { + d = *db++; + + /* Loop over the bits of the lower-order digits */ + for(bit = 0; bit < DIGIT_BIT; bit++) { + if(d & 1) { + if((res = s_mw_mp_mul(&s, &x)) != MP_OKAY) + goto CLEANUP; + if((res = s_mw_mp_reduce(&s, m, &mu)) != MP_OKAY) + goto CLEANUP; + } + + d >>= 1; + + if((res = s_mw_mp_sqr(&x)) != MP_OKAY) + goto CLEANUP; + if((res = s_mw_mp_reduce(&x, m, &mu)) != MP_OKAY) + goto CLEANUP; + } + } + + /* Now do the last digit... */ + d = *db; + + while(d) { + if(d & 1) { + if((res = s_mw_mp_mul(&s, &x)) != MP_OKAY) + goto CLEANUP; + if((res = s_mw_mp_reduce(&s, m, &mu)) != MP_OKAY) + goto CLEANUP; + } + + d >>= 1; + + if((res = s_mw_mp_sqr(&x)) != MP_OKAY) + goto CLEANUP; + if((res = s_mw_mp_reduce(&x, m, &mu)) != MP_OKAY) + goto CLEANUP; + } + + s_mw_mp_exch(&s, c); + + CLEANUP: + mw_mp_clear(&mu); + MU: + mw_mp_clear(&x); + X: + mw_mp_clear(&s); + + return res; + +} /* end mw_mp_exptmod() */ + +/* }}} */ + +/* {{{ mw_mp_exptmod_d(a, d, m, c) */ + +mw_mp_err mw_mp_exptmod_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *m, mw_mp_int *c) +{ + mw_mp_int s, x; + mw_mp_err res; + + ARGCHK(a != NULL && c != NULL, MP_BADARG); + + if((res = mw_mp_init(&s)) != MP_OKAY) + return res; + if((res = mw_mp_init_copy(&x, a)) != MP_OKAY) + goto X; + + mw_mp_set(&s, 1); + + while(d != 0) { + if(d & 1) { + if((res = s_mw_mp_mul(&s, &x)) != MP_OKAY || + (res = mw_mp_mod(&s, m, &s)) != MP_OKAY) + goto CLEANUP; + } + + d /= 2; + + if((res = s_mw_mp_sqr(&x)) != MP_OKAY || + (res = mw_mp_mod(&x, m, &x)) != MP_OKAY) + goto CLEANUP; + } + + s_mw_mp_exch(&s, c); + +CLEANUP: + mw_mp_clear(&x); +X: + mw_mp_clear(&s); + + return res; + +} /* end mw_mp_exptmod_d() */ + +/* }}} */ +#endif /* if MP_MODARITH */ + +/* }}} */ + +/*------------------------------------------------------------------------*/ +/* {{{ Comparison functions */ + +/* {{{ mw_mp_cmw_mp_z(a) */ + +/* + mw_mp_cmw_mp_z(a) + + Compare a <=> 0. Returns <0 if a<0, 0 if a=0, >0 if a>0. + */ + +int mw_mp_cmw_mp_z(mw_mp_int *a) +{ + if(SIGN(a) == MP_NEG) + return MP_LT; + else if(USED(a) == 1 && DIGIT(a, 0) == 0) + return MP_EQ; + else + return MP_GT; + +} /* end mw_mp_cmw_mp_z() */ + +/* }}} */ + +/* {{{ mw_mp_cmw_mp_d(a, d) */ + +/* + mw_mp_cmw_mp_d(a, d) + + Compare a <=> d. Returns <0 if a0 if a>d + */ + +int mw_mp_cmw_mp_d(mw_mp_int *a, mw_mp_digit d) +{ + ARGCHK(a != NULL, MP_EQ); + + if(SIGN(a) == MP_NEG) + return MP_LT; + + return s_mw_mp_cmw_mp_d(a, d); + +} /* end mw_mp_cmw_mp_d() */ + +/* }}} */ + +/* {{{ mw_mp_cmp(a, b) */ + +int mw_mp_cmp(mw_mp_int *a, mw_mp_int *b) +{ + ARGCHK(a != NULL && b != NULL, MP_EQ); + + if(SIGN(a) == SIGN(b)) { + int mag; + + if((mag = s_mw_mp_cmp(a, b)) == MP_EQ) + return MP_EQ; + + if(SIGN(a) == MP_ZPOS) + return mag; + else + return -mag; + + } else if(SIGN(a) == MP_ZPOS) { + return MP_GT; + } else { + return MP_LT; + } + +} /* end mw_mp_cmp() */ + +/* }}} */ + +/* {{{ mw_mp_cmw_mp_mag(a, b) */ + +/* + mw_mp_cmw_mp_mag(a, b) + + Compares |a| <=> |b|, and returns an appropriate comparison result + */ + +int mw_mp_cmw_mp_mag(mw_mp_int *a, mw_mp_int *b) +{ + ARGCHK(a != NULL && b != NULL, MP_EQ); + + return s_mw_mp_cmp(a, b); + +} /* end mw_mp_cmw_mp_mag() */ + +/* }}} */ + +/* {{{ mw_mp_cmw_mp_int(a, z) */ + +/* + This just converts z to an mw_mp_int, and uses the existing comparison + routines. This is sort of inefficient, but it's not clear to me how + frequently this wil get used anyway. For small positive constants, + you can always use mw_mp_cmw_mp_d(), and for zero, there is mw_mp_cmw_mp_z(). + */ +int mw_mp_cmw_mp_int(mw_mp_int *a, long z) +{ + mw_mp_int tmp; + int out; + + ARGCHK(a != NULL, MP_EQ); + + mw_mp_init(&tmp); mw_mp_set_int(&tmp, z); + out = mw_mp_cmp(a, &tmp); + mw_mp_clear(&tmp); + + return out; + +} /* end mw_mp_cmw_mp_int() */ + +/* }}} */ + +/* {{{ mw_mp_isodd(a) */ + +/* + mw_mp_isodd(a) + + Returns a true (non-zero) value if a is odd, false (zero) otherwise. + */ +int mw_mp_isodd(mw_mp_int *a) +{ + ARGCHK(a != NULL, 0); + + return (DIGIT(a, 0) & 1); + +} /* end mw_mp_isodd() */ + +/* }}} */ + +/* {{{ mw_mp_iseven(a) */ + +int mw_mp_iseven(mw_mp_int *a) +{ + return !mw_mp_isodd(a); + +} /* end mw_mp_iseven() */ + +/* }}} */ + +/* }}} */ + +/*------------------------------------------------------------------------*/ +/* {{{ Number theoretic functions */ + +#if MP_NUMTH +/* {{{ mw_mp_gcd(a, b, c) */ + +/* + Like the old mw_mp_gcd() function, except computes the GCD using the + binary algorithm due to Josef Stein in 1961 (via Knuth). + */ +mw_mp_err mw_mp_gcd(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c) +{ + mw_mp_err res; + mw_mp_int u, v, t; + mw_mp_size k = 0; + + ARGCHK(a != NULL && b != NULL && c != NULL, MP_BADARG); + + if(mw_mp_cmw_mp_z(a) == MP_EQ && mw_mp_cmw_mp_z(b) == MP_EQ) + return MP_RANGE; + if(mw_mp_cmw_mp_z(a) == MP_EQ) { + if((res = mw_mp_copy(b, c)) != MP_OKAY) + return res; + SIGN(c) = MP_ZPOS; return MP_OKAY; + } else if(mw_mp_cmw_mp_z(b) == MP_EQ) { + if((res = mw_mp_copy(a, c)) != MP_OKAY) + return res; + SIGN(c) = MP_ZPOS; return MP_OKAY; + } + + if((res = mw_mp_init(&t)) != MP_OKAY) + return res; + if((res = mw_mp_init_copy(&u, a)) != MP_OKAY) + goto U; + if((res = mw_mp_init_copy(&v, b)) != MP_OKAY) + goto V; + + SIGN(&u) = MP_ZPOS; + SIGN(&v) = MP_ZPOS; + + /* Divide out common factors of 2 until at least 1 of a, b is even */ + while(mw_mp_iseven(&u) && mw_mp_iseven(&v)) { + s_mw_mp_div_2(&u); + s_mw_mp_div_2(&v); + ++k; + } + + /* Initialize t */ + if(mw_mp_isodd(&u)) { + if((res = mw_mp_copy(&v, &t)) != MP_OKAY) + goto CLEANUP; + + /* t = -v */ + if(SIGN(&v) == MP_ZPOS) + SIGN(&t) = MP_NEG; + else + SIGN(&t) = MP_ZPOS; + + } else { + if((res = mw_mp_copy(&u, &t)) != MP_OKAY) + goto CLEANUP; + + } + + for(;;) { + while(mw_mp_iseven(&t)) { + s_mw_mp_div_2(&t); + } + + if(mw_mp_cmw_mp_z(&t) == MP_GT) { + if((res = mw_mp_copy(&t, &u)) != MP_OKAY) + goto CLEANUP; + + } else { + if((res = mw_mp_copy(&t, &v)) != MP_OKAY) + goto CLEANUP; + + /* v = -t */ + if(SIGN(&t) == MP_ZPOS) + SIGN(&v) = MP_NEG; + else + SIGN(&v) = MP_ZPOS; + } + + if((res = mw_mp_sub(&u, &v, &t)) != MP_OKAY) + goto CLEANUP; + + if(s_mw_mp_cmw_mp_d(&t, 0) == MP_EQ) + break; + } + + s_mw_mp_2expt(&v, k); /* v = 2^k */ + res = mw_mp_mul(&u, &v, c); /* c = u * v */ + + CLEANUP: + mw_mp_clear(&v); + V: + mw_mp_clear(&u); + U: + mw_mp_clear(&t); + + return res; + +} /* end mw_mp_bgcd() */ + +/* }}} */ + +/* {{{ mw_mp_lcm(a, b, c) */ + +/* We compute the least common multiple using the rule: + + ab = [a, b](a, b) + + ... by computing the product, and dividing out the gcd. + */ + +mw_mp_err mw_mp_lcm(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c) +{ + mw_mp_int gcd, prod; + mw_mp_err res; + + ARGCHK(a != NULL && b != NULL && c != NULL, MP_BADARG); + + /* Set up temporaries */ + if((res = mw_mp_init(&gcd)) != MP_OKAY) + return res; + if((res = mw_mp_init(&prod)) != MP_OKAY) + goto GCD; + + if((res = mw_mp_mul(a, b, &prod)) != MP_OKAY) + goto CLEANUP; + if((res = mw_mp_gcd(a, b, &gcd)) != MP_OKAY) + goto CLEANUP; + + res = mw_mp_div(&prod, &gcd, c, NULL); + + CLEANUP: + mw_mp_clear(&prod); + GCD: + mw_mp_clear(&gcd); + + return res; + +} /* end mw_mp_lcm() */ + +/* }}} */ + +/* {{{ mw_mp_xgcd(a, b, g, x, y) */ + +/* + mw_mp_xgcd(a, b, g, x, y) + + Compute g = (a, b) and values x and y satisfying Bezout's identity + (that is, ax + by = g). This uses the extended binary GCD algorithm + based on the Stein algorithm used for mw_mp_gcd() + */ + +mw_mp_err mw_mp_xgcd(mw_mp_int *a, mw_mp_int *b, mw_mp_int *g, mw_mp_int *x, mw_mp_int *y) +{ + mw_mp_int gx, xc, yc, u, v, A, B, C, D; + mw_mp_int *clean[9]; + mw_mp_err res; + int last = -1; + + if(mw_mp_cmw_mp_z(b) == 0) + return MP_RANGE; + + /* Initialize all these variables we need */ + if((res = mw_mp_init(&u)) != MP_OKAY) goto CLEANUP; + clean[++last] = &u; + if((res = mw_mp_init(&v)) != MP_OKAY) goto CLEANUP; + clean[++last] = &v; + if((res = mw_mp_init(&gx)) != MP_OKAY) goto CLEANUP; + clean[++last] = &gx; + if((res = mw_mp_init(&A)) != MP_OKAY) goto CLEANUP; + clean[++last] = &A; + if((res = mw_mp_init(&B)) != MP_OKAY) goto CLEANUP; + clean[++last] = &B; + if((res = mw_mp_init(&C)) != MP_OKAY) goto CLEANUP; + clean[++last] = &C; + if((res = mw_mp_init(&D)) != MP_OKAY) goto CLEANUP; + clean[++last] = &D; + if((res = mw_mp_init_copy(&xc, a)) != MP_OKAY) goto CLEANUP; + clean[++last] = &xc; + mw_mp_abs(&xc, &xc); + if((res = mw_mp_init_copy(&yc, b)) != MP_OKAY) goto CLEANUP; + clean[++last] = &yc; + mw_mp_abs(&yc, &yc); + + mw_mp_set(&gx, 1); + + /* Divide by two until at least one of them is even */ + while(mw_mp_iseven(&xc) && mw_mp_iseven(&yc)) { + s_mw_mp_div_2(&xc); + s_mw_mp_div_2(&yc); + if((res = s_mw_mp_mul_2(&gx)) != MP_OKAY) + goto CLEANUP; + } + + mw_mp_copy(&xc, &u); + mw_mp_copy(&yc, &v); + mw_mp_set(&A, 1); mw_mp_set(&D, 1); + + /* Loop through binary GCD algorithm */ + for(;;) { + while(mw_mp_iseven(&u)) { + s_mw_mp_div_2(&u); + + if(mw_mp_iseven(&A) && mw_mp_iseven(&B)) { + s_mw_mp_div_2(&A); s_mw_mp_div_2(&B); + } else { + if((res = mw_mp_add(&A, &yc, &A)) != MP_OKAY) goto CLEANUP; + s_mw_mp_div_2(&A); + if((res = mw_mp_sub(&B, &xc, &B)) != MP_OKAY) goto CLEANUP; + s_mw_mp_div_2(&B); + } + } + + while(mw_mp_iseven(&v)) { + s_mw_mp_div_2(&v); + + if(mw_mp_iseven(&C) && mw_mp_iseven(&D)) { + s_mw_mp_div_2(&C); s_mw_mp_div_2(&D); + } else { + if((res = mw_mp_add(&C, &yc, &C)) != MP_OKAY) goto CLEANUP; + s_mw_mp_div_2(&C); + if((res = mw_mp_sub(&D, &xc, &D)) != MP_OKAY) goto CLEANUP; + s_mw_mp_div_2(&D); + } + } + + if(mw_mp_cmp(&u, &v) >= 0) { + if((res = mw_mp_sub(&u, &v, &u)) != MP_OKAY) goto CLEANUP; + if((res = mw_mp_sub(&A, &C, &A)) != MP_OKAY) goto CLEANUP; + if((res = mw_mp_sub(&B, &D, &B)) != MP_OKAY) goto CLEANUP; + + } else { + if((res = mw_mp_sub(&v, &u, &v)) != MP_OKAY) goto CLEANUP; + if((res = mw_mp_sub(&C, &A, &C)) != MP_OKAY) goto CLEANUP; + if((res = mw_mp_sub(&D, &B, &D)) != MP_OKAY) goto CLEANUP; + + } + + /* If we're done, copy results to output */ + if(mw_mp_cmw_mp_z(&u) == 0) { + if(x) + if((res = mw_mp_copy(&C, x)) != MP_OKAY) goto CLEANUP; + + if(y) + if((res = mw_mp_copy(&D, y)) != MP_OKAY) goto CLEANUP; + + if(g) + if((res = mw_mp_mul(&gx, &v, g)) != MP_OKAY) goto CLEANUP; + + break; + } + } + + CLEANUP: + while(last >= 0) + mw_mp_clear(clean[last--]); + + return res; + +} /* end mw_mp_xgcd() */ + +/* }}} */ + +/* {{{ mw_mp_invmod(a, m, c) */ + +/* + mw_mp_invmod(a, m, c) + + Compute c = a^-1 (mod m), if there is an inverse for a (mod m). + This is equivalent to the question of whether (a, m) = 1. If not, + MP_UNDEF is returned, and there is no inverse. + */ + +mw_mp_err mw_mp_invmod(mw_mp_int *a, mw_mp_int *m, mw_mp_int *c) +{ + mw_mp_int g, x; + mw_mp_sign sa; + mw_mp_err res; + + ARGCHK(a && m && c, MP_BADARG); + + if(mw_mp_cmw_mp_z(a) == 0 || mw_mp_cmw_mp_z(m) == 0) + return MP_RANGE; + + sa = SIGN(a); + + if((res = mw_mp_init(&g)) != MP_OKAY) + return res; + if((res = mw_mp_init(&x)) != MP_OKAY) + goto X; + + if((res = mw_mp_xgcd(a, m, &g, &x, NULL)) != MP_OKAY) + goto CLEANUP; + + if(mw_mp_cmw_mp_d(&g, 1) != MP_EQ) { + res = MP_UNDEF; + goto CLEANUP; + } + + res = mw_mp_mod(&x, m, c); + SIGN(c) = sa; + +CLEANUP: + mw_mp_clear(&x); +X: + mw_mp_clear(&g); + + return res; + +} /* end mw_mp_invmod() */ + +/* }}} */ +#endif /* if MP_NUMTH */ + +/* }}} */ + +/*------------------------------------------------------------------------*/ +/* {{{ mw_mp_print(mp, ofp) */ + +#if MP_IOFUNC +/* + mw_mp_print(mp, ofp) + + Print a textual representation of the given mw_mp_int on the output + stream 'ofp'. Output is generated using the internal radix. + */ + +void mw_mp_print(mw_mp_int *mp, FILE *ofp) +{ + int ix; + + if(mp == NULL || ofp == NULL) + return; + + fputc((SIGN(mp) == MP_NEG) ? '-' : '+', ofp); + + for(ix = USED(mp) - 1; ix >= 0; ix--) { + fprintf(ofp, DIGIT_FMT, DIGIT(mp, ix)); + } + +} /* end mw_mp_print() */ + +#endif /* if MP_IOFUNC */ + +/* }}} */ + +/*------------------------------------------------------------------------*/ +/* {{{ More I/O Functions */ + +/* {{{ mw_mp_read_signed_bin(mp, str, len) */ + +/* + mw_mp_read_signed_bin(mp, str, len) + + Read in a raw value (base 256) into the given mw_mp_int + */ + +mw_mp_err mw_mp_read_signed_bin(mw_mp_int *mp, unsigned char *str, int len) +{ + mw_mp_err res; + + ARGCHK(mp != NULL && str != NULL && len > 0, MP_BADARG); + + if((res = mw_mp_read_unsigned_bin(mp, str + 1, len - 1)) == MP_OKAY) { + /* Get sign from first byte */ + if(str[0]) + SIGN(mp) = MP_NEG; + else + SIGN(mp) = MP_ZPOS; + } + + return res; + +} /* end mw_mp_read_signed_bin() */ + +/* }}} */ + +/* {{{ mw_mp_signed_bin_size(mp) */ + +int mw_mp_signed_bin_size(mw_mp_int *mp) +{ + ARGCHK(mp != NULL, 0); + + return mw_mp_unsigned_bin_size(mp) + 1; + +} /* end mw_mp_signed_bin_size() */ + +/* }}} */ + +/* {{{ mw_mp_to_signed_bin(mp, str) */ + +mw_mp_err mw_mp_to_signed_bin(mw_mp_int *mp, unsigned char *str) +{ + ARGCHK(mp != NULL && str != NULL, MP_BADARG); + + /* Caller responsible for allocating enough memory (use mw_mp_raw_size(mp)) */ + str[0] = (char)SIGN(mp); + + return mw_mp_to_unsigned_bin(mp, str + 1); + +} /* end mw_mp_to_signed_bin() */ + +/* }}} */ + +/* {{{ mw_mp_read_unsigned_bin(mp, str, len) */ + +/* + mw_mp_read_unsigned_bin(mp, str, len) + + Read in an unsigned value (base 256) into the given mw_mp_int + */ + +mw_mp_err mw_mp_read_unsigned_bin(mw_mp_int *mp, unsigned char *str, int len) +{ + int ix; + mw_mp_err res; + + ARGCHK(mp != NULL && str != NULL && len > 0, MP_BADARG); + + mw_mp_zero(mp); + + for(ix = 0; ix < len; ix++) { + if((res = s_mw_mp_mul_2d(mp, CHAR_BIT)) != MP_OKAY) + return res; + + if((res = mw_mp_add_d(mp, str[ix], mp)) != MP_OKAY) + return res; + } + + return MP_OKAY; + +} /* end mw_mp_read_unsigned_bin() */ + +/* }}} */ + +/* {{{ mw_mp_unsigned_bin_size(mp) */ + +int mw_mp_unsigned_bin_size(mw_mp_int *mp) +{ + mw_mp_digit topdig; + int count; + + ARGCHK(mp != NULL, 0); + + /* Special case for the value zero */ + if(USED(mp) == 1 && DIGIT(mp, 0) == 0) + return 1; + + count = (USED(mp) - 1) * sizeof(mw_mp_digit); + topdig = DIGIT(mp, USED(mp) - 1); + + while(topdig != 0) { + ++count; + topdig >>= CHAR_BIT; + } + + return count; + +} /* end mw_mp_unsigned_bin_size() */ + +/* }}} */ + +/* {{{ mw_mp_to_unsigned_bin(mp, str) */ + +mw_mp_err mw_mp_to_unsigned_bin(mw_mp_int *mp, unsigned char *str) +{ + mw_mp_digit *dp, *end, d; + unsigned char *spos; + + ARGCHK(mp != NULL && str != NULL, MP_BADARG); + + dp = DIGITS(mp); + end = dp + USED(mp) - 1; + spos = str; + + /* Special case for zero, quick test */ + if(dp == end && *dp == 0) { + *str = '\0'; + return MP_OKAY; + } + + /* Generate digits in reverse order */ + while(dp < end) { + int ix; + + d = *dp; + for(ix = 0; ix < sizeof(mw_mp_digit); ++ix) { + *spos = d & UCHAR_MAX; + d >>= CHAR_BIT; + ++spos; + } + + ++dp; + } + + /* Now handle last digit specially, high order zeroes are not written */ + d = *end; + while(d != 0) { + *spos = d & UCHAR_MAX; + d >>= CHAR_BIT; + ++spos; + } + + /* Reverse everything to get digits in the correct order */ + while(--spos > str) { + unsigned char t = *str; + *str = *spos; + *spos = t; + + ++str; + } + + return MP_OKAY; + +} /* end mw_mp_to_unsigned_bin() */ + +/* }}} */ + +/* {{{ mw_mp_count_bits(mp) */ + +int mw_mp_count_bits(mw_mp_int *mp) +{ + int len; + mw_mp_digit d; + + ARGCHK(mp != NULL, MP_BADARG); + + len = DIGIT_BIT * (USED(mp) - 1); + d = DIGIT(mp, USED(mp) - 1); + + while(d != 0) { + ++len; + d >>= 1; + } + + return len; + +} /* end mw_mp_count_bits() */ + +/* }}} */ + +/* {{{ mw_mp_read_radix(mp, str, radix) */ + +/* + mw_mp_read_radix(mp, str, radix) + + Read an integer from the given string, and set mp to the resulting + value. The input is presumed to be in base 10. Leading non-digit + characters are ignored, and the function reads until a non-digit + character or the end of the string. + */ + +mw_mp_err mw_mp_read_radix(mw_mp_int *mp, unsigned char *str, int radix) +{ + int ix = 0, val = 0; + mw_mp_err res; + mw_mp_sign sig = MP_ZPOS; + + ARGCHK(mp != NULL && str != NULL && radix >= 2 && radix <= MAX_RADIX, + MP_BADARG); + + mw_mp_zero(mp); + + /* Skip leading non-digit characters until a digit or '-' or '+' */ + while(str[ix] && + (s_mw_mp_tovalue(str[ix], radix) < 0) && + str[ix] != '-' && + str[ix] != '+') { + ++ix; + } + + if(str[ix] == '-') { + sig = MP_NEG; + ++ix; + } else if(str[ix] == '+') { + sig = MP_ZPOS; /* this is the default anyway... */ + ++ix; + } + + while((val = s_mw_mp_tovalue(str[ix], radix)) >= 0) { + if((res = s_mw_mp_mul_d(mp, radix)) != MP_OKAY) + return res; + if((res = s_mw_mp_add_d(mp, val)) != MP_OKAY) + return res; + ++ix; + } + + if(s_mw_mp_cmw_mp_d(mp, 0) == MP_EQ) + SIGN(mp) = MP_ZPOS; + else + SIGN(mp) = sig; + + return MP_OKAY; + +} /* end mw_mp_read_radix() */ + +/* }}} */ + +/* {{{ mw_mp_radix_size(mp, radix) */ + +int mw_mp_radix_size(mw_mp_int *mp, int radix) +{ + int len; + ARGCHK(mp != NULL, 0); + + len = s_mw_mp_outlen(mw_mp_count_bits(mp), radix) + 1; /* for NUL terminator */ + + if(mw_mp_cmw_mp_z(mp) < 0) + ++len; /* for sign */ + + return len; + +} /* end mw_mp_radix_size() */ + +/* }}} */ + +/* {{{ mw_mp_value_radix_size(num, qty, radix) */ + +/* num = number of digits + qty = number of bits per digit + radix = target base + + Return the number of digits in the specified radix that would be + needed to express 'num' digits of 'qty' bits each. + */ +int mw_mp_value_radix_size(int num, int qty, int radix) +{ + ARGCHK(num >= 0 && qty > 0 && radix >= 2 && radix <= MAX_RADIX, 0); + + return s_mw_mp_outlen(num * qty, radix); + +} /* end mw_mp_value_radix_size() */ + +/* }}} */ + +/* {{{ mw_mp_toradix(mp, str, radix) */ + +mw_mp_err mw_mp_toradix(mw_mp_int *mp, unsigned char *str, int radix) +{ + int ix, pos = 0; + + ARGCHK(mp != NULL && str != NULL, MP_BADARG); + ARGCHK(radix > 1 && radix <= MAX_RADIX, MP_RANGE); + + if(mw_mp_cmw_mp_z(mp) == MP_EQ) { + str[0] = '0'; + str[1] = '\0'; + } else { + mw_mp_err res; + mw_mp_int tmp; + mw_mp_sign sgn; + mw_mp_digit rem, rdx = (mw_mp_digit)radix; + char ch; + + if((res = mw_mp_init_copy(&tmp, mp)) != MP_OKAY) + return res; + + /* Save sign for later, and take absolute value */ + sgn = SIGN(&tmp); SIGN(&tmp) = MP_ZPOS; + + /* Generate output digits in reverse order */ + while(mw_mp_cmw_mp_z(&tmp) != 0) { + if((res = s_mw_mp_div_d(&tmp, rdx, &rem)) != MP_OKAY) { + mw_mp_clear(&tmp); + return res; + } + + /* Generate digits, use capital letters */ + ch = s_mw_mp_todigit(rem, radix, 0); + + str[pos++] = ch; + } + + /* Add - sign if original value was negative */ + if(sgn == MP_NEG) + str[pos++] = '-'; + + /* Add trailing NUL to end the string */ + str[pos--] = '\0'; + + /* Reverse the digits and sign indicator */ + ix = 0; + while(ix < pos) { + char tmp = str[ix]; + + str[ix] = str[pos]; + str[pos] = tmp; + ++ix; + --pos; + } + + mw_mp_clear(&tmp); + } + + return MP_OKAY; + +} /* end mw_mp_toradix() */ + +/* }}} */ + +/* {{{ mw_mp_char2value(ch, r) */ + +int mw_mp_char2value(char ch, int r) +{ + return s_mw_mp_tovalue(ch, r); + +} /* end mw_mp_tovalue() */ + +/* }}} */ + +/* }}} */ + +/* {{{ mw_mp_strerror(ec) */ + +/* + mw_mp_strerror(ec) + + Return a string describing the meaning of error code 'ec'. The + string returned is allocated in static memory, so the caller should + not attempt to modify or free the memory associated with this + string. + */ +const char *mw_mp_strerror(mw_mp_err ec) +{ + int aec = (ec < 0) ? -ec : ec; + + /* Code values are negative, so the senses of these comparisons + are accurate */ + if(ec < MP_LAST_CODE || ec > MP_OKAY) { + return mw_mp_err_string[0]; /* unknown error code */ + } else { + return mw_mp_err_string[aec + 1]; + } + +} /* end mw_mp_strerror() */ + +/* }}} */ + +/*========================================================================*/ +/*------------------------------------------------------------------------*/ +/* Static function definitions (internal use only) */ + +/* {{{ Memory management */ + +/* {{{ s_mw_mp_grow(mp, min) */ + +/* Make sure there are at least 'min' digits allocated to mp */ +mw_mp_err s_mw_mp_grow(mw_mp_int *mp, mw_mp_size min) +{ + if(min > ALLOC(mp)) { + mw_mp_digit *tmp; + + /* Set min to next nearest default precision block size */ + min = ((min + (s_mw_mp_defprec - 1)) / s_mw_mp_defprec) * s_mw_mp_defprec; + + if((tmp = s_mw_mp_alloc(min, sizeof(mw_mp_digit))) == NULL) + return MP_MEM; + + s_mw_mp_copy(DIGITS(mp), tmp, USED(mp)); + +#if MP_CRYPTO + s_mw_mp_setz(DIGITS(mp), ALLOC(mp)); +#endif + s_mw_mp_free(DIGITS(mp)); + DIGITS(mp) = tmp; + ALLOC(mp) = min; + } + + return MP_OKAY; + +} /* end s_mw_mp_grow() */ + +/* }}} */ + +/* {{{ s_mw_mp_pad(mp, min) */ + +/* Make sure the used size of mp is at least 'min', growing if needed */ +mw_mp_err s_mw_mp_pad(mw_mp_int *mp, mw_mp_size min) +{ + if(min > USED(mp)) { + mw_mp_err res; + + /* Make sure there is room to increase precision */ + if(min > ALLOC(mp) && (res = s_mw_mp_grow(mp, min)) != MP_OKAY) + return res; + + /* Increase precision; should already be 0-filled */ + USED(mp) = min; + } + + return MP_OKAY; + +} /* end s_mw_mp_pad() */ + +/* }}} */ + +/* {{{ s_mw_mp_setz(dp, count) */ + +#if MP_MACRO == 0 +/* Set 'count' digits pointed to by dp to be zeroes */ +void s_mw_mp_setz(mw_mp_digit *dp, mw_mp_size count) +{ +#if MP_MEMSET == 0 + int ix; + + for(ix = 0; ix < count; ix++) + dp[ix] = 0; +#else + memset(dp, 0, count * sizeof(mw_mp_digit)); +#endif + +} /* end s_mw_mp_setz() */ +#endif + +/* }}} */ + +/* {{{ s_mw_mp_copy(sp, dp, count) */ + +#if MP_MACRO == 0 +/* Copy 'count' digits from sp to dp */ +void s_mw_mp_copy(mw_mp_digit *sp, mw_mp_digit *dp, mw_mp_size count) +{ +#if MP_MEMCPY == 0 + int ix; + + for(ix = 0; ix < count; ix++) + dp[ix] = sp[ix]; +#else + memcpy(dp, sp, count * sizeof(mw_mp_digit)); +#endif + +} /* end s_mw_mp_copy() */ +#endif + +/* }}} */ + +/* {{{ s_mw_mp_alloc(nb, ni) */ + +#if MP_MACRO == 0 +/* Allocate ni records of nb bytes each, and return a pointer to that */ +void *s_mw_mp_alloc(size_t nb, size_t ni) +{ + return calloc(nb, ni); + +} /* end s_mw_mp_alloc() */ +#endif + +/* }}} */ + +/* {{{ s_mw_mp_free(ptr) */ + +#if MP_MACRO == 0 +/* Free the memory pointed to by ptr */ +void s_mw_mp_free(void *ptr) +{ + if(ptr) + free(ptr); + +} /* end s_mw_mp_free() */ +#endif + +/* }}} */ + +/* {{{ s_mw_mp_clamp(mp) */ + +/* Remove leading zeroes from the given value */ +void s_mw_mp_clamp(mw_mp_int *mp) +{ + mw_mp_size du = USED(mp); + mw_mp_digit *zp = DIGITS(mp) + du - 1; + + while(du > 1 && !*zp--) + --du; + + if(du == 1 && *zp == 0) + SIGN(mp) = MP_ZPOS; + + USED(mp) = du; + +} /* end s_mw_mp_clamp() */ + + +/* }}} */ + +/* {{{ s_mw_mp_exch(a, b) */ + +/* Exchange the data for a and b; (b, a) = (a, b) */ +void s_mw_mp_exch(mw_mp_int *a, mw_mp_int *b) +{ + mw_mp_int tmp; + + tmp = *a; + *a = *b; + *b = tmp; + +} /* end s_mw_mp_exch() */ + +/* }}} */ + +/* }}} */ + +/* {{{ Arithmetic helpers */ + +/* {{{ s_mw_mp_lshd(mp, p) */ + +/* + Shift mp leftward by p digits, growing if needed, and zero-filling + the in-shifted digits at the right end. This is a convenient + alternative to multiplication by powers of the radix + */ + +mw_mp_err s_mw_mp_lshd(mw_mp_int *mp, mw_mp_size p) +{ + mw_mp_err res; + mw_mp_size pos; + mw_mp_digit *dp; + int ix; + + if(p == 0) + return MP_OKAY; + + if((res = s_mw_mp_pad(mp, USED(mp) + p)) != MP_OKAY) + return res; + + pos = USED(mp) - 1; + dp = DIGITS(mp); + + /* Shift all the significant figures over as needed */ + for(ix = pos - p; ix >= 0; ix--) + dp[ix + p] = dp[ix]; + + /* Fill the bottom digits with zeroes */ + for(ix = 0; ix < p; ix++) + dp[ix] = 0; + + return MP_OKAY; + +} /* end s_mw_mp_lshd() */ + +/* }}} */ + +/* {{{ s_mw_mp_rshd(mp, p) */ + +/* + Shift mp rightward by p digits. Maintains the invariant that + digits above the precision are all zero. Digits shifted off the + end are lost. Cannot fail. + */ + +void s_mw_mp_rshd(mw_mp_int *mp, mw_mp_size p) +{ + mw_mp_size ix; + mw_mp_digit *dp; + + if(p == 0) + return; + + /* Shortcut when all digits are to be shifted off */ + if(p >= USED(mp)) { + s_mw_mp_setz(DIGITS(mp), ALLOC(mp)); + USED(mp) = 1; + SIGN(mp) = MP_ZPOS; + return; + } + + /* Shift all the significant figures over as needed */ + dp = DIGITS(mp); + for(ix = p; ix < USED(mp); ix++) + dp[ix - p] = dp[ix]; + + /* Fill the top digits with zeroes */ + ix -= p; + while(ix < USED(mp)) + dp[ix++] = 0; + + /* Strip off any leading zeroes */ + s_mw_mp_clamp(mp); + +} /* end s_mw_mp_rshd() */ + +/* }}} */ + +/* {{{ s_mw_mp_div_2(mp) */ + +/* Divide by two -- take advantage of radix properties to do it fast */ +void s_mw_mp_div_2(mw_mp_int *mp) +{ + s_mw_mp_div_2d(mp, 1); + +} /* end s_mw_mp_div_2() */ + +/* }}} */ + +/* {{{ s_mw_mp_mul_2(mp) */ + +mw_mp_err s_mw_mp_mul_2(mw_mp_int *mp) +{ + int ix; + mw_mp_digit kin = 0, kout, *dp = DIGITS(mp); + mw_mp_err res; + + /* Shift digits leftward by 1 bit */ + for(ix = 0; ix < USED(mp); ix++) { + kout = (dp[ix] >> (DIGIT_BIT - 1)) & 1; + dp[ix] = (dp[ix] << 1) | kin; + + kin = kout; + } + + /* Deal with rollover from last digit */ + if(kin) { + if(ix >= ALLOC(mp)) { + if((res = s_mw_mp_grow(mp, ALLOC(mp) + 1)) != MP_OKAY) + return res; + dp = DIGITS(mp); + } + + dp[ix] = kin; + USED(mp) += 1; + } + + return MP_OKAY; + +} /* end s_mw_mp_mul_2() */ + +/* }}} */ + +/* {{{ s_mw_mp_mod_2d(mp, d) */ + +/* + Remainder the integer by 2^d, where d is a number of bits. This + amounts to a bitwise AND of the value, and does not require the full + division code + */ +void s_mw_mp_mod_2d(mw_mp_int *mp, mw_mp_digit d) +{ + unsigned int ndig = (d / DIGIT_BIT), nbit = (d % DIGIT_BIT); + unsigned int ix; + mw_mp_digit dmask, *dp = DIGITS(mp); + + if(ndig >= USED(mp)) + return; + + /* Flush all the bits above 2^d in its digit */ + dmask = (1 << nbit) - 1; + dp[ndig] &= dmask; + + /* Flush all digits above the one with 2^d in it */ + for(ix = ndig + 1; ix < USED(mp); ix++) + dp[ix] = 0; + + s_mw_mp_clamp(mp); + +} /* end s_mw_mp_mod_2d() */ + +/* }}} */ + +/* {{{ s_mw_mp_mul_2d(mp, d) */ + +/* + Multiply by the integer 2^d, where d is a number of bits. This + amounts to a bitwise shift of the value, and does not require the + full multiplication code. + */ +mw_mp_err s_mw_mp_mul_2d(mw_mp_int *mp, mw_mp_digit d) +{ + mw_mp_err res; + mw_mp_digit save, next, mask, *dp; + mw_mp_size used; + int ix; + + if((res = s_mw_mp_lshd(mp, d / DIGIT_BIT)) != MP_OKAY) + return res; + + dp = DIGITS(mp); used = USED(mp); + d %= DIGIT_BIT; + + mask = (1 << d) - 1; + + /* If the shift requires another digit, make sure we've got one to + work with */ + if((dp[used - 1] >> (DIGIT_BIT - d)) & mask) { + if((res = s_mw_mp_grow(mp, used + 1)) != MP_OKAY) + return res; + dp = DIGITS(mp); + } + + /* Do the shifting... */ + save = 0; + for(ix = 0; ix < used; ix++) { + next = (dp[ix] >> (DIGIT_BIT - d)) & mask; + dp[ix] = (dp[ix] << d) | save; + save = next; + } + + /* If, at this point, we have a nonzero carryout into the next + digit, we'll increase the size by one digit, and store it... + */ + if(save) { + dp[used] = save; + USED(mp) += 1; + } + + s_mw_mp_clamp(mp); + return MP_OKAY; + +} /* end s_mw_mp_mul_2d() */ + +/* }}} */ + +/* {{{ s_mw_mp_div_2d(mp, d) */ + +/* + Divide the integer by 2^d, where d is a number of bits. This + amounts to a bitwise shift of the value, and does not require the + full division code (used in Barrett reduction, see below) + */ +void s_mw_mp_div_2d(mw_mp_int *mp, mw_mp_digit d) +{ + int ix; + mw_mp_digit save, next, mask, *dp = DIGITS(mp); + + s_mw_mp_rshd(mp, d / DIGIT_BIT); + d %= DIGIT_BIT; + + mask = (1 << d) - 1; + + save = 0; + for(ix = USED(mp) - 1; ix >= 0; ix--) { + next = dp[ix] & mask; + dp[ix] = (dp[ix] >> d) | (save << (DIGIT_BIT - d)); + save = next; + } + + s_mw_mp_clamp(mp); + +} /* end s_mw_mp_div_2d() */ + +/* }}} */ + +/* {{{ s_mw_mp_norm(a, b) */ + +/* + s_mw_mp_norm(a, b) + + Normalize a and b for division, where b is the divisor. In order + that we might make good guesses for quotient digits, we want the + leading digit of b to be at least half the radix, which we + accomplish by multiplying a and b by a constant. This constant is + returned (so that it can be divided back out of the remainder at the + end of the division process). + + We multiply by the smallest power of 2 that gives us a leading digit + at least half the radix. By choosing a power of 2, we simplify the + multiplication and division steps to simple shifts. + */ +mw_mp_digit s_mw_mp_norm(mw_mp_int *a, mw_mp_int *b) +{ + mw_mp_digit t, d = 0; + + t = DIGIT(b, USED(b) - 1); + while(t < (RADIX / 2)) { + t <<= 1; + ++d; + } + + if(d != 0) { + s_mw_mp_mul_2d(a, d); + s_mw_mp_mul_2d(b, d); + } + + return d; + +} /* end s_mw_mp_norm() */ + +/* }}} */ + +/* }}} */ + +/* {{{ Primitive digit arithmetic */ + +/* {{{ s_mw_mp_add_d(mp, d) */ + +/* Add d to |mp| in place */ +mw_mp_err s_mw_mp_add_d(mw_mp_int *mp, mw_mp_digit d) /* unsigned digit addition */ +{ + mw_mp_word w, k = 0; + mw_mp_size ix = 1, used = USED(mp); + mw_mp_digit *dp = DIGITS(mp); + + w = dp[0] + d; + dp[0] = ACCUM(w); + k = CARRYOUT(w); + + while(ix < used && k) { + w = dp[ix] + k; + dp[ix] = ACCUM(w); + k = CARRYOUT(w); + ++ix; + } + + if(k != 0) { + mw_mp_err res; + + if((res = s_mw_mp_pad(mp, USED(mp) + 1)) != MP_OKAY) + return res; + + DIGIT(mp, ix) = k; + } + + return MP_OKAY; + +} /* end s_mw_mp_add_d() */ + +/* }}} */ + +/* {{{ s_mw_mp_sub_d(mp, d) */ + +/* Subtract d from |mp| in place, assumes |mp| > d */ +mw_mp_err s_mw_mp_sub_d(mw_mp_int *mp, mw_mp_digit d) /* unsigned digit subtract */ +{ + mw_mp_word w, b = 0; + mw_mp_size ix = 1, used = USED(mp); + mw_mp_digit *dp = DIGITS(mp); + + /* Compute initial subtraction */ + w = (RADIX + dp[0]) - d; + b = CARRYOUT(w) ? 0 : 1; + dp[0] = ACCUM(w); + + /* Propagate borrows leftward */ + while(b && ix < used) { + w = (RADIX + dp[ix]) - b; + b = CARRYOUT(w) ? 0 : 1; + dp[ix] = ACCUM(w); + ++ix; + } + + /* Remove leading zeroes */ + s_mw_mp_clamp(mp); + + /* If we have a borrow out, it's a violation of the input invariant */ + if(b) + return MP_RANGE; + else + return MP_OKAY; + +} /* end s_mw_mp_sub_d() */ + +/* }}} */ + +/* {{{ s_mw_mp_mul_d(a, d) */ + +/* Compute a = a * d, single digit multiplication */ +mw_mp_err s_mw_mp_mul_d(mw_mp_int *a, mw_mp_digit d) +{ + mw_mp_word w, k = 0; + mw_mp_size ix, max; + mw_mp_err res; + mw_mp_digit *dp = DIGITS(a); + + /* + Single-digit multiplication will increase the precision of the + output by at most one digit. However, we can detect when this + will happen -- if the high-order digit of a, times d, gives a + two-digit result, then the precision of the result will increase; + otherwise it won't. We use this fact to avoid calling s_mw_mp_pad() + unless absolutely necessary. + */ + max = USED(a); + w = dp[max - 1] * d; + if(CARRYOUT(w) != 0) { + if((res = s_mw_mp_pad(a, max + 1)) != MP_OKAY) + return res; + dp = DIGITS(a); + } + + for(ix = 0; ix < max; ix++) { + w = (dp[ix] * d) + k; + dp[ix] = ACCUM(w); + k = CARRYOUT(w); + } + + /* If there is a precision increase, take care of it here; the above + test guarantees we have enough storage to do this safely. + */ + if(k) { + dp[max] = k; + USED(a) = max + 1; + } + + s_mw_mp_clamp(a); + + return MP_OKAY; + +} /* end s_mw_mp_mul_d() */ + +/* }}} */ + +/* {{{ s_mw_mp_div_d(mp, d, r) */ + +/* + s_mw_mp_div_d(mp, d, r) + + Compute the quotient mp = mp / d and remainder r = mp mod d, for a + single digit d. If r is null, the remainder will be discarded. + */ + +mw_mp_err s_mw_mp_div_d(mw_mp_int *mp, mw_mp_digit d, mw_mp_digit *r) +{ + mw_mp_word w = 0, t; + mw_mp_int quot; + mw_mp_err res; + mw_mp_digit *dp = DIGITS(mp), *qp; + int ix; + + if(d == 0) + return MP_RANGE; + + /* Make room for the quotient */ + if((res = mw_mp_init_size(", USED(mp))) != MP_OKAY) + return res; + + USED(") = USED(mp); /* so clamping will work below */ + qp = DIGITS("); + + /* Divide without subtraction */ + for(ix = USED(mp) - 1; ix >= 0; ix--) { + w = (w << DIGIT_BIT) | dp[ix]; + + if(w >= d) { + t = w / d; + w = w % d; + } else { + t = 0; + } + + qp[ix] = t; + } + + /* Deliver the remainder, if desired */ + if(r) + *r = w; + + s_mw_mp_clamp("); + mw_mp_exch(", mp); + mw_mp_clear("); + + return MP_OKAY; + +} /* end s_mw_mp_div_d() */ + +/* }}} */ + +/* }}} */ + +/* {{{ Primitive full arithmetic */ + +/* {{{ s_mw_mp_add(a, b) */ + +/* Compute a = |a| + |b| */ +mw_mp_err s_mw_mp_add(mw_mp_int *a, mw_mp_int *b) /* magnitude addition */ +{ + mw_mp_word w = 0; + mw_mp_digit *pa, *pb; + mw_mp_size ix, used = USED(b); + mw_mp_err res; + + /* Make sure a has enough precision for the output value */ + if((used > USED(a)) && (res = s_mw_mp_pad(a, used)) != MP_OKAY) + return res; + + /* + Add up all digits up to the precision of b. If b had initially + the same precision as a, or greater, we took care of it by the + padding step above, so there is no problem. If b had initially + less precision, we'll have to make sure the carry out is duly + propagated upward among the higher-order digits of the sum. + */ + pa = DIGITS(a); + pb = DIGITS(b); + for(ix = 0; ix < used; ++ix) { + w += *pa + *pb++; + *pa++ = ACCUM(w); + w = CARRYOUT(w); + } + + /* If we run out of 'b' digits before we're actually done, make + sure the carries get propagated upward... + */ + used = USED(a); + while(w && ix < used) { + w += *pa; + *pa++ = ACCUM(w); + w = CARRYOUT(w); + ++ix; + } + + /* If there's an overall carry out, increase precision and include + it. We could have done this initially, but why touch the memory + allocator unless we're sure we have to? + */ + if(w) { + if((res = s_mw_mp_pad(a, used + 1)) != MP_OKAY) + return res; + + DIGIT(a, ix) = w; /* pa may not be valid after s_mw_mp_pad() call */ + } + + return MP_OKAY; + +} /* end s_mw_mp_add() */ + +/* }}} */ + +/* {{{ s_mw_mp_sub(a, b) */ + +/* Compute a = |a| - |b|, assumes |a| >= |b| */ +mw_mp_err s_mw_mp_sub(mw_mp_int *a, mw_mp_int *b) /* magnitude subtract */ +{ + mw_mp_word w = 0; + mw_mp_digit *pa, *pb; + mw_mp_size ix, used = USED(b); + + /* + Subtract and propagate borrow. Up to the precision of b, this + accounts for the digits of b; after that, we just make sure the + carries get to the right place. This saves having to pad b out to + the precision of a just to make the loops work right... + */ + pa = DIGITS(a); + pb = DIGITS(b); + + for(ix = 0; ix < used; ++ix) { + w = (RADIX + *pa) - w - *pb++; + *pa++ = ACCUM(w); + w = CARRYOUT(w) ? 0 : 1; + } + + used = USED(a); + while(ix < used) { + w = RADIX + *pa - w; + *pa++ = ACCUM(w); + w = CARRYOUT(w) ? 0 : 1; + ++ix; + } + + /* Clobber any leading zeroes we created */ + s_mw_mp_clamp(a); + + /* + If there was a borrow out, then |b| > |a| in violation + of our input invariant. We've already done the work, + but we'll at least complain about it... + */ + if(w) + return MP_RANGE; + else + return MP_OKAY; + +} /* end s_mw_mp_sub() */ + +/* }}} */ + +/* {{{ s_mw_mp_mul(a, b) */ + +/* Compute a = |a| * |b| */ +mw_mp_err s_mw_mp_mul(mw_mp_int *a, mw_mp_int *b) +{ + mw_mp_word w, k = 0; + mw_mp_int tmp; + mw_mp_err res; + mw_mp_size ix, jx, ua = USED(a), ub = USED(b); + mw_mp_digit *pa, *pb, *pt, *pbt; + + if((res = mw_mp_init_size(&tmp, ua + ub)) != MP_OKAY) + return res; + + /* This has the effect of left-padding with zeroes... */ + USED(&tmp) = ua + ub; + + /* We're going to need the base value each iteration */ + pbt = DIGITS(&tmp); + + /* Outer loop: Digits of b */ + + pb = DIGITS(b); + for(ix = 0; ix < ub; ++ix, ++pb) { + if(*pb == 0) + continue; + + /* Inner product: Digits of a */ + pa = DIGITS(a); + for(jx = 0; jx < ua; ++jx, ++pa) { + pt = pbt + ix + jx; + w = *pb * *pa + k + *pt; + *pt = ACCUM(w); + k = CARRYOUT(w); + } + + pbt[ix + jx] = k; + k = 0; + } + + s_mw_mp_clamp(&tmp); + s_mw_mp_exch(&tmp, a); + + mw_mp_clear(&tmp); + + return MP_OKAY; + +} /* end s_mw_mp_mul() */ + +/* }}} */ + +/* {{{ s_mw_mp_kmul(a, b, out, len) */ + +#if 0 +void s_mw_mp_kmul(mw_mp_digit *a, mw_mp_digit *b, mw_mp_digit *out, mw_mp_size len) +{ + mw_mp_word w, k = 0; + mw_mp_size ix, jx; + mw_mp_digit *pa, *pt; + + for(ix = 0; ix < len; ++ix, ++b) { + if(*b == 0) + continue; + + pa = a; + for(jx = 0; jx < len; ++jx, ++pa) { + pt = out + ix + jx; + w = *b * *pa + k + *pt; + *pt = ACCUM(w); + k = CARRYOUT(w); + } + + out[ix + jx] = k; + k = 0; + } + +} /* end s_mw_mp_kmul() */ +#endif + +/* }}} */ + +/* {{{ s_mw_mp_sqr(a) */ + +/* + Computes the square of a, in place. This can be done more + efficiently than a general multiplication, because many of the + computation steps are redundant when squaring. The inner product + step is a bit more complicated, but we save a fair number of + iterations of the multiplication loop. + */ +#if MP_SQUARE +mw_mp_err s_mw_mp_sqr(mw_mp_int *a) +{ + mw_mp_word w, k = 0; + mw_mp_int tmp; + mw_mp_err res; + mw_mp_size ix, jx, kx, used = USED(a); + mw_mp_digit *pa1, *pa2, *pt, *pbt; + + if((res = mw_mp_init_size(&tmp, 2 * used)) != MP_OKAY) + return res; + + /* Left-pad with zeroes */ + USED(&tmp) = 2 * used; + + /* We need the base value each time through the loop */ + pbt = DIGITS(&tmp); + + pa1 = DIGITS(a); + for(ix = 0; ix < used; ++ix, ++pa1) { + if(*pa1 == 0) + continue; + + w = DIGIT(&tmp, ix + ix) + (*pa1 * *pa1); + + pbt[ix + ix] = ACCUM(w); + k = CARRYOUT(w); + + /* + The inner product is computed as: + + (C, S) = t[i,j] + 2 a[i] a[j] + C + + This can overflow what can be represented in an mw_mp_word, and + since C arithmetic does not provide any way to check for + overflow, we have to check explicitly for overflow conditions + before they happen. + */ + for(jx = ix + 1, pa2 = DIGITS(a) + jx; jx < used; ++jx, ++pa2) { + mw_mp_word u = 0, v; + + /* Store this in a temporary to avoid indirections later */ + pt = pbt + ix + jx; + + /* Compute the multiplicative step */ + w = *pa1 * *pa2; + + /* If w is more than half MP_WORD_MAX, the doubling will + overflow, and we need to record a carry out into the next + word */ + u = (w >> (MP_WORD_BIT - 1)) & 1; + + /* Double what we've got, overflow will be ignored as defined + for C arithmetic (we've already noted if it is to occur) + */ + w *= 2; + + /* Compute the additive step */ + v = *pt + k; + + /* If we do not already have an overflow carry, check to see + if the addition will cause one, and set the carry out if so + */ + u |= ((MP_WORD_MAX - v) < w); + + /* Add in the rest, again ignoring overflow */ + w += v; + + /* Set the i,j digit of the output */ + *pt = ACCUM(w); + + /* Save carry information for the next iteration of the loop. + This is why k must be an mw_mp_word, instead of an mw_mp_digit */ + k = CARRYOUT(w) | (u << DIGIT_BIT); + + } /* for(jx ...) */ + + /* Set the last digit in the cycle and reset the carry */ + k = DIGIT(&tmp, ix + jx) + k; + pbt[ix + jx] = ACCUM(k); + k = CARRYOUT(k); + + /* If we are carrying out, propagate the carry to the next digit + in the output. This may cascade, so we have to be somewhat + circumspect -- but we will have enough precision in the output + that we won't overflow + */ + kx = 1; + while(k) { + k = pbt[ix + jx + kx] + 1; + pbt[ix + jx + kx] = ACCUM(k); + k = CARRYOUT(k); + ++kx; + } + } /* for(ix ...) */ + + s_mw_mp_clamp(&tmp); + s_mw_mp_exch(&tmp, a); + + mw_mp_clear(&tmp); + + return MP_OKAY; + +} /* end s_mw_mp_sqr() */ +#endif + +/* }}} */ + +/* {{{ s_mw_mp_div(a, b) */ + +/* + s_mw_mp_div(a, b) + + Compute a = a / b and b = a mod b. Assumes b > a. + */ + +mw_mp_err s_mw_mp_div(mw_mp_int *a, mw_mp_int *b) +{ + mw_mp_int quot, rem, t; + mw_mp_word q; + mw_mp_err res; + mw_mp_digit d; + int ix; + + if(mw_mp_cmw_mp_z(b) == 0) + return MP_RANGE; + + /* Shortcut if b is power of two */ + if((ix = s_mw_mp_ispow2(b)) >= 0) { + mw_mp_copy(a, b); /* need this for remainder */ + s_mw_mp_div_2d(a, (mw_mp_digit)ix); + s_mw_mp_mod_2d(b, (mw_mp_digit)ix); + + return MP_OKAY; + } + + /* Allocate space to store the quotient */ + if((res = mw_mp_init_size(", USED(a))) != MP_OKAY) + return res; + + /* A working temporary for division */ + if((res = mw_mp_init_size(&t, USED(a))) != MP_OKAY) + goto T; + + /* Allocate space for the remainder */ + if((res = mw_mp_init_size(&rem, USED(a))) != MP_OKAY) + goto REM; + + /* Normalize to optimize guessing */ + d = s_mw_mp_norm(a, b); + + /* Perform the division itself...woo! */ + ix = USED(a) - 1; + + while(ix >= 0) { + /* Find a partial substring of a which is at least b */ + while(s_mw_mp_cmp(&rem, b) < 0 && ix >= 0) { + if((res = s_mw_mp_lshd(&rem, 1)) != MP_OKAY) + goto CLEANUP; + + if((res = s_mw_mp_lshd(", 1)) != MP_OKAY) + goto CLEANUP; + + DIGIT(&rem, 0) = DIGIT(a, ix); + s_mw_mp_clamp(&rem); + --ix; + } + + /* If we didn't find one, we're finished dividing */ + if(s_mw_mp_cmp(&rem, b) < 0) + break; + + /* Compute a guess for the next quotient digit */ + q = DIGIT(&rem, USED(&rem) - 1); + if(q <= DIGIT(b, USED(b) - 1) && USED(&rem) > 1) + q = (q << DIGIT_BIT) | DIGIT(&rem, USED(&rem) - 2); + + q /= DIGIT(b, USED(b) - 1); + + /* The guess can be as much as RADIX + 1 */ + if(q >= RADIX) + q = RADIX - 1; + + /* See what that multiplies out to */ + mw_mp_copy(b, &t); + if((res = s_mw_mp_mul_d(&t, q)) != MP_OKAY) + goto CLEANUP; + + /* + If it's too big, back it off. We should not have to do this + more than once, or, in rare cases, twice. Knuth describes a + method by which this could be reduced to a maximum of once, but + I didn't implement that here. + */ + while(s_mw_mp_cmp(&t, &rem) > 0) { + --q; + s_mw_mp_sub(&t, b); + } + + /* At this point, q should be the right next digit */ + if((res = s_mw_mp_sub(&rem, &t)) != MP_OKAY) + goto CLEANUP; + + /* + Include the digit in the quotient. We allocated enough memory + for any quotient we could ever possibly get, so we should not + have to check for failures here + */ + DIGIT(", 0) = q; + } + + /* Denormalize remainder */ + if(d != 0) + s_mw_mp_div_2d(&rem, d); + + s_mw_mp_clamp("); + s_mw_mp_clamp(&rem); + + /* Copy quotient back to output */ + s_mw_mp_exch(", a); + + /* Copy remainder back to output */ + s_mw_mp_exch(&rem, b); + +CLEANUP: + mw_mp_clear(&rem); +REM: + mw_mp_clear(&t); +T: + mw_mp_clear("); + + return res; + +} /* end s_mw_mp_div() */ + +/* }}} */ + +/* {{{ s_mw_mp_2expt(a, k) */ + +mw_mp_err s_mw_mp_2expt(mw_mp_int *a, mw_mp_digit k) +{ + mw_mp_err res; + mw_mp_size dig, bit; + + dig = k / DIGIT_BIT; + bit = k % DIGIT_BIT; + + mw_mp_zero(a); + if((res = s_mw_mp_pad(a, dig + 1)) != MP_OKAY) + return res; + + DIGIT(a, dig) |= (1 << bit); + + return MP_OKAY; + +} /* end s_mw_mp_2expt() */ + +/* }}} */ + +/* {{{ s_mw_mp_reduce(x, m, mu) */ + +/* + Compute Barrett reduction, x (mod m), given a precomputed value for + mu = b^2k / m, where b = RADIX and k = #digits(m). This should be + faster than straight division, when many reductions by the same + value of m are required (such as in modular exponentiation). This + can nearly halve the time required to do modular exponentiation, + as compared to using the full integer divide to reduce. + + This algorithm was derived from the _Handbook of Applied + Cryptography_ by Menezes, Oorschot and VanStone, Ch. 14, + pp. 603-604. + */ + +mw_mp_err s_mw_mp_reduce(mw_mp_int *x, mw_mp_int *m, mw_mp_int *mu) +{ + mw_mp_int q; + mw_mp_err res; + mw_mp_size um = USED(m); + + if((res = mw_mp_init_copy(&q, x)) != MP_OKAY) + return res; + + s_mw_mp_rshd(&q, um - 1); /* q1 = x / b^(k-1) */ + s_mw_mp_mul(&q, mu); /* q2 = q1 * mu */ + s_mw_mp_rshd(&q, um + 1); /* q3 = q2 / b^(k+1) */ + + /* x = x mod b^(k+1), quick (no division) */ + s_mw_mp_mod_2d(x, DIGIT_BIT * (um + 1)); + + /* q = q * m mod b^(k+1), quick (no division) */ + s_mw_mp_mul(&q, m); + s_mw_mp_mod_2d(&q, DIGIT_BIT * (um + 1)); + + /* x = x - q */ + if((res = mw_mp_sub(x, &q, x)) != MP_OKAY) + goto CLEANUP; + + /* If x < 0, add b^(k+1) to it */ + if(mw_mp_cmw_mp_z(x) < 0) { + mw_mp_set(&q, 1); + if((res = s_mw_mp_lshd(&q, um + 1)) != MP_OKAY) + goto CLEANUP; + if((res = mw_mp_add(x, &q, x)) != MP_OKAY) + goto CLEANUP; + } + + /* Back off if it's too big */ + while(mw_mp_cmp(x, m) >= 0) { + if((res = s_mw_mp_sub(x, m)) != MP_OKAY) + break; + } + + CLEANUP: + mw_mp_clear(&q); + + return res; + +} /* end s_mw_mp_reduce() */ + +/* }}} */ + +/* }}} */ + +/* {{{ Primitive comparisons */ + +/* {{{ s_mw_mp_cmp(a, b) */ + +/* Compare |a| <=> |b|, return 0 if equal, <0 if a0 if a>b */ +int s_mw_mp_cmp(mw_mp_int *a, mw_mp_int *b) +{ + mw_mp_size ua = USED(a), ub = USED(b); + + if(ua > ub) + return MP_GT; + else if(ua < ub) + return MP_LT; + else { + int ix = ua - 1; + mw_mp_digit *ap = DIGITS(a) + ix, *bp = DIGITS(b) + ix; + + while(ix >= 0) { + if(*ap > *bp) + return MP_GT; + else if(*ap < *bp) + return MP_LT; + + --ap; --bp; --ix; + } + + return MP_EQ; + } + +} /* end s_mw_mp_cmp() */ + +/* }}} */ + +/* {{{ s_mw_mp_cmw_mp_d(a, d) */ + +/* Compare |a| <=> d, return 0 if equal, <0 if a0 if a>d */ +int s_mw_mp_cmw_mp_d(mw_mp_int *a, mw_mp_digit d) +{ + mw_mp_size ua = USED(a); + mw_mp_digit *ap = DIGITS(a); + + if(ua > 1) + return MP_GT; + + if(*ap < d) + return MP_LT; + else if(*ap > d) + return MP_GT; + else + return MP_EQ; + +} /* end s_mw_mp_cmw_mp_d() */ + +/* }}} */ + +/* {{{ s_mw_mp_ispow2(v) */ + +/* + Returns -1 if the value is not a power of two; otherwise, it returns + k such that v = 2^k, i.e. lg(v). + */ +int s_mw_mp_ispow2(mw_mp_int *v) +{ + mw_mp_digit d, *dp; + mw_mp_size uv = USED(v); + int extra = 0, ix; + + d = DIGIT(v, uv - 1); /* most significant digit of v */ + + while(d && ((d & 1) == 0)) { + d >>= 1; + ++extra; + } + + if(d == 1) { + ix = uv - 2; + dp = DIGITS(v) + ix; + + while(ix >= 0) { + if(*dp) + return -1; /* not a power of two */ + + --dp; --ix; + } + + return ((uv - 1) * DIGIT_BIT) + extra; + } + + return -1; + +} /* end s_mw_mp_ispow2() */ + +/* }}} */ + +/* {{{ s_mw_mp_ispow2d(d) */ + +int s_mw_mp_ispow2d(mw_mp_digit d) +{ + int pow = 0; + + while((d & 1) == 0) { + ++pow; d >>= 1; + } + + if(d == 1) + return pow; + + return -1; + +} /* end s_mw_mp_ispow2d() */ + +/* }}} */ + +/* }}} */ + +/* {{{ Primitive I/O helpers */ + +/* {{{ s_mw_mp_tovalue(ch, r) */ + +/* + Convert the given character to its digit value, in the given radix. + If the given character is not understood in the given radix, -1 is + returned. Otherwise the digit's numeric value is returned. + + The results will be odd if you use a radix < 2 or > 62, you are + expected to know what you're up to. + */ +int s_mw_mp_tovalue(char ch, int r) +{ + int val, xch; + + if(r > 36) + xch = ch; + else + xch = toupper(ch); + + if(isdigit(xch)) + val = xch - '0'; + else if(isupper(xch)) + val = xch - 'A' + 10; + else if(islower(xch)) + val = xch - 'a' + 36; + else if(xch == '+') + val = 62; + else if(xch == '/') + val = 63; + else + return -1; + + if(val < 0 || val >= r) + return -1; + + return val; + +} /* end s_mw_mp_tovalue() */ + +/* }}} */ + +/* {{{ s_mw_mp_todigit(val, r, low) */ + +/* + Convert val to a radix-r digit, if possible. If val is out of range + for r, returns zero. Otherwise, returns an ASCII character denoting + the value in the given radix. + + The results may be odd if you use a radix < 2 or > 64, you are + expected to know what you're doing. + */ + +char s_mw_mp_todigit(int val, int r, int low) +{ + char ch; + + if(val < 0 || val >= r) + return 0; + + ch = s_dmap_1[val]; + + if(r <= 36 && low) + ch = tolower(ch); + + return ch; + +} /* end s_mw_mp_todigit() */ + +/* }}} */ + +/* {{{ s_mw_mp_outlen(bits, radix) */ + +/* + Return an estimate for how long a string is needed to hold a radix + r representation of a number with 'bits' significant bits. + + Does not include space for a sign or a NUL terminator. + */ +int s_mw_mp_outlen(int bits, int r) +{ + return (int)((double)bits * LOG_V_2(r) + 0.5); + +} /* end s_mw_mp_outlen() */ + +/* }}} */ + +/* }}} */ + +/*------------------------------------------------------------------------*/ +/* HERE THERE BE DRAGONS */ diff --git a/src/mpi/mpi.h b/src/mpi/mpi.h new file mode 100644 index 0000000..0e7cc52 --- /dev/null +++ b/src/mpi/mpi.h @@ -0,0 +1,221 @@ +/* + mpi.h + + by Michael J. Fromberger + Copyright (C) 1998 Michael J. Fromberger, All Rights Reserved + + Arbitrary precision integer arithmetic library + + modified for use in Meanwhile as a convenience library +*/ + +#ifndef _H_MPI_ +#define _H_MPI_ + +#include "mpi-config.h" + +#if MP_DEBUG +#undef MP_IOFUNC +#define MP_IOFUNC 1 +#endif + +#if MP_IOFUNC +#include +#include +#endif + +#include + +#define MP_NEG 1 +#define MP_ZPOS 0 + +/* Included for compatibility... */ +#define NEG MP_NEG +#define ZPOS MP_ZPOS + +#define MP_OKAY 0 /* no error, all is well */ +#define MP_YES 0 /* yes (boolean result) */ +#define MP_NO -1 /* no (boolean result) */ +#define MP_MEM -2 /* out of memory */ +#define MP_RANGE -3 /* argument out of range */ +#define MP_BADARG -4 /* invalid parameter */ +#define MP_UNDEF -5 /* answer is undefined */ +#define MP_LAST_CODE MP_UNDEF + +#include "mpi-types.h" + +/* Included for compatibility... */ +#define DIGIT_BIT MP_DIGIT_BIT +#define DIGIT_MAX MP_DIGIT_MAX + +/* Macros for accessing the mw_mp_int internals */ +#define SIGN(MP) ((MP)->sign) +#define USED(MP) ((MP)->used) +#define ALLOC(MP) ((MP)->alloc) +#define DIGITS(MP) ((MP)->dp) +#define DIGIT(MP,N) (MP)->dp[(N)] + +#if MP_ARGCHK == 1 +#define ARGCHK(X,Y) {if(!(X)){return (Y);}} +#elif MP_ARGCHK == 2 +#include +#define ARGCHK(X,Y) assert(X) +#else +#define ARGCHK(X,Y) /* */ +#endif + +/* This defines the maximum I/O base (minimum is 2) */ +#define MAX_RADIX 64 + +typedef struct { + mw_mp_sign sign; /* sign of this quantity */ + mw_mp_size alloc; /* how many digits allocated */ + mw_mp_size used; /* how many digits used */ + mw_mp_digit *dp; /* the digits themselves */ +} mw_mp_int; + +/*------------------------------------------------------------------------*/ +/* Default precision */ + +unsigned int mw_mp_get_prec(void); +void mw_mp_set_prec(unsigned int prec); + +/*------------------------------------------------------------------------*/ +/* Memory management */ + +mw_mp_err mw_mp_init(mw_mp_int *mp); +mw_mp_err mw_mp_init_array(mw_mp_int mp[], int count); +mw_mp_err mw_mp_init_size(mw_mp_int *mp, mw_mp_size prec); +mw_mp_err mw_mp_init_copy(mw_mp_int *mp, mw_mp_int *from); +mw_mp_err mw_mp_copy(mw_mp_int *from, mw_mp_int *to); +void mw_mp_exch(mw_mp_int *mp1, mw_mp_int *mp2); +void mw_mp_clear(mw_mp_int *mp); +void mw_mp_clear_array(mw_mp_int mp[], int count); +void mw_mp_zero(mw_mp_int *mp); +void mw_mp_set(mw_mp_int *mp, mw_mp_digit d); +mw_mp_err mw_mp_set_int(mw_mp_int *mp, long z); + +/*------------------------------------------------------------------------*/ +/* Single digit arithmetic */ + +mw_mp_err mw_mp_add_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *b); +mw_mp_err mw_mp_sub_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *b); +mw_mp_err mw_mp_mul_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *b); +mw_mp_err mw_mp_mul_2(mw_mp_int *a, mw_mp_int *c); +mw_mp_err mw_mp_div_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *q, mw_mp_digit *r); +mw_mp_err mw_mp_div_2(mw_mp_int *a, mw_mp_int *c); +mw_mp_err mw_mp_expt_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *c); + +/*------------------------------------------------------------------------*/ +/* Sign manipulations */ + +mw_mp_err mw_mp_abs(mw_mp_int *a, mw_mp_int *b); +mw_mp_err mw_mp_neg(mw_mp_int *a, mw_mp_int *b); + +/*------------------------------------------------------------------------*/ +/* Full arithmetic */ + +mw_mp_err mw_mp_add(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c); +mw_mp_err mw_mp_sub(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c); +mw_mp_err mw_mp_mul(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c); +mw_mp_err mw_mp_mul_2d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *c); +#if MP_SQUARE +mw_mp_err mw_mp_sqr(mw_mp_int *a, mw_mp_int *b); +#else +#define mw_mp_sqr(a, b) mw_mp_mul(a, a, b) +#endif +mw_mp_err mw_mp_div(mw_mp_int *a, mw_mp_int *b, mw_mp_int *q, mw_mp_int *r); +mw_mp_err mw_mp_div_2d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *q, mw_mp_int *r); +mw_mp_err mw_mp_expt(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c); +mw_mp_err mw_mp_2expt(mw_mp_int *a, mw_mp_digit k); +mw_mp_err mw_mp_sqrt(mw_mp_int *a, mw_mp_int *b); + +/*------------------------------------------------------------------------*/ +/* Modular arithmetic */ + +#if MP_MODARITH +mw_mp_err mw_mp_mod(mw_mp_int *a, mw_mp_int *m, mw_mp_int *c); +mw_mp_err mw_mp_mod_d(mw_mp_int *a, mw_mp_digit d, mw_mp_digit *c); +mw_mp_err mw_mp_addmod(mw_mp_int *a, mw_mp_int *b, mw_mp_int *m, mw_mp_int *c); +mw_mp_err mw_mp_submod(mw_mp_int *a, mw_mp_int *b, mw_mp_int *m, mw_mp_int *c); +mw_mp_err mw_mp_mulmod(mw_mp_int *a, mw_mp_int *b, mw_mp_int *m, mw_mp_int *c); +#if MP_SQUARE +mw_mp_err mw_mp_sqrmod(mw_mp_int *a, mw_mp_int *m, mw_mp_int *c); +#else +#define mw_mp_sqrmod(a, m, c) mw_mp_mulmod(a, a, m, c) +#endif +mw_mp_err mw_mp_exptmod(mw_mp_int *a, mw_mp_int *b, mw_mp_int *m, mw_mp_int *c); +mw_mp_err mw_mp_exptmod_d(mw_mp_int *a, mw_mp_digit d, mw_mp_int *m, mw_mp_int *c); +#endif /* MP_MODARITH */ + +/*------------------------------------------------------------------------*/ +/* Comparisons */ + +int mw_mp_cmw_mp_z(mw_mp_int *a); +int mw_mp_cmw_mp_d(mw_mp_int *a, mw_mp_digit d); +int mw_mp_cmp(mw_mp_int *a, mw_mp_int *b); +int mw_mp_cmw_mp_mag(mw_mp_int *a, mw_mp_int *b); +int mw_mp_cmw_mp_int(mw_mp_int *a, long z); +int mw_mp_isodd(mw_mp_int *a); +int mw_mp_iseven(mw_mp_int *a); + +/*------------------------------------------------------------------------*/ +/* Number theoretic */ + +#if MP_NUMTH +mw_mp_err mw_mp_gcd(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c); +mw_mp_err mw_mp_lcm(mw_mp_int *a, mw_mp_int *b, mw_mp_int *c); +mw_mp_err mw_mp_xgcd(mw_mp_int *a, mw_mp_int *b, mw_mp_int *g, mw_mp_int *x, mw_mp_int *y); +mw_mp_err mw_mp_invmod(mw_mp_int *a, mw_mp_int *m, mw_mp_int *c); +#endif /* end MP_NUMTH */ + +/*------------------------------------------------------------------------*/ +/* Input and output */ + +#if MP_IOFUNC +void mw_mp_print(mw_mp_int *mp, FILE *ofp); +#endif /* end MP_IOFUNC */ + +/*------------------------------------------------------------------------*/ +/* Base conversion */ + +#define BITS 1 +#define BYTES CHAR_BIT + +mw_mp_err mw_mp_read_signed_bin(mw_mp_int *mp, unsigned char *str, int len); +int mw_mp_signed_bin_size(mw_mp_int *mp); +mw_mp_err mw_mp_to_signed_bin(mw_mp_int *mp, unsigned char *str); + +mw_mp_err mw_mp_read_unsigned_bin(mw_mp_int *mp, unsigned char *str, int len); +int mw_mp_unsigned_bin_size(mw_mp_int *mp); +mw_mp_err mw_mp_to_unsigned_bin(mw_mp_int *mp, unsigned char *str); + +int mw_mp_count_bits(mw_mp_int *mp); + +#if MP_COMPAT_MACROS +#define mw_mp_read_raw(mp, str, len) mw_mp_read_signed_bin((mp), (str), (len)) +#define mw_mp_raw_size(mp) mw_mp_signed_bin_size(mp) +#define mw_mp_toraw(mp, str) mw_mp_to_signed_bin((mp), (str)) +#define mw_mp_read_mag(mp, str, len) mw_mp_read_unsigned_bin((mp), (str), (len)) +#define mw_mp_mag_size(mp) mw_mp_unsigned_bin_size(mp) +#define mw_mp_tomag(mp, str) mw_mp_to_unsigned_bin((mp), (str)) +#endif + +mw_mp_err mw_mp_read_radix(mw_mp_int *mp, unsigned char *str, int radix); +int mw_mp_radix_size(mw_mp_int *mp, int radix); +int mw_mp_value_radix_size(int num, int qty, int radix); +mw_mp_err mw_mp_toradix(mw_mp_int *mp, unsigned char *str, int radix); + +int mw_mp_char2value(char ch, int r); + +#define mw_mp_tobinary(M, S) mw_mp_toradix((M), (S), 2) +#define mw_mp_tooctal(M, S) mw_mp_toradix((M), (S), 8) +#define mw_mp_todecimal(M, S) mw_mp_toradix((M), (S), 10) +#define mw_mp_tohex(M, S) mw_mp_toradix((M), (S), 16) + +/*------------------------------------------------------------------------*/ +/* Error strings */ + +const char *mw_mp_strerror(mw_mp_err ec); + +#endif /* end _H_MPI_ */ diff --git a/src/mw_channel.h b/src/mw_channel.h new file mode 100644 index 0000000..7af5e22 --- /dev/null +++ b/src/mw_channel.h @@ -0,0 +1,380 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_CHANNEL_H +#define _MW_CHANNEL_H + + +/** @file mw_channel.h + +Life-cycle of an outgoing channel: + +1: mwChannel_new is called. If there is a channel in the outgoing +collection in state NEW, then it is returned. Otherwise, a channel +is allocated, assigned a unique outgoing id, marked as NEW, and +returned. + +2: channel is set to INIT status (effectively earmarking it as in- +use). fields on the channel can then be set as necessary to +prepare it for creation. + +3: mwChannel_create is called. The channel is marked to WAIT status +and a message is sent to the server. The channel is also marked as +inactive as of that moment. + +4: the channel is accepted (step 5) or rejected (step 7) + +5: an accept message is received from the server, and the channel +is marked as OPEN, and the inactive mark is removed. And messages +in the in or out queues for that channel are processed. The channel +is now ready to be used. + +6: data is sent and received over the channel + +7: the channel is closed either by receipt of a close message or by +local action. If by local action, then a close message is sent to +the server. The channel is cleaned up, its queues dumped, and it +is set to NEW status to await re-use. + +Life-cycle of an incoming channel: + +1: a channel create message is received. A channel is allocated and +given an id matching the message. It is placed in status WAIT, and +marked as inactive as of that moment. The service matching that +channel is alerted of the incoming creation request. + +2: the service can either accept (step 3) or reject (step 5) the +channel + +3: mwChannel_accept is called. The channel is marked as OPEN, and +an accept message is sent to the server. And messages in the in or +out queues for that channel are processed. The channel is now ready +to be used. + +4: data is sent and received over the channel + +5: The channel is closed either by receipt of a close message or by +local action. If by local action, then a close message is sent to +the server. The channel is cleaned up, its queues dumped, and it +is deallocated. */ + + +#include +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* place-holders */ +struct mwCipherInstance; +struct mwMsgChannelAccept; +struct mwMsgChannelCreate; +struct mwMsgChannelDestroy; +struct mwMsgChannelSend; +struct mwService; +struct mwSession; + + + +/** @struct mwChannel + Represents a channel to a service */ +struct mwChannel; + + +/** @struct mwChannelSet + Collection of channels */ +struct mwChannelSet; + + +/** special ID indicating the master channel */ +#define MW_MASTER_CHANNEL_ID 0x00000000 + + +/** non-zero if a channel id appears to be that of an outgoing channel */ +#define mwChannel_idIsOutgoing(id) \ + (! (0x80000000 & (id))) + +/** non-zero if a channel id appears to be that of an incoming channel */ +#define mwChannel_idIsIncoming(id) \ + (! mwChannel_idIsOutgoing(id)) + +/** non-zero if a channel appears to be an outgoing channel */ +#define mwChannel_isOutgoing(chan) \ + mwChannel_idIsOutgoing(mwChannel_getId(chan)) + +/** non-zero if a channel appears to be an incoming channel */ +#define mwChannel_isIncoming(chan) \ + mwChannel_idIsIncoming(mwChannel_getId(chan)) + + +/** channel status */ +enum mwChannelState { + mwChannel_NEW, /**< channel is newly allocated, in the pool */ + mwChannel_INIT, /**< channel is being prepared, out of the pool */ + mwChannel_WAIT, /**< channel is waiting for accept */ + mwChannel_OPEN, /**< channel is accepted and open */ + mwChannel_DESTROY, /**< channel is being destroyed */ + mwChannel_ERROR, /**< channel is being destroyed due to error */ + mwChannel_UNKNOWN, /**< unknown state, or error determining state */ +}; + + +#define mwChannel_isState(chan, state) \ + (mwChannel_getState(chan) == (state)) + + +/** channel statistic fields. + @see mwChannel_getStatistic */ +enum mwChannelStatField { + mwChannelStat_MSG_SENT, /**< total send-on-chan messages sent */ + mwChannelStat_MSG_RECV, /**< total send-on-chan messages received */ + mwChannelStat_U_BYTES_SENT, /**< total bytes sent, pre-encryption */ + mwChannelStat_U_BYTES_RECV, /**< total bytes received, post-decryption */ + mwChannelStat_OPENED_AT, /**< time when channel was opened */ + mwChannelStat_CLOSED_AT, /**< time when channel was closed */ +}; + + +/** @enum mwEncryptPolicy + + Policy for a channel, dictating what sort of encryption should be + used, if any, and when. +*/ +enum mwEncryptPolicy { + mwEncrypt_NONE = 0x0000, /**< encrypt none */ + mwEncrypt_WHATEVER = 0x0001, /**< encrypt whatever you want */ + mwEncrypt_ALL = 0x0002, /**< encrypt all, any cipher */ + mwEncrypt_RC2_40 = 0x1000, /**< encrypt all, RC2/40 cipher */ + mwEncrypt_RC2_128 = 0x2000, /**< encrypt all, RC2/128 cipher */ +}; + + +/** Allocate and initialize a channel set for a session */ +struct mwChannelSet *mwChannelSet_new(struct mwSession *); + + +/** Clear and deallocate a channel set. Closes, clears, and frees all + contained channels. */ +void mwChannelSet_free(struct mwChannelSet *); + + +/** Create an incoming channel with the given channel id. Channel's state + will be set to WAIT. Primarily for use in mw_session */ +struct mwChannel *mwChannel_newIncoming(struct mwChannelSet *, guint32 id); + + +/** Create an outgoing channel. Its channel ID will be generated by + the owning channel set. Channel's state will be set to INIT */ +struct mwChannel *mwChannel_newOutgoing(struct mwChannelSet *); + + +/** Obtain a reference to a channel by its id. + @returns the channel matching chan, or NULL */ +struct mwChannel *mwChannel_find(struct mwChannelSet *cs, guint32 chan); + + +/** get the ID for a channel. 0x00 indicates an error, as that is not + a permissible value */ +guint32 mwChannel_getId(struct mwChannel *); + + +/** get the session for a channel. */ +struct mwSession *mwChannel_getSession(struct mwChannel *); + + +/** get the ID of the service for a channel. This may be 0x00 for NEW + channels */ +guint32 mwChannel_getServiceId(struct mwChannel *); + + +/** get the service for a channel. This may be NULL for NEW + channels */ +struct mwService *mwChannel_getService(struct mwChannel *); + + +/** associate a channel with an owning service */ +void mwChannel_setService(struct mwChannel *chan, struct mwService *srvc); + + +/** get service-specific data. This is for use by service + implementations to easily associate information with the + channel */ +gpointer mwChannel_getServiceData(struct mwChannel *chan); + + +/** set service-specific data. This is for use by service + implementations to easily associate information with the + channel */ +void mwChannel_setServiceData(struct mwChannel *chan, + gpointer data, GDestroyNotify clean); + + +void mwChannel_removeServiceData(struct mwChannel *chan); + + +guint32 mwChannel_getProtoType(struct mwChannel *chan); + + +void mwChannel_setProtoType(struct mwChannel *chan, guint32 proto_type); + + +guint32 mwChannel_getProtoVer(struct mwChannel *chan); + + +void mwChannel_setProtoVer(struct mwChannel *chan, guint32 proto_ver); + + +/** Channel encryption policy. + + Cannot currently be set, used internally to automatically + negotiate ciphers. Future revisions may allow this to be specified + in a new channel to dictate channel encryption. + + @see enum mwEncryptPolicy +*/ +guint16 mwChannel_getEncryptPolicy(struct mwChannel *chan); + + +guint32 mwChannel_getOptions(struct mwChannel *chan); + + +void mwChannel_setOptions(struct mwChannel *chan, guint32 options); + + +/** User at the other end of the channel. The target user for outgoing + channels, the creator for incoming channels */ +struct mwLoginInfo *mwChannel_getUser(struct mwChannel *chan); + + +/** direct reference to the create addtl information for a channel */ +struct mwOpaque *mwChannel_getAddtlCreate(struct mwChannel *); + + +/** direct reference to the accept addtl information for a channel */ +struct mwOpaque *mwChannel_getAddtlAccept(struct mwChannel *); + + +/** automatically adds instances of all ciphers in the session to the + list of supported ciphers for a channel */ +void mwChannel_populateSupportedCipherInstances(struct mwChannel *chan); + + +/** add a cipher instance to a channel's list of supported + ciphers. Channel must be NEW. */ +void mwChannel_addSupportedCipherInstance(struct mwChannel *chan, + struct mwCipherInstance *ci); + + +/** the list of supported ciphers for a channel. This list will be + empty once a cipher has been selected for the channel */ +GList *mwChannel_getSupportedCipherInstances(struct mwChannel *chan); + + +/** select a cipher instance for a channel. A NULL instance indicates + that no encryption should be used. */ +void mwChannel_selectCipherInstance(struct mwChannel *chan, + struct mwCipherInstance *ci); + + +struct mwCipherInstance * +mwChannel_getCipherInstance(struct mwChannel *chan); + + +/** get the state of a channel */ +enum mwChannelState mwChannel_getState(struct mwChannel *); + + +/** obtain the value for a statistic field as a gpointer */ +gpointer mwChannel_getStatistic(struct mwChannel *chan, + enum mwChannelStatField stat); + + +/** Formally open a channel. + + For outgoing channels: instruct the session to send a channel + create message to the server, and to mark the channel (which must + be in INIT status) as being in WAIT status. + + For incoming channels: configures the channel according to options + in the channel create message. Marks the channel as being in WAIT + status +*/ +int mwChannel_create(struct mwChannel *chan); + + +/** Formally accept an incoming channel. Instructs the session to send + a channel accept message to the server, and to mark the channel as + being OPEN. */ +int mwChannel_accept(struct mwChannel *chan); + + +/** Destroy a channel. Sends a channel-destroy message to the server, + and perform cleanup to remove the channel. + + @param chan the channel to destroy + @param reason the reason code for closing the channel + @param data optional additional information +*/ +int mwChannel_destroy(struct mwChannel *chan, guint32 reason, + struct mwOpaque *data); + + +/** Compose a send-on-channel message, encrypt it as per the channel's + specification, and send it */ +int mwChannel_send(struct mwChannel *chan, guint32 msg_type, + struct mwOpaque *msg); + + +/** Compose a send-on-channel message, and if encrypt is TRUE, encrypt + it as per the channel's specification, and send it */ +int mwChannel_sendEncrypted(struct mwChannel *chan, + guint32 msg_type, struct mwOpaque *msg, + gboolean encrypt); + + +/** pass a create message to a channel for handling */ +void mwChannel_recvCreate(struct mwChannel *chan, + struct mwMsgChannelCreate *msg); + + +/** pass an accept message to a channel for handling */ +void mwChannel_recvAccept(struct mwChannel *chan, + struct mwMsgChannelAccept *msg); + + +/** pass a destroy message to a channel for handling */ +void mwChannel_recvDestroy(struct mwChannel *chan, + struct mwMsgChannelDestroy *msg); + + +/** Feed data into a channel. */ +void mwChannel_recv(struct mwChannel *chan, struct mwMsgChannelSend *msg); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_CHANNEL_H */ + diff --git a/src/mw_cipher.h b/src/mw_cipher.h new file mode 100644 index 0000000..77e12cd --- /dev/null +++ b/src/mw_cipher.h @@ -0,0 +1,297 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_CIPHER_H +#define _MW_CIPHER_H + + +#include +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* place-holders */ +struct mwChannel; +struct mwSession; + + +/** @enum mwCipherType + Common cipher types */ +enum mwCipherType { + mwCipher_RC2_40 = 0x0000, + mwCipher_RC2_128 = 0x0001, +}; + + +struct mwCipher; +struct mwCipherInstance; + + +/** Obtain an instance of a given cipher, which can be used for the + processing of a single channel. */ +typedef struct mwCipherInstance *(*mwCipherInstantiator) + (struct mwCipher *cipher, struct mwChannel *chan); + + +/** Process (encrypt or decrypt, depending) the given data. The passed + buffer may be freed in processing and be replaced with a freshly + allocated buffer. The post-processed buffer must in turn be freed + after use */ +typedef int (*mwCipherProcessor) + (struct mwCipherInstance *ci, struct mwOpaque *data); + + +/** A cipher. Ciphers are primarily used to provide cipher instances + for bi-directional encryption on channels, but some may be used + for other activities. Expand upon this structure to create a + custom encryption provider. + @see mwCipherInstance */ +struct mwCipher { + + /** service this cipher is providing for + @see mwCipher_getSession */ + struct mwSession *session; + + guint16 type; /**< @see mwCipher_getType */ + const char *(*get_name)(void); /**< @see mwCipher_getName */ + const char *(*get_desc)(void); /**< @see mwCipher_getDesc */ + + /** Generate a new Cipher Instance for use on a channel + @see mwCipher_newInstance */ + mwCipherInstantiator new_instance; + + void (*offered)(struct mwCipherInstance *ci, struct mwEncryptItem *item); + struct mwEncryptItem *(*offer)(struct mwCipherInstance *ci); + void (*accepted)(struct mwCipherInstance *ci, struct mwEncryptItem *item); + struct mwEncryptItem *(*accept)(struct mwCipherInstance *ci); + + mwCipherProcessor encrypt; /**< @see mwCipherInstance_encrypt */ + mwCipherProcessor decrypt; /**< @see mwCipherInstance_decrypt */ + + /** prepare this cipher for being free'd + @see mwCipher_free */ + void (*clear)(struct mwCipher *c); + + /** clean up a cipher instance before being free'd + @see mwCipherInstance_free */ + void (*clear_instance)(struct mwCipherInstance *ci); +}; + + +/** An instance of a cipher. Expand upon this structure to contain + necessary state data + @see mwCipher */ +struct mwCipherInstance { + + /** the parent cipher. + @see mwCipherInstance_getCipher */ + struct mwCipher *cipher; + + /** the channel this instances processes + @see mwCipherInstance_getChannel */ + struct mwChannel *channel; +}; + + +struct mwCipher *mwCipher_new_RC2_40(struct mwSession *s); + + +struct mwCipher *mwCipher_new_RC2_128(struct mwSession *s); + + +struct mwSession *mwCipher_getSession(struct mwCipher *cipher); + + +guint16 mwCipher_getType(struct mwCipher *cipher); + + +const char *mwCipher_getName(struct mwCipher *cipher); + + +const char *mwCipher_getDesc(struct mwCipher *cipher); + + +struct mwCipherInstance *mwCipher_newInstance(struct mwCipher *cipher, + struct mwChannel *channel); + + +/** destroy a cipher */ +void mwCipher_free(struct mwCipher* cipher); + + +/** reference the parent cipher of an instance */ +struct mwCipher *mwCipherInstance_getCipher(struct mwCipherInstance *ci); + + +/** reference the channel a cipher instance is attached to */ +struct mwChannel *mwCipherInstance_getChannel(struct mwCipherInstance *ci); + + +/** Indicates a cipher has been offered to our channel */ +void mwCipherInstance_offered(struct mwCipherInstance *ci, + struct mwEncryptItem *item); + + +/** Offer a cipher */ +struct mwEncryptItem * +mwCipherInstance_offer(struct mwCipherInstance *ci); + + +/** Indicates an offered cipher has been accepted */ +void mwCipherInstance_accepted(struct mwCipherInstance *ci, + struct mwEncryptItem *item); + + +/** Accept a cipher offered to our channel */ +struct mwEncryptItem * +mwCipherInstance_accept(struct mwCipherInstance *ci); + + +/** encrypt data */ +int mwCipherInstance_encrypt(struct mwCipherInstance *ci, + struct mwOpaque *data); + + +/** decrypt data */ +int mwCipherInstance_decrypt(struct mwCipherInstance *ci, + struct mwOpaque *data); + + +/** destroy a cipher instance */ +void mwCipherInstance_free(struct mwCipherInstance *ci); + + +/** + @section General Cipher Functions + + These functions are reused where encryption is necessary outside of + a channel (eg. session authentication) +*/ +/* @{ */ + + +/** generate some pseudo-random bytes + @param keylen count of bytes to write into key + @param key buffer to write keys into +*/ +void mwKeyRandom(guchar *key, gsize keylen); + + +/** Setup an Initialization Vector. IV must be at least 8 bytes */ +void mwIV_init(guchar *iv); + + +/** Expand a variable-length key into a 128-byte key (represented as + an an array of 64 ints) */ +void mwKeyExpand(int *ekey, const guchar *key, gsize keylen); + + +/** Encrypt data using an already-expanded key */ +void mwEncryptExpanded(const int *ekey, guchar *iv, + struct mwOpaque *in, + struct mwOpaque *out); + + +/** Encrypt data using an expanded form of the given key */ +void mwEncrypt(const guchar *key, gsize keylen, guchar *iv, + struct mwOpaque *in, struct mwOpaque *out); + + +/** Decrypt data using an already expanded key */ +void mwDecryptExpanded(const int *ekey, guchar *iv, + struct mwOpaque *in, + struct mwOpaque *out); + + +/** Decrypt data using an expanded form of the given key */ +void mwDecrypt(const guchar *key, gsize keylen, guchar *iv, + struct mwOpaque *in, struct mwOpaque *out); + + +/* @} */ + + +/** + @section Diffie-Hellman Functions + + These functions are reused where DH Key negotiation is necessary + outside of a channel (eg. session authentication). These are + wrapping a full multiple-precision integer math library, but most of + the functionality there-of is not exposed. Currently, the math is + provided by a copy of the public domain libmpi. + + for more information on the used MPI Library, visit + http://www.cs.dartmouth.edu/~sting/mpi/ +*/ +/* @{ */ + + +/** @struct mwMpi */ +struct mwMpi; + + +/** prepare a new mpi value */ +struct mwMpi *mwMpi_new(void); + + +/** destroy an mpi value */ +void mwMpi_free(struct mwMpi *i); + + +/** Import a value from an opaque */ +void mwMpi_import(struct mwMpi *i, struct mwOpaque *o); + + +/** Export a value into an opaque */ +void mwMpi_export(struct mwMpi *i, struct mwOpaque *o); + + +/** set a big integer to the Sametime Prime value */ +void mwMpi_setDHPrime(struct mwMpi *i); + + +/** set a big integer to the Sametime Base value */ +void mwMpi_setDHBase(struct mwMpi *i); + + +/** sets private to a randomly generated value, and calculates public + using the Sametime Prime and Base */ +void mwMpi_randDHKeypair(struct mwMpi *private_key, struct mwMpi *public_key); + + +/** sets the shared key value based on the remote and private keys, + using the Sametime Prime and Base */ +void mwMpi_calculateDHShared(struct mwMpi *shared_key, struct mwMpi *remote_key, + struct mwMpi *private_key); + + +/* @} */ + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_CIPHER_H */ diff --git a/src/mw_common.h b/src/mw_common.h new file mode 100644 index 0000000..dcd0565 --- /dev/null +++ b/src/mw_common.h @@ -0,0 +1,441 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_COMMON_H +#define _MW_COMMON_H + + +/** @file mw_common.h + + Common data types and functions for handling those types. + + Functions in this file all fit into similar naming conventions of + TYPE_ACTION as per the activity they perform. The + following actions are available: + + void TYPE_put(struct mwPutBuffer *b, TYPE *val) + - marshalls val onto the buffer b. The buffer will grow as necessary + to fit all the data put into it. For guint16, guint32, and + gboolean, TYPE val is used instead of TYPE + \*val. + + void TYPE_get(struct mwGetBuffer *b, TYPE *val) + - unmarshals val from the buffer b. Failure (due to lack of + insufficient remaining buffer) is indicated in the buffer's error + field. A call to a _get function with a buffer in an error state + has to effect. + + void TYPE_clear(TYPE *val) + - zeros and frees internal members of val, but does not free val + itself. Needs to be called before free-ing any complex types which + have been unmarshalled from a TYPE_get or populated from a + TYPE_clone call to prevent memory leaks. + + void TYPE_clone(TYPE *to, TYPE *from) + - copies/clones members of from into to. May result in memory + allocation for some types. Note that to is not cleared + before-hand, it must already be in a pristine condition. + + gboolean TYPE_equal(TYPE *y, TYPE *z) + - simple equality test. +*/ + + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** @struct mwPutBuffer + buffer to be written to */ +struct mwPutBuffer; + +/** @struct mwGetBuffer + buffer to be read from */ +struct mwGetBuffer; + + +/** A length of binary data, not null-terminated. */ +struct mwOpaque { + gsize len; /**< length of data. */ + guchar *data; /**< data, normally with no NULL termination */ +}; + + +/* 8.3.6 Login Types */ + +/** The type of login. Normally meaning the type of client code being + used to login with. + + If you know of any additional client identifiers, please add them + below or submit an RFE to the meanwhile tracker. +*/ +enum mwLoginType { + mwLogin_LIB = 0x1000, /**< official Lotus binary library */ + mwLogin_JAVA_WEB = 0x1001, /**< official Lotus Java applet */ + mwLogin_BINARY = 0x1002, /**< official Lotus binary application */ + mwLogin_JAVA_APP = 0x1003, /**< official Lotus Java application */ + mwLogin_LINKS = 0x100a, /**< official Sametime Links toolkit */ + + /* now we're getting crazy */ + mwLogin_NOTES_6_5 = 0x1200, + mwLogin_NOTES_6_5_3 = 0x1203, + mwLogin_NOTES_7_0_beta = 0x1210, + mwLogin_NOTES_7_0 = 0x1214, + mwLogin_ICT = 0x1300, + mwLogin_ICT_1_7_8_2 = 0x1302, + mwLogin_ICT_SIP = 0x1303, + mwLogin_NOTESBUDDY_4_14 = 0x1400, /**< 0xff00 mask? */ + mwLogin_NOTESBUDDY_4_15 = 0x1405, + mwLogin_NOTESBUDDY_4_16 = 0x1406, + mwLogin_SANITY = 0x1600, + mwLogin_ST_PERL = 0x1625, + mwLogin_PMR_ALERT = 0x1650, + mwLogin_TRILLIAN = 0x16aa, /**< http://sf.net/st-plugin/ */ + mwLogin_TRILLIAN_IBM = 0x16bb, + mwLogin_MEANWHILE = 0x1700, /**< Meanwhile library */ +}; + + +/* 8.2 Common Structures */ +/* 8.2.1 Login Info block */ + +struct mwLoginInfo { + char *login_id; /**< community-unique ID of the login */ + guint16 type; /**< @see mwLoginType */ + char *user_id; /**< community-unique ID of the user */ + char *user_name; /**< name of user (nick name, full name, etc) */ + char *community; /**< community name (usually domain name) */ + gboolean full; /**< if FALSE, following fields non-existant */ + char *desc; /**< implementation defined description */ + guint32 ip_addr; /**< ip addr of the login */ + char *server_id; /**< unique ID of login's server */ +}; + + +/* 8.2.2 Private Info Block */ + +struct mwUserItem { + gboolean full; /**< if FALSE, don't include name */ + char *id; /**< user id */ + char *community; /**< community */ + char *name; /**< user name */ +}; + + +struct mwPrivacyInfo { + gboolean deny; /**< deny (true) or allow (false) users */ + guint32 count; /**< count of users */ + struct mwUserItem *users; /**< the users list */ +}; + + +/* 8.3.5 User Status Types */ + +enum mwStatusType { + mwStatus_OFFLINE = 0x0000, + + mwStatus_ACTIVE = 0x0020, + mwStatus_IDLE = 0x0040, + mwStatus_AWAY = 0x0060, + mwStatus_BUSY = 0x0080, + + mwStatus_MASK_MOBILE = 0x0200, +}; + + +/* 8.2.3 User Status Block */ + +struct mwUserStatus { + guint16 status; /**< @see mwStatusType */ + guint32 time; /**< last status change time in seconds */ + char *desc; /**< status description */ +}; + + +/* 8.2.4 ID Block */ + +struct mwIdBlock { + char *user; /**< user id (login id or empty for some services) */ + char *community; /**< community id (NULL for same community) */ +}; + + +/* 8.3.8.2 Awareness Presence Types */ + +/* @todo move mwAwareType, mwAwareIdBlock and mwAwareSnapshot into the + aware service and out of common */ + +/** type codes for mwAwareIdBlock */ +enum mwAwareType { + mwAware_USER = 0x0002, /**< a single user */ + mwAware_GROUP = 0x0003, /**< a group */ + mwAware_SERVER = 0x0008, /**< a server */ +}; + + +/* 8.4.2 Awareness Messages */ +/* 8.4.2.1 Awareness ID Block */ + +struct mwAwareIdBlock { + guint16 type; /**< @see mwAwareType */ + char *user; /**< user id */ + char *community; /**< community id (NULL for same community) */ +}; + + +/* 8.4.2.4 Snapshot */ + +struct mwAwareSnapshot { + struct mwAwareIdBlock id; + char *group; /**< group this id belongs to */ + gboolean online; /**< is this user online? */ + char *alt_id; /**< alternate ID, often same as id.user */ + struct mwUserStatus status; /**< status of this user */ + char *name; /**< Formatted version of ID */ +}; + + +/** encryption blocks */ +struct mwEncryptItem { + guint16 id; /**< cipher identifier */ + struct mwOpaque info; /**< cipher information */ +}; + + +/** @name buffer utility functions */ +/*@{*/ + + +/** allocate a new empty buffer */ +struct mwPutBuffer *mwPutBuffer_new(void); + + +/** write raw data to the put buffer */ +void mwPutBuffer_write(struct mwPutBuffer *b, gpointer data, gsize len); + + +/** destroy the buffer */ +void mwPutBuffer_free(struct mwPutBuffer *b); + + +/** move the buffer's data into an opaque, destroy the buffer */ +void mwPutBuffer_finalize(struct mwOpaque *to, struct mwPutBuffer *from); + + +/** allocate a new buffer with a copy of the given data */ +struct mwGetBuffer *mwGetBuffer_new(struct mwOpaque *data); + + +/** read len bytes of raw data from the get buffer into mem. If len is + greater than the count of bytes remaining in the buffer, the + buffer's error flag will NOT be set. + + @returns count of bytes successfully copied to mem */ +gsize mwGetBuffer_read(struct mwGetBuffer *b, gpointer mem, gsize len); + + +/** skip len bytes in the get buffer. If len is greater than the count + of bytes remaining in the buffer, the buffer's error flag will NOT + be set. + + @returns count of bytes successfully skipped */ +gsize mwGetBuffer_advance(struct mwGetBuffer *b, gsize len); + + +/** allocate a new buffer backed by the given data. Calling + mwGetBuffer_free will not result in the underlying data being + freed */ +struct mwGetBuffer *mwGetBuffer_wrap(const struct mwOpaque *data); + + +/** destroy the buffer */ +void mwGetBuffer_free(struct mwGetBuffer *b); + + +/** reset the buffer to the very beginning. Also clears the buffer's + error flag. */ +void mwGetBuffer_reset(struct mwGetBuffer *b); + + +/** count of remaining available bytes */ +gsize mwGetBuffer_remaining(struct mwGetBuffer *b); + + +/** TRUE if an error occurred while reading a basic type from this + buffer */ +gboolean mwGetBuffer_error(struct mwGetBuffer *b); + + +/*@}*/ + + +/** @name Basic Data Types + The basic types are combined to construct the compound types. + */ +/*@{*/ + + +void guint16_put(struct mwPutBuffer *b, guint16 val); + +void guint16_get(struct mwGetBuffer *b, guint16 *val); + +guint16 guint16_peek(struct mwGetBuffer *b); + + +void guint32_put(struct mwPutBuffer *b, guint32 val); + +void guint32_get(struct mwGetBuffer *b, guint32 *val); + +guint32 guint32_peek(struct mwGetBuffer *b); + + +void gboolean_put(struct mwPutBuffer *b, gboolean val); + +void gboolean_get(struct mwGetBuffer *b, gboolean *val); + +gboolean gboolean_peek(struct mwGetBuffer *b); + + +void mwString_put(struct mwPutBuffer *b, const char *str); + +void mwString_get(struct mwGetBuffer *b, char **str); + + +void mwOpaque_put(struct mwPutBuffer *b, const struct mwOpaque *o); + +void mwOpaque_get(struct mwGetBuffer *b, struct mwOpaque *o); + +void mwOpaque_clear(struct mwOpaque *o); + +void mwOpaque_free(struct mwOpaque *o); + +void mwOpaque_clone(struct mwOpaque *to, const struct mwOpaque *from); + + +/*@}*/ + + +/** @name Compound Data Types */ +/*@{*/ + + +void mwLoginInfo_put(struct mwPutBuffer *b, const struct mwLoginInfo *info); + +void mwLoginInfo_get(struct mwGetBuffer *b, struct mwLoginInfo *info); + +void mwLoginInfo_clear(struct mwLoginInfo *info); + +void mwLoginInfo_clone(struct mwLoginInfo *to, const struct mwLoginInfo *from); + + +void mwUserItem_put(struct mwPutBuffer *b, const struct mwUserItem *user); + +void mwUserItem_get(struct mwGetBuffer *b, struct mwUserItem *user); + +void mwUserItem_clear(struct mwUserItem *user); + +void mwUserItem_clone(struct mwUserItem *to, const struct mwUserItem *from); + + +void mwPrivacyInfo_put(struct mwPutBuffer *b, + const struct mwPrivacyInfo *info); + +void mwPrivacyInfo_get(struct mwGetBuffer *b, struct mwPrivacyInfo *info); + +void mwPrivacyInfo_clear(struct mwPrivacyInfo *info); + +void mwPrivacyInfo_clone(struct mwPrivacyInfo *to, + const struct mwPrivacyInfo *from); + + +void mwUserStatus_put(struct mwPutBuffer *b, + const struct mwUserStatus *stat); + +void mwUserStatus_get(struct mwGetBuffer *b, struct mwUserStatus *stat); + +void mwUserStatus_clear(struct mwUserStatus *stat); + +void mwUserStatus_clone(struct mwUserStatus *to, + const struct mwUserStatus *from); + + +void mwIdBlock_put(struct mwPutBuffer *b, const struct mwIdBlock *id); + +void mwIdBlock_get(struct mwGetBuffer *b, struct mwIdBlock *id); + +void mwIdBlock_clear(struct mwIdBlock *id); + +void mwIdBlock_clone(struct mwIdBlock *to, + const struct mwIdBlock *from); + +guint mwIdBlock_hash(const struct mwIdBlock *idb); + +gboolean mwIdBlock_equal(const struct mwIdBlock *a, + const struct mwIdBlock *b); + + +void mwAwareIdBlock_put(struct mwPutBuffer *b, + const struct mwAwareIdBlock *idb); + +void mwAwareIdBlock_get(struct mwGetBuffer *b, struct mwAwareIdBlock *idb); + +void mwAwareIdBlock_clear(struct mwAwareIdBlock *idb); + +void mwAwareIdBlock_clone(struct mwAwareIdBlock *to, + const struct mwAwareIdBlock *from); + +guint mwAwareIdBlock_hash(const struct mwAwareIdBlock *a); + +gboolean mwAwareIdBlock_equal(const struct mwAwareIdBlock *a, + const struct mwAwareIdBlock *b); + + +void mwAwareSnapshot_get(struct mwGetBuffer *b, + struct mwAwareSnapshot *idb); + +void mwAwareSnapshot_clear(struct mwAwareSnapshot *idb); + +void mwAwareSnapshot_clone(struct mwAwareSnapshot *to, + const struct mwAwareSnapshot *from); + + +void mwEncryptItem_put(struct mwPutBuffer *b, + const struct mwEncryptItem *item); + +void mwEncryptItem_get(struct mwGetBuffer *b, struct mwEncryptItem *item); + +void mwEncryptItem_clear(struct mwEncryptItem *item); + +void mwEncryptItem_free(struct mwEncryptItem *item); + + +/*@}*/ + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_COMMON_H */ diff --git a/src/mw_debug.c b/src/mw_debug.c new file mode 100644 index 0000000..cf47a38 --- /dev/null +++ b/src/mw_debug.c @@ -0,0 +1,184 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include + +#include "mw_debug.h" + + + +#define FRMT1 "%02x" +#define FRMT2 FRMT1 FRMT1 " " +#define FRMT4 FRMT2 FRMT2 +#define FRMT8 FRMT4 FRMT4 +#define FRMT16 FRMT8 FRMT8 + +#define ADVANCE(b, n, c) {b += c; n -= c;} + + + +/** writes hex pairs of buf to str */ +static void pretty_print(GString *str, const guchar *buf, gsize len) { + while(len >= 16) { + /* write a complete line */ + g_string_append_printf(str, FRMT16, + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], buf[15]); + ADVANCE(buf, len, 16); + + /* append \n to each line but the last */ + if(len) g_string_append(str, "\n"); + } + + /* write an incomplete line */ + if(len >= 8) { + g_string_append_printf(str, FRMT8, + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7]); + ADVANCE(buf, len, 8); + } + + if(len >= 4) { + g_string_append_printf(str, FRMT4, + buf[0], buf[1], buf[2], buf[3]); + ADVANCE(buf, len, 4); + } + + if(len >= 2) { + g_string_append_printf(str, FRMT2, buf[0], buf[1]); + ADVANCE(buf, len, 2); + } + + if(len >= 1) { + g_string_append_printf(str, FRMT1, buf[0]); + ADVANCE(buf, len, 1); + } +} + + + +void mw_debug_datav(const guchar *buf, gsize len, + const char *msg, va_list args) { + GString *str; + + g_return_if_fail(buf != NULL || len == 0); + + str = g_string_new(NULL); + + if(msg) { + char *txt = g_strdup_vprintf(msg, args); + g_string_append_printf(str, "%s\n", txt); + g_free(txt); + } + pretty_print(str, buf, len); + + g_debug(str->str); + g_string_free(str, TRUE); +} + + + +void mw_debug_data(const guchar *buf, gsize len, + const char *msg, ...) { + va_list args; + + g_return_if_fail(buf != NULL || len == 0); + + va_start(args, msg); + mw_debug_datav(buf, len, msg, args); + va_end(args); +} + + + +void mw_debug_opaquev(struct mwOpaque *o, const char *txt, va_list args) { + g_return_if_fail(o != NULL); + mw_debug_datav(o->data, o->len, txt, args); +} + + + +void mw_debug_opaque(struct mwOpaque *o, const char *txt, ...) { + va_list args; + + g_return_if_fail(o != NULL); + + va_start(args, txt); + mw_debug_opaquev(o, txt, args); + va_end(args); +} + + +void mw_mailme_datav(const guchar *buf, gsize len, + const char *info, va_list args) { + +#if MW_MAILME + GString *str; + char *txt; + + str = g_string_new(MW_MAILME_MESSAGE "\n" + " Please send mail to: " MW_MAILME_ADDRESS "\n" + MW_MAILME_CUT_START "\n"); + str = g_string_new(NULL); + + txt = g_strdup_vprintf(info, args); + g_string_append_printf(str, "%s\n", txt); + g_free(txt); + + if(buf && len) pretty_print(str, buf, len); + + g_string_append(str, MW_MAILME_CUT_STOP); + + g_debug(str->str); + g_string_free(str, TRUE); + +#else + mw_debug_datav(buf, len, info, args); + +#endif +} + + + +void mw_mailme_data(const guchar *buf, gsize len, + const char *info, ...) { + va_list args; + va_start(args, info); + mw_mailme_datav(buf, len, info, args); + va_end(args); +} + + + +void mw_mailme_opaquev(struct mwOpaque *o, const char *info, va_list args) { + mw_mailme_datav(o->data, o->len, info, args); +} + + + +void mw_mailme_opaque(struct mwOpaque *o, const char *info, ...) { + va_list args; + va_start(args, info); + mw_mailme_opaquev(o, info, args); + va_end(args); +} diff --git a/src/mw_debug.h b/src/mw_debug.h new file mode 100644 index 0000000..93d6452 --- /dev/null +++ b/src/mw_debug.h @@ -0,0 +1,128 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_DEBUG_H +#define _MW_DEBUG_H + + +#include +#include + +#include "mw_common.h" + + +/** replaces NULL strings with "(null)". useful for printf where + you're unsure that the %s will be non-NULL. Note that while the + linux printf will do this automatically, not all will. The others + will instead segfault */ +#define NSTR(str) ((str)? (str): "(null)") + + +#ifndef g_debug +#define g_debug(format...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format) +#endif + + +#ifndef g_info +#define g_info(format...) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, format) +#endif + + +#ifndef MW_MAILME_ADDRESS +/** email address used in mw_debug_mailme. */ +#define MW_MAILME_ADDRESS "meanwhile-devel@lists.sourceforge.net" +#endif + + +#ifndef MW_MAILME_CUT_START +#define MW_MAILME_CUT_START "-------- begin copy --------" +#endif + + +#ifndef MW_MAILME_CUT_STOP +#define MW_MAILME_CUT_STOP "--------- end copy ---------" +#endif + + +#ifndef MW_MAILME_MESSAGE +/** message used in mw_debug_mailme instructing user on what to do + with the debugging output produced from that function */ +#define MW_MAILME_MESSAGE "\n" \ + " Greetings! It seems that you've run across protocol data that the\n" \ + "Meanwhile library does not yet know about. As such, there may be\n" \ + "some unexpected behaviour in this session. If you'd like to help\n" \ + "resolve this issue, please copy and paste the following block into\n" \ + "an email to the address listed below with a brief explanation of\n" \ + "what you were doing at the time of this message. Thanks a lot!" +#endif + + +void mw_debug_datav(const guchar *buf, gsize len, + const char *info, va_list args); + + +void mw_debug_data(const guchar *buf, gsize len, + const char *info, ...); + + +void mw_debug_opaquev(struct mwOpaque *o, const char *info, va_list args); + + +void mw_debug_opaque(struct mwOpaque *o, const char *info, ...); + + +void mw_mailme_datav(const guchar *buf, gsize len, + const char *info, va_list args); + +void mw_mailme_data(const guchar *buf, gsize len, + const char *info, ...); + + +/** Outputs a hex dump of a mwOpaque with debugging info and a + pre-defined message. Identical to mw_mailme_opaque, but taking a + va_list argument */ +void mw_mailme_opaquev(struct mwOpaque *o, const char *info, va_list args); + + + +/** Outputs a hex dump of a mwOpaque with debugging info and a + pre-defined message. + + if MW_MAILME is undefined or false, this function acts the same as + mw_mailme_opaque. + + @arg block data to be printed in a hex block + @arg info a printf-style format string + + The resulting message is in the following format: + @code + MW_MAILME_MESSAGE + " Please send mail to: " MW_MAILME_ADDRESS + MW_MAILME_CUT_START + info + block + MW_MAILME_CUT_STOP + @endcode + */ +void mw_mailme_opaque(struct mwOpaque *o, const char *info, ...); + + +#endif + diff --git a/src/mw_error.h b/src/mw_error.h new file mode 100644 index 0000000..6345358 --- /dev/null +++ b/src/mw_error.h @@ -0,0 +1,174 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_ERROR_H +#define _MW_ERROR_H + + +/** @file mw_error.h + + Common error code constants used by Meanwhile. + + Not all of these error codes (or even many, really) will ever + actually appear from Meanwhile. These are taken directly from the + houri draft, along with the minimal explanation for each. +*/ + + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** reference to a new string appropriate for the given error code.*/ +char* mwError(guint32 code); + + +/* 8.3 Constants */ +/* 8.3.1 Error Codes */ +/* 8.3.1.1 General error/success codes */ + +/** @enum ERR_GENERAL + general error codes */ +enum ERR_GENERAL { + ERR_SUCCESS = 0x00000000, + ERR_FAILURE = 0x80000000, + ERR_REQUEST_DELAY = 0x00000001, + ERR_REQUEST_INVALID = 0x80000001, + ERR_NOT_LOGGED_IN = 0x80000002, + ERR_NOT_AUTHORIZED = 0x80000003, + ERR_ABORT = 0x80000004, + ERR_NO_ELEMENT = 0x80000005, + ERR_NO_USER = 0x80000006, + ERR_BAD_DATA = 0x80000007, + ERR_NOT_IMPLEMENTED = 0x80000008, + ERR_UNKNOWN_ERROR = 0x80000009, /* what is this? */ + ERR_STARVING = 0x8000000a, + ERR_CHANNEL_NO_SUPPORT = 0x8000000b, + ERR_CHANNEL_EXISTS = 0x8000000c, + ERR_SERVICE_NO_SUPPORT = 0x8000000d, + ERR_PROTOCOL_NO_SUPPORT = 0x8000000e, + ERR_PROTOCOL_NO_SUPPORT2 = 0x8000000f, /* duplicate? */ + ERR_VERSION_NO_SUPPORT = 0x80000010, + ERR_USER_SKETCHY = 0x80000011, + ERR_ALREADY_INITIALIZED = 0x80000013, + ERR_NOT_OWNER = 0x80000014, + ERR_TOKEN_INVALID = 0x80000015, + ERR_TOKEN_EXPIRED = 0x80000016, + ERR_TOKEN_IP_MISMATCH = 0x80000017, + ERR_PORT_IN_USE = 0x80000018, + ERR_NETWORK_DEAD = 0x80000019, + ERR_NO_MASTER_CHANNEL = 0x8000001a, + ERR_ALREADY_SUBSCRIBED = 0x8000001b, + ERR_NOT_SUBSCRIBED = 0x8000001c, + ERR_ENCRYPT_NO_SUPPORT = 0x8000001d, + ERR_ENCRYPT_UNINITIALIZED = 0x8000001e, + ERR_ENCRYPT_UNACCEPTABLE = 0x8000001f, + ERR_ENCRYPT_INVALID = 0x80000020, + ERR_NO_COMMON_ENCRYPT = 0x80000021, + ERR_CHANNEL_DESTROYED = 0x80000022, + ERR_CHANNEL_REDIRECTED = 0x80000023 +}; + + +/* 8.3.1.2 Connection/disconnection errors */ + +#define VERSION_MISMATCH 0x80000200 +#define INSUF_BUFFER 0x80000201 +#define NOT_IN_USE 0x80000202 +#define INSUF_SOCKET 0x80000203 +#define HARDWARE_ERROR 0x80000204 +#define NETWORK_DOWN 0x80000205 +#define HOST_DOWN 0x80000206 +#define HOST_UNREACHABLE 0x80000207 +#define TCPIP_ERROR 0x80000208 +#define FAT_MESSAGE 0x80000209 +#define PROXY_ERROR 0x8000020A +#define SERVER_FULL 0x8000020B +#define SERVER_NORESPOND 0x8000020C +#define CANT_CONNECT 0x8000020D +#define USER_REMOVED 0x8000020E +#define PROTOCOL_ERROR 0x8000020F +#define USER_RESTRICTED 0x80000210 +#define INCORRECT_LOGIN 0x80000211 +#define ENCRYPT_MISMATCH 0x80000212 +#define USER_UNREGISTERED 0x80000213 +#define VERIFICATION_DOWN 0x80000214 +#define USER_TOO_IDLE 0x80000216 +#define GUEST_IN_USE 0x80000217 +#define USER_EXISTS 0x80000218 +#define USER_RE_LOGIN 0x80000219 +#define BAD_NAME 0x8000021A +#define REG_MODE_NS 0x8000021B +#define WRONG_USER_PRIV 0x8000021C +#define NEED_EMAIL 0x8000021D +#define DNS_ERROR 0x8000021E +#define DNS_FATAL_ERROR 0x8000021F +#define DNS_NOT_FOUND 0x80000220 +#define CONNECTION_BROKEN 0x80000221 +#define CONNECTION_ABORTED 0x80000222 +#define CONNECTION_REFUSED 0x80000223 +#define CONNECTION_RESET 0x80000224 +#define CONNECTION_TIMED 0x80000225 +#define CONNECTION_CLOSED 0x80000226 +#define MULTI_SERVER_LOGIN 0x80000227 +#define MULTI_SERVER_LOGIN2 0x80000228 +#define MULTI_LOGIN_COMP 0x80000229 +#define MUTLI_LOGIN_ALREADY 0x8000022A +#define SERVER_BROKEN 0x8000022B +#define SERVER_PATH_OLD 0x8000022C +#define APPLET_LOGOUT 0x8000022D + + +/* 8.3.1.3 Client error codes */ + +/** @enum ERR_CLIENT + Client error codes */ +enum ERR_CLIENT { + ERR_CLIENT_USER_GONE = 0x80002000, /* user isn't here */ + ERR_CLIENT_USER_DND = 0x80002001, /* user is DND */ + ERR_CLIENT_USER_ELSEWHERE = 0x80002002, /* already logged in elsewhere */ +}; + + +/* 8.3.1.4 IM error codes */ + +/** @enum ERR_IM + IM error codes */ +enum ERR_IM { + ERR_IM_COULDNT_REGISTER = 0x80002003, + ERR_IM_ALREADY_REGISTERED = 0x80002004, + + /** apparently, this is used to mean that the requested feature (per + the channel create addtl data) is not supported by the client on + the other end of the IM channel */ + ERR_IM_NOT_REGISTERED = 0x80002005, +}; + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_ERROR_H */ diff --git a/src/mw_message.h b/src/mw_message.h new file mode 100644 index 0000000..8402b8b --- /dev/null +++ b/src/mw_message.h @@ -0,0 +1,305 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_MESSAGE_H +#define _MW_MESSAGE_H + + +#include +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** Cast a pointer to a message subtype (eg, mwMsgHandshake, + mwMsgAdmin) into a pointer to a mwMessage */ +#define MW_MESSAGE(msg) (&msg->head) + + +/** Indicates the type of a message. */ +enum mwMessageType { + mwMessage_HANDSHAKE = 0x0000, /**< mwMsgHandshake */ + mwMessage_HANDSHAKE_ACK = 0x8000, /**< mwMsgHandshakeAck */ + mwMessage_LOGIN = 0x0001, /**< mwMsgLogin */ + mwMessage_LOGIN_ACK = 0x8001, /**< mwMsgLoginAck */ + mwMessage_LOGIN_REDIRECT = 0x0018, /**< mwMsgLoginRedirect */ + mwMessage_LOGIN_CONTINUE = 0x0016, /**< mwMsgLoginContinue */ + + mwMessage_CHANNEL_CREATE = 0x0002, /**< mwMsgChannelCreate */ + mwMessage_CHANNEL_DESTROY = 0x0003, /**< mwMsgChannelDestroy */ + mwMessage_CHANNEL_SEND = 0x0004, /**< mwMsgChannelSend */ + mwMessage_CHANNEL_ACCEPT = 0x0006, /**< mwMsgChannelAccept */ + + mwMessage_SET_USER_STATUS = 0x0009, /**< mwMsgSetUserStatus */ + mwMessage_SET_PRIVACY_LIST = 0x000b, /**< mwMsgSetPrivacyList */ + mwMessage_SENSE_SERVICE = 0x0011, /**< mwMsgSenseService */ + mwMessage_ADMIN = 0x0019, /**< mwMsgAdmin */ + mwMessage_ANNOUNCE = 0x0022, /**< mwMsgAnnounce */ +}; + + +enum mwMessageOption { + mwMessageOption_ENCRYPT = 0x4000, /**< message data is encrypted */ + mwMessageOption_HAS_ATTRIBS = 0x8000, /**< message has attributes */ +}; + + +/** @see mwMessageOption */ +#define MW_MESSAGE_HAS_OPTION(msg, opt) \ + ((msg)->options & (opt)) + + +struct mwMessage { + guint16 type; /**< @see mwMessageType */ + guint16 options; /**< @see mwMessageOption */ + guint32 channel; /**< ID of channel message is intended for */ + struct mwOpaque attribs; /**< optional message attributes */ +}; + + + +/** Allocate and initialize a new message of the specified type */ +struct mwMessage *mwMessage_new(enum mwMessageType type); + + +/** build a message from its representation */ +struct mwMessage *mwMessage_get(struct mwGetBuffer *b); + + +void mwMessage_put(struct mwPutBuffer *b, struct mwMessage *msg); + + +void mwMessage_free(struct mwMessage *msg); + + +/* 8.4 Messages */ +/* 8.4.1 Basic Community Messages */ +/* 8.4.1.1 Handshake */ + +struct mwMsgHandshake { + struct mwMessage head; + guint16 major; /**< client's major version number */ + guint16 minor; /**< client's minor version number */ + guint32 srvrcalc_addr; /**< 0.0.0.0 */ + guint16 login_type; /**< @see mwLoginType */ + guint32 loclcalc_addr; /**< local public IP */ + guint16 unknown_a; /**< normally 0x0100 */ + guint32 unknown_b; /**< normally 0x00000000 */ + char *local_host; /**< name of client host */ +}; + + +/* 8.4.1.2 HandshakeAck */ + +struct mwMsgHandshakeAck { + struct mwMessage head; + guint16 major; /**< server's major version number */ + guint16 minor; /**< server's minor version number */ + guint32 srvrcalc_addr; /**< server-calculated address */ + guint32 magic; /**< four bytes of something */ + struct mwOpaque data; /**< server's DH public key for auth */ +}; + + +/* 8.3.7 Authentication Types */ + +enum mwAuthType { + mwAuthType_PLAIN = 0x0000, + mwAuthType_TOKEN = 0x0001, + mwAuthType_ENCRYPT = 0x0002, /**< @todo remove for 1.0 */ + mwAuthType_RC2_40 = 0x0002, + mwAuthType_RC2_128 = 0x0004, +}; + + +/* 8.4.1.3 Login */ + +struct mwMsgLogin { + struct mwMessage head; + guint16 login_type; /**< @see mwLoginType */ + char *name; /**< user identification */ + guint16 auth_type; /**< @see mwAuthType */ + struct mwOpaque auth_data; /**< authentication data */ +}; + + +/* 8.4.1.4 LoginAck */ + +struct mwMsgLoginAck { + struct mwMessage head; + struct mwLoginInfo login; + struct mwPrivacyInfo privacy; + struct mwUserStatus status; +}; + + +/* 8.4.1.5 LoginCont */ + +struct mwMsgLoginContinue { + struct mwMessage head; +}; + + +/* 8.4.1.6 AuthPassed */ + +struct mwMsgLoginRedirect { + struct mwMessage head; + char *host; + char *server_id; +}; + + +/* 8.4.1.7 CreateCnl */ + +/** an offer of encryption items */ +struct mwEncryptOffer { + guint16 mode; /**< encryption mode */ + GList *items; /**< list of mwEncryptItem offered */ + guint16 extra; /**< encryption mode again? */ + gboolean flag; /**< unknown flag */ +}; + + +struct mwMsgChannelCreate { + struct mwMessage head; + guint32 reserved; /**< unknown reserved data */ + guint32 channel; /**< intended ID for new channel */ + struct mwIdBlock target; /**< User ID. for service use */ + guint32 service; /**< ID for the target service */ + guint32 proto_type; /**< protocol type for the service */ + guint32 proto_ver; /**< protocol version for the service */ + guint32 options; /**< options */ + struct mwOpaque addtl; /**< service-specific additional data */ + gboolean creator_flag; /**< indicate presence of creator information */ + struct mwLoginInfo creator; + struct mwEncryptOffer encrypt; +}; + + +/* 8.4.1.8 AcceptCnl */ + +/** a selected encryption item from those offered */ +struct mwEncryptAccept { + guint16 mode; /**< encryption mode */ + struct mwEncryptItem *item; /**< chosen mwEncryptItem (optional) */ + guint16 extra; /**< encryption mode again? */ + gboolean flag; /**< unknown flag */ +}; + + +struct mwMsgChannelAccept { + struct mwMessage head; + guint32 service; /**< ID for the channel's service */ + guint32 proto_type; /**< protocol type for the service */ + guint32 proto_ver; /**< protocol version for the service */ + struct mwOpaque addtl; /**< service-specific additional data */ + gboolean acceptor_flag; /**< indicate presence of acceptor information */ + struct mwLoginInfo acceptor; + struct mwEncryptAccept encrypt; +}; + + +/* 8.4.1.9 SendOnCnl */ + +struct mwMsgChannelSend { + struct mwMessage head; + + /** message type. each service defines its own send types. Type IDs + are only necessarily unique within a given service. */ + guint16 type; + + /** protocol data to be interpreted by the handling service */ + struct mwOpaque data; +}; + + +/* 8.4.1.10 DestroyCnl */ + +struct mwMsgChannelDestroy { + struct mwMessage head; + guint32 reason; /**< reason for closing the channel. */ + struct mwOpaque data; /**< additional information */ +}; + + +/* 8.4.1.11 SetUserStatus */ + +struct mwMsgSetUserStatus { + struct mwMessage head; + struct mwUserStatus status; +}; + + +/* 8.4.1.12 SetPrivacyList */ + +struct mwMsgSetPrivacyList { + struct mwMessage head; + struct mwPrivacyInfo privacy; +}; + + +/* Sense Service */ + +/** Sent to the server to request the presense of a service by its + ID. Sent to the client to indicate the presense of such a + service */ +struct mwMsgSenseService { + struct mwMessage head; + guint32 service; +}; + + +/* Admin */ + +/** An administrative broadcast message */ +struct mwMsgAdmin { + struct mwMessage head; + char *text; +}; + + +/* Announce */ + +/** An announcement between users */ +struct mwMsgAnnounce { + struct mwMessage head; + gboolean sender_present; /**< indicates presence of sender data */ + struct mwLoginInfo sender; /**< who sent the announcement */ + guint16 unknown_a; /**< unknown A. Usually 0x00 */ + gboolean may_reply; /**< replies allowed */ + char *text; /**< text of message */ + + /** list of (char *) indicating recipients. Recipient users are in + the format "@U username" and recipient NAB groups are in the + format "@G groupname" */ + GList *recipients; +}; + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_MESSAGE_H */ + diff --git a/src/mw_service.h b/src/mw_service.h new file mode 100644 index 0000000..9bcd650 --- /dev/null +++ b/src/mw_service.h @@ -0,0 +1,370 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_SERVICE_H +#define _MW_SERVICE_H + + +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* place-holders */ +struct mwChannel; +struct mwService; +struct mwSession; +struct mwMsgChannelCreate; +struct mwMsgChannelAccept; +struct mwMsgChannelDestroy; + + +/** State-tracking for a service */ +enum mwServiceState { + mwServiceState_STOPPED, /**< the service is not active */ + mwServiceState_STOPPING, /**< the service is shutting down */ + mwServiceState_STARTED, /**< the service is active */ + mwServiceState_STARTING, /**< the service is starting up */ + mwServiceState_ERROR, /**< error in service, shutting down */ + mwServiceState_UNKNOWN, /**< error determining state */ +}; + + +/** Casts a concrete service (such as mwServiceAware) into a mwService */ +#define MW_SERVICE(srv) ((struct mwService *) srv) + + +#define MW_SERVICE_IS_STATE(srvc, state) \ + (mwService_getState(MW_SERVICE(srvc)) == (state)) + +#define MW_SERVICE_IS_STOPPED(srvc) \ + MW_SERVICE_IS_STATE(srvc, mwServiceState_STOPPED) + +#define MW_SERVICE_IS_STOPPING(srvc) \ + MW_SERVICE_IS_STATE(srvc, mwServiceState_STOPPING) + +#define MW_SERVICE_IS_STARTED(srvc) \ + MW_SERVICE_IS_STATE(srvc, mwServiceState_STARTED) + +#define MW_SERVICE_IS_STARTING(srvc) \ + MW_SERVICE_IS_STATE(srvc, mwServiceState_STARTING) + + +/** If a service is STARTING or STARTED, it's considered LIVE */ +#define MW_SERVICE_IS_LIVE(srvc) \ + (MW_SERVICE_IS_STARTING(srvc) || MW_SERVICE_IS_STARTED(srvc)) + +/** If a service is STOPPING or STOPPED, it's considered DEAD */ +#define MW_SERVICE_IS_DEAD(srvc) \ + (MW_SERVICE_IS_STOPPING(srvc) || MW_SERVICE_IS_STOPPED(srvc)) + + +typedef void (*mwService_funcStart)(struct mwService *service); + +typedef void (*mwService_funcStop)(struct mwService *service); + +typedef void (*mwService_funcClear)(struct mwService *service); + +typedef const char *(*mwService_funcGetName)(struct mwService *service); + +typedef const char *(*mwService_funcGetDesc)(struct mwService *service); + +/** @todo remove msg and replace with appropriate additional parameters */ +typedef void (*mwService_funcRecvCreate) + (struct mwService *service, + struct mwChannel *channel, + struct mwMsgChannelCreate *msg); + +/** @todo remove msg and replace with appropriate additional parameters */ +typedef void (*mwService_funcRecvAccept) + (struct mwService *service, + struct mwChannel *channel, + struct mwMsgChannelAccept *msg); + +/** @todo remove msg and replace with appropriate additional parameters */ +typedef void (*mwService_funcRecvDestroy) + (struct mwService *service, + struct mwChannel *channel, + struct mwMsgChannelDestroy *msg); + +typedef void (*mwService_funcRecv) + (struct mwService *service, + struct mwChannel *channel, + guint16 msg_type, + struct mwOpaque *data); + + +/** A service is the recipient of sendOnCnl messages sent over + channels marked with the corresponding service id. Services + provide functionality such as IM relaying, Awareness tracking and + notification, and Conference handling. It is a service's + responsibility to accept or destroy channels, and to process data + sent over those channels */ +struct mwService { + + /** the unique identifier by which this service is registered. The + type value also relates to those channels which will be directed + to this service */ + guint32 type; + + /** the state of this service. Determines whether or not the session + should call the start function upon receipt of a service + available message. Should not be set or checked by hand. + + @relates mwService_getState */ + enum mwServiceState state; + + /** session this service is attached to. + @relates mwService_getSession */ + struct mwSession *session; + + /** @return string short name of the service + @relates mwService_getName */ + mwService_funcGetName get_name; + + /** @return string short description of the service + @relates mwService_getDesc */ + mwService_funcGetDesc get_desc; + + /** The service's channel create handler. Called when the session + receives a channel create message with a service matching this + service's type. + + @relates mwService_recvCreate */ + mwService_funcRecvCreate recv_create; + + /** The service's channel accept handler. Called when the session + receives a channel accept message for a channel with a service + matching this service's type. + + @relates mwService_recvAccept */ + mwService_funcRecvAccept recv_accept; + + /** The service's channel destroy handler. Called when the session + receives a channel destroy message for a channel with a service + matching this service's type. + + @relates mwService_recvDestroy */ + mwService_funcRecvDestroy recv_destroy; + + /** The service's input handler. Called when the session receives + data on a channel belonging to this service + + @relates mwService_recv */ + mwService_funcRecv recv; + + /** The service's start handler. Called upon the receipt of a + service available message. + + @relates mwService_start */ + mwService_funcStart start; + + /** The service's stop handler. Called when the session is shutting + down, or when the service is free'd. + + @relates mwService_stop */ + mwService_funcStop stop; + + /** The service's cleanup handler. Service implementations should + presume that mwService::stop will be called first. The clear + handler is not for shutting down channels or generating + non-cleanup side-effects, it is only for handling tear-down of + the service, and will only be called once for any instance. + + @relates mwService_free */ + mwService_funcClear clear; + + /** Optional client data, not for use by service implementations + + @relates mwService_getClientData + @relates mwService_setClientData */ + gpointer client_data; + + /** Optional client data cleanup function. Called with client_data + from mwService_free + + @relates mwService_getClientData + @relates mwService_setClientData */ + GDestroyNotify client_cleanup; +}; + + +/** @name Service Extension API + + These functions are for use by service implementations */ +/*@{*/ + + +/** Prepares a newly allocated service for use. + + Intended for use by service implementations, rather than by code + utilizing a service. + + The service state will be initialized to STOPPED. + + @param service the service to initialize + @param session the service's owning session + @param service_type the service ID number */ +void mwService_init(struct mwService *service, + struct mwSession *session, + guint32 service_type); + + +/** Indicate that a service is started. To be used by service + implementations when the service is fully started. */ +void mwService_started(struct mwService *service); + + +/** Indicate that a service is stopped. To be used by service + implementations when the service is fully stopped. */ +void mwService_stopped(struct mwService *service); + + +/*@}*/ + + +/** @name General Services API + + These functions provide unified access to the general functions of + a client service, with some simple sanity-checking. */ +/*@{*/ + + +/** Triggers the recv_create handler on the service. + + @param service the service to handle the message + @param channel the channel being created + @param msg the channel create message */ +void mwService_recvCreate(struct mwService *service, + struct mwChannel *channel, + struct mwMsgChannelCreate *msg); + + +/** Triggers the recv_accept handler on the service. + + @param service the service to handle the message + @param channel the channel being accepted + @param msg the channel accept message */ +void mwService_recvAccept(struct mwService *service, + struct mwChannel *channel, + struct mwMsgChannelAccept *msg); + + +/** Triggers the recv_destroy handler on the service. + + @param service the service to handle the message + @param channel the channel being destroyed + @param msg the channel destroy message */ +void mwService_recvDestroy(struct mwService *service, + struct mwChannel *channel, + struct mwMsgChannelDestroy *msg); + + +/** Triggers the input handler on the service + + @param service the service to receive the input + @param channel the channel the input was received from + @param msg_type the service-dependant message type + @param data the message data */ +void mwService_recv(struct mwService *service, + struct mwChannel *channel, + guint16 msg_type, + struct mwOpaque *data); + + +/** @return the appropriate type id for the service */ +guint32 mwService_getType(struct mwService *); + + +/** @return string short name of the service */ +const char *mwService_getName(struct mwService *); + + +/** @return string short description of the service */ +const char *mwService_getDesc(struct mwService *); + + +/** @return the service's session */ +struct mwSession *mwService_getSession(struct mwService *service); + + +/** @returns the service's state +*/ +enum mwServiceState mwService_getState(struct mwService *service); + + +/** Triggers the start handler for the service. Normally called from + the session upon receipt of a service available message. Service + implementations should use this handler to open any necessary + channels, etc. Checks that the service is STOPPED, or returns. + + @param service The service to start +*/ +void mwService_start(struct mwService *service); + + +/** Triggers the stop handler for the service. Normally called from + the session before closing down the connection. Checks that the + service is STARTED or STARTING, or returns + + @param service The service to stop +*/ +void mwService_stop(struct mwService *service); + + +/** Frees memory used by a service. Will trigger the stop handler if + the service is STARTED or STARTING. Triggers clear handler to allow + cleanup. + + @param service The service to clear and free +*/ +void mwService_free(struct mwService *service); + + +/** Associates client data with service. If there is existing data, it + will not have its cleanup function called. Client data is not for + use by service implementations. Rather, it is for use by client + code which may later make use of those service implementations. */ +void mwService_setClientData(struct mwService *service, + gpointer data, GDestroyNotify cleanup); + + +/** Reference associated client data */ +gpointer mwService_getClientData(struct mwService *service); + + +/** Removes client data from service. If there is a cleanup function, + it will be called. */ +void mwService_removeClientData(struct mwService *service); + + +/*@}*/ + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SERVICE_H */ + diff --git a/src/mw_session.h b/src/mw_session.h new file mode 100644 index 0000000..e606e0c --- /dev/null +++ b/src/mw_session.h @@ -0,0 +1,397 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_SESSION_H +#define _MW_SESSION_H + + +/** @file mw_session.h + + A client session with a Sametime server is encapsulated in the + mwSession structure. The session controls channels, provides + encryption ciphers, and manages services using messages over the + Master channel. + + A session does not directly communicate with a socket or stream, + instead the session is initialized from client code with an + instance of a mwSessionHandler structure. This session handler + provides functions as call-backs for common session events, and + provides functions for writing-to and closing the connection to + the server. + + A session does not perform reads on a socket directly. Instead, it + must be fed from an outside source via the mwSession_recv + function. The session will buffer and merge data passed to this + function to build complete protocol messages, and will act upon + each complete message accordingly. +*/ + + +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +struct mwChannelSet; +struct mwCipher; +struct mwMessage; +struct mwService; + + +/** default protocol major version */ +#define MW_PROTOCOL_VERSION_MAJOR 0x001e + + +/** default protocol minor version */ +#define MW_PROTOCOL_VERSION_MINOR 0x001d + + +/** @section Session Properties + for use with mwSession_setProperty, et al. +*/ +/*@{*/ + +/** char *, session user ID */ +#define mwSession_AUTH_USER_ID "session.auth.user" + +/** char *, plaintext password */ +#define mwSession_AUTH_PASSWORD "session.auth.password" + +/** struct mwOpaque *, authentication token */ +#define mwSession_AUTH_TOKEN "session.auth.token" + +/** char *, hostname of client */ +#define mwSession_CLIENT_HOST "client.host" + +/** guint32, local IP of client */ +#define mwSession_CLIENT_IP "client.ip" + +/** guint16, major version of client protocol */ +#define mwSession_CLIENT_VER_MAJOR "client.version.major" + +/** guint16, minor version of client protocol */ +#define mwSession_CLIENT_VER_MINOR "client.version.minor" + +/** guint16, client type identifier */ +#define mwSession_CLIENT_TYPE_ID "client.id" + +/** guint16, major version of server protocol */ +#define mwSession_SERVER_VER_MAJOR "server.version.major" + +/** guint16, minor version of server protocol */ +#define mwSession_SERVER_VER_MINOR "server.version.minor" + +/*@}*/ + + +enum mwSessionState { + mwSession_STARTING, /**< session is starting */ + mwSession_HANDSHAKE, /**< session has sent handshake */ + mwSession_HANDSHAKE_ACK, /**< session has received handshake ack */ + mwSession_LOGIN, /**< session has sent login */ + mwSession_LOGIN_REDIR, /**< session has been redirected */ + mwSession_LOGIN_ACK, /**< session has received login ack */ + mwSession_STARTED, /**< session is active */ + mwSession_STOPPING, /**< session is shutting down */ + mwSession_STOPPED, /**< session is stopped */ + mwSession_UNKNOWN, /**< indicates an error determining state */ + mwSession_LOGIN_CONT, /**< session has sent a login continue */ +}; + + +#define mwSession_isState(session, state) \ + (mwSession_getState((session)) == (state)) + +#define mwSession_isStarting(s) \ + (mwSession_isState((s), mwSession_STARTING) || \ + mwSession_isState((s), mwSession_HANDSHAKE) || \ + mwSession_isState((s), mwSession_HANDSHAKE_ACK) || \ + mwSession_isState((s), mwSession_LOGIN) || \ + mwSession_isState((s), mwSession_LOGIN_ACK) || \ + mwSession_isState((s), mwSession_LOGIN_REDIR) || \ + mwSession_isState((s), mwSession_LOGIN_CONT)) + +#define mwSession_isStarted(s) \ + (mwSession_isState((s), mwSession_STARTED)) + +#define mwSession_isStopping(s) \ + (mwSession_isState((s), mwSession_STOPPING)) + +#define mwSession_isStopped(s) \ + (mwSession_isState((s), mwSession_STOPPED)) + + +/** @struct mwSession + + Represents a Sametime client session */ +struct mwSession; + + +/** @struct mwSessionHandler + + session handler. Structure which interfaces a session with client + code to provide I/O and event handling */ +struct mwSessionHandler { + + /** write data to the server connection. Required. Should return + zero for success, non-zero for error */ + int (*io_write)(struct mwSession *, const guchar *buf, gsize len); + + /** close the server connection. Required */ + void (*io_close)(struct mwSession *); + + /** triggered by mwSession_free. Optional. Put cleanup code here */ + void (*clear)(struct mwSession *); + + /** Called when the session has changed status. + + @see mwSession_getStateInfo for uses of info field + + @param s the session + @param state the session's state + @param info additional state information */ + void (*on_stateChange)(struct mwSession *s, + enum mwSessionState state, gpointer info); + + /** called when privacy information has been sent or received + + @see mwSession_getPrivacyInfo + */ + void (*on_setPrivacyInfo)(struct mwSession *); + + /** called when user status has changed + + @see mwSession_getUserStatus */ + void (*on_setUserStatus)(struct mwSession *); + + /** called when an admin messages has been received */ + void (*on_admin)(struct mwSession *, const char *text); + + /** called when an announcement arrives */ + void (*on_announce)(struct mwSession *, struct mwLoginInfo *from, + gboolean may_reply, const char *text); + +}; + + +/** allocate a new session */ +struct mwSession *mwSession_new(struct mwSessionHandler *); + + +/** stop, clear, free a session. Does not free contained ciphers or + services, these must be taken care of explicitly. */ +void mwSession_free(struct mwSession *); + + +/** obtain a reference to the session's handler */ +struct mwSessionHandler *mwSession_getHandler(struct mwSession *); + + +/** instruct the session to begin. This will result in the initial + handshake message being sent. */ +void mwSession_start(struct mwSession *); + + +/** instruct the session to shut down with the following reason + code. */ +void mwSession_stop(struct mwSession *, guint32 reason); + + +/** Data is buffered, unpacked, and parsed into a message, then + processed accordingly. */ +void mwSession_recv(struct mwSession *, const guchar *, gsize); + + +/** primarily used by services to have messages serialized and sent + @param s session to send message over + @param msg message to serialize and send + @returns 0 for success */ +int mwSession_send(struct mwSession *s, struct mwMessage *msg); + + +/** sends the keepalive byte */ +int mwSession_sendKeepalive(struct mwSession *s); + + +/** respond to a login redirect message by forcing the login sequence + to continue through the immediate server. */ +int mwSession_forceLogin(struct mwSession *s); + + +/** send an announcement to a list of users/groups. Targets of + announcement must be in the same community as the session. + + @param s session to send announcement from + @param may_reply permit clients to reply. Not all clients honor this. + @param text text of announcement + @param recipients list of recipients. Each recipient is specified + by a single string, prefix with "@U " for users + and "@G " for Notes Address Book groups. +*/ +int mwSession_sendAnnounce(struct mwSession *s, gboolean may_reply, + const char *text, const GList *recipients); + + +/** set the internal privacy information, and inform the server as + necessary. Triggers the on_setPrivacyInfo call-back. */ +int mwSession_setPrivacyInfo(struct mwSession *, struct mwPrivacyInfo *); + + +/** direct reference to the session's internal privacy structure */ +struct mwPrivacyInfo *mwSession_getPrivacyInfo(struct mwSession *); + + +/** reference the login information for the session */ +struct mwLoginInfo *mwSession_getLoginInfo(struct mwSession *); + + +/** set the internal user status state, and inform the server as + necessary. Triggers the on_setUserStatus call-back */ +int mwSession_setUserStatus(struct mwSession *, struct mwUserStatus *); + + +struct mwUserStatus *mwSession_getUserStatus(struct mwSession *); + + +/** current status of the session */ +enum mwSessionState mwSession_getState(struct mwSession *); + + +/** additional status-specific information. Depending on the state of + the session, this value has different meaning. + + @li @c mwSession_STOPPING guint32 error code causing + the session to shut down + + @li @c mwSession_STOPPED guint32 error code causing + the session to shut down + + @li @c mwSession_LOGIN_REDIR (char *) host to redirect + to +*/ +gpointer mwSession_getStateInfo(struct mwSession *); + + +struct mwChannelSet *mwSession_getChannels(struct mwSession *); + + +/** adds a service to the session. If the session is started (or when + the session is successfully started) and the service has a start + function, the session will request service availability from the + server. On receipt of the service availability notification, the + session will call the service's start function. + + @return TRUE if the session was added correctly */ +gboolean mwSession_addService(struct mwSession *, struct mwService *); + + +/** find a service by its type identifier */ +struct mwService *mwSession_getService(struct mwSession *, guint32 type); + + +/** removes a service from the session. If the session is started and + the service has a stop function, it will be called. Returns the + removed service */ +struct mwService *mwSession_removeService(struct mwSession *, guint32 type); + + +/** a GList of services in this session. The GList needs to be freed + after use */ +GList *mwSession_getServices(struct mwSession *); + + +/** instruct a STARTED session to check the server for the presense of + a given service. The service will be automatically started upon + receipt of an affirmative reply from the server. This function is + automatically called upon all services in a session when the + session is fully STARTED. + + Services which terminate due to an error may call this on + themselves to re-initialize when their server-side counterpart is + made available again. + + @param s owning session + @param type service type ID */ +void mwSession_senseService(struct mwSession *s, guint32 type); + + +/** adds a cipher to the session. */ +gboolean mwSession_addCipher(struct mwSession *, struct mwCipher *); + + +/** find a cipher by its type identifier */ +struct mwCipher *mwSession_getCipher(struct mwSession *, guint16 type); + + +/** remove a cipher from the session */ +struct mwCipher *mwSession_removeCipher(struct mwSession *, guint16 type); + + +/** a GList of ciphers in this session. The GList needs to be freed + after use */ +GList *mwSession_getCiphers(struct mwSession *); + + +/** associate a key:value pair with the session. If an existing value is + associated with the same key, it will have its clear function called + and will be replaced with the new value */ +void mwSession_setProperty(struct mwSession *, const char *key, + gpointer val, GDestroyNotify clear); + + +/** obtain the value of a previously set property, or NULL */ +gpointer mwSession_getProperty(struct mwSession *, const char *key); + + +/** remove a property, calling the optional GDestroyNotify function + indicated in mwSession_setProperty if applicable */ +void mwSession_removeProperty(struct mwSession *, const char *key); + + +/** associate arbitrary data with the session for use by the client + code. Only client applications should use this, never services. + + @param session the session to associate the data with + @param data arbitrary client data + @param clear optional cleanup function called on data from + mwSession_removeClientData and mwSession_free +*/ +void mwSession_setClientData(struct mwSession *session, + gpointer data, GDestroyNotify clear); + + +gpointer mwSession_getClientData(struct mwSession *session); + + +/** remove client data, calling the optional GDestroyNotify function + indicated in mwSession_setClientData if applicable */ +void mwSession_removeClientData(struct mwSession *session); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SESSION_H */ + diff --git a/src/mw_srvc_aware.h b/src/mw_srvc_aware.h new file mode 100644 index 0000000..ec22e79 --- /dev/null +++ b/src/mw_srvc_aware.h @@ -0,0 +1,279 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_SRVC_AWARE_H +#define _MW_SRVC_AWARE_H + + +/** @file mw_srvc_aware.h + + The aware service... + + @todo remove the whole idea of an instantiated mwAwareList and + instead use arbitrary pointers (including NULL) as keys to + internally stored lists. This removes the problem of the service + free'ing its lists and invalidating mwAwareList references from + client code. +*/ + + +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** Type identifier for the aware service */ +#define mwService_AWARE 0x00000011 + + +/** @struct mwServiceAware + + Instance of an Aware Service. The members of this structure are + not made available. Accessing the parts of an aware service should + be performed through the appropriate functions. Note that + instances of this structure can be safely cast to a mwService. +*/ +struct mwServiceAware; + + +/** @struct mwAwareList + + Instance of an Aware List. The members of this structure are not + made available. Access to the parts of an aware list should be + handled through the appropriate functions. + + Any references to an aware list are rendered invalid when the + parent service is free'd +*/ +struct mwAwareList; + + +/** @struct mwAwareAttribute + + Key/Opaque pair indicating an identity's attribute. + */ +struct mwAwareAttribute; + + +/** Predefined keys appropriate for a mwAwareAttribute + */ +enum mwAwareAttributeKeys { + mwAttribute_AV_PREFS_SET = 0x01, /**< A/V prefs specified, gboolean */ + mwAttribute_MICROPHONE = 0x02, /**< has a microphone, gboolean */ + mwAttribute_SPEAKERS = 0x03, /**< has speakers, gboolean */ + mwAttribute_VIDEO_CAMERA = 0x04, /**< has a video camera, gboolean */ + mwAttribute_FILE_TRANSFER = 0x06, /**< supports file transfers, gboolean */ +}; + + +typedef void (*mwAwareAttributeHandler) + (struct mwServiceAware *srvc, + struct mwAwareAttribute *attrib); + + +struct mwAwareHandler { + mwAwareAttributeHandler on_attrib; + void (*clear)(struct mwServiceAware *srvc); +}; + + +/** Appropriate function type for the on-aware signal + + @param list mwAwareList emiting the signal + @param id awareness status information + @param data user-specified data +*/ +typedef void (*mwAwareSnapshotHandler) + (struct mwAwareList *list, + struct mwAwareSnapshot *id); + + +/** Appropriate function type for the on-option signal. The option's + value may need to be explicitly loaded in some instances, + resulting in this handler being triggered again. + + @param list mwAwareList emiting the signal + @param id awareness the attribute belongs to + @param attrib attribute +*/ +typedef void (*mwAwareIdAttributeHandler) + (struct mwAwareList *list, + struct mwAwareIdBlock *id, + struct mwAwareAttribute *attrib); + + +struct mwAwareListHandler { + /** handle aware updates */ + mwAwareSnapshotHandler on_aware; + + /** handle attribute updates */ + mwAwareIdAttributeHandler on_attrib; + + /** optional. Called from mwAwareList_free */ + void (*clear)(struct mwAwareList *list); +}; + + +struct mwServiceAware * +mwServiceAware_new(struct mwSession *session, + struct mwAwareHandler *handler); + + +/** Set an attribute value for this session */ +int mwServiceAware_setAttribute(struct mwServiceAware *srvc, + guint32 key, struct mwOpaque *opaque); + + +int mwServiceAware_setAttributeBoolean(struct mwServiceAware *srvc, + guint32 key, gboolean val); + + +int mwServiceAware_setAttributeInteger(struct mwServiceAware *srvc, + guint32 key, guint32 val); + + +int mwServiceAware_setAttributeString(struct mwServiceAware *srvc, + guint32 key, const char *str); + + +/** Unset an attribute for this session */ +int mwServiceAware_unsetAttribute(struct mwServiceAware *srvc, + guint32 key); + + +guint32 mwAwareAttribute_getKey(const struct mwAwareAttribute *attrib); + + +gboolean mwAwareAttribute_asBoolean(const struct mwAwareAttribute *attrib); + + +guint32 mwAwareAttribute_asInteger(const struct mwAwareAttribute *attrib); + + +/** Copy of attribute string, must be g_free'd. If the attribute's + content cannot be loaded as a string, returns NULL */ +char *mwAwareAttribute_asString(const struct mwAwareAttribute *attrib); + + +/** Direct access to an attribute's underlying opaque */ +const struct mwOpaque * +mwAwareAttribute_asOpaque(const struct mwAwareAttribute *attrib); + + +/** Allocate and initialize an aware list */ +struct mwAwareList * +mwAwareList_new(struct mwServiceAware *srvc, + struct mwAwareListHandler *handler); + + +/** Clean and free an aware list */ +void mwAwareList_free(struct mwAwareList *list); + + +struct mwAwareListHandler *mwAwareList_getHandler(struct mwAwareList *list); + + +/** Add a collection of user IDs to an aware list. + @param list mwAwareList to add user ID to + @param id_list mwAwareIdBlock list of user IDs to add + @return 0 for success, non-zero to indicate an error. +*/ +int mwAwareList_addAware(struct mwAwareList *list, GList *id_list); + + +/** Remove a collection of user IDs from an aware list. + @param list mwAwareList to remove user ID from + @param id_list mwAwareIdBlock list of user IDs to remove + @return 0 for success, non-zero to indicate an error. +*/ +int mwAwareList_removeAware(struct mwAwareList *list, GList *id_list); + + +int mwAwareList_removeAllAware(struct mwAwareList *list); + + +/** watch an NULL terminated array of keys */ +int mwAwareList_watchAttributeArray(struct mwAwareList *list, + guint32 *keys); + + +/** watch a NULL terminated list of keys */ +int mwAwareList_watchAttributes(struct mwAwareList *list, + guint32 key, ...); + + +/** stop watching a NULL terminated array of keys */ +int mwAwareList_unwatchAttributeArray(struct mwAwareList *list, + guint32 *keys); + + +/** stop watching a NULL terminated list of keys */ +int mwAwareList_unwatchAttributes(struct mwAwareList *list, + guint32 key, ...); + + +/** remove all watched attributes */ +int mwAwareList_unwatchAllAttributes(struct mwAwareList *list); + + +guint32 *mwAwareList_getWatchedAttributes(struct mwAwareList *list); + + +void mwAwareList_setClientData(struct mwAwareList *list, + gpointer data, GDestroyNotify cleanup); + + +void mwAwareList_removeClientData(struct mwAwareList *list); + + +gpointer mwAwareList_getClientData(struct mwAwareList *list); + + +/** trigger a got_aware event constructed from the passed user and + status information. Useful for adding false users and having the + getText function work for them */ +void mwServiceAware_setStatus(struct mwServiceAware *srvc, + struct mwAwareIdBlock *user, + struct mwUserStatus *stat); + + +/** look up the status description for a user */ +const char *mwServiceAware_getText(struct mwServiceAware *srvc, + struct mwAwareIdBlock *user); + + +/** look up the last known copy of an attribute for a user by the + attribute's key */ +const struct mwAwareAttribute * +mwServiceAware_getAttribute(struct mwServiceAware *srvc, + struct mwAwareIdBlock *user, + guint32 key); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_AWARE_H */ + diff --git a/src/mw_srvc_conf.h b/src/mw_srvc_conf.h new file mode 100644 index 0000000..156c639 --- /dev/null +++ b/src/mw_srvc_conf.h @@ -0,0 +1,209 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_SRVC_CONF_H +#define _MW_SRVC_CONF_H + + +#include +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** Type identifier for the conference service */ +#define mwService_CONFERENCE 0x80000010 + + +enum mwConferenceState { + mwConference_NEW, /**< new outgoing conference */ + mwConference_PENDING, /**< outgoing conference pending creation */ + mwConference_INVITED, /**< invited to incoming conference */ + mwConference_OPEN, /**< conference open and active */ + mwConference_CLOSING, /**< conference is closing */ + mwConference_ERROR, /**< conference is closing due to error */ + mwConference_UNKNOWN, /**< unable to determine conference state */ +}; + + +/** @struct mwServiceConference + Instance of the multi-user conference service */ +struct mwServiceConference; + + +/** @struct mwConference + A multi-user chat */ +struct mwConference; + + +/** Handler structure used to provide callbacks for an instance of the + conferencing service */ +struct mwConferenceHandler { + + /** triggered when we receive a conference invitation. Call + mwConference_accept to accept the invitation and join the + conference, or mwConference_close to reject the invitation. + + @param conf the newly created conference + @param inviter the indentity of the user who sent the invitation + @param invite the invitation text + */ + void (*on_invited)(struct mwConference *conf, + struct mwLoginInfo *inviter, const char *invite); + + /** triggered when we enter the conference. Provides the initial + conference membership list as a GList of mwLoginInfo structures + + @param conf the conference just joined + @param members mwLoginInfo list of existing conference members + */ + void (*conf_opened)(struct mwConference *conf, GList *members); + + /** triggered when a conference is closed. This is typically when + we've left it */ + void (*conf_closed)(struct mwConference *, guint32 reason); + + /** triggered when someone joins the conference */ + void (*on_peer_joined)(struct mwConference *, struct mwLoginInfo *); + + /** triggered when someone leaves the conference */ + void (*on_peer_parted)(struct mwConference *, struct mwLoginInfo *); + + /** triggered when someone says something */ + void (*on_text)(struct mwConference *conf, + struct mwLoginInfo *who, const char *what); + + /** typing notification */ + void (*on_typing)(struct mwConference *conf, + struct mwLoginInfo *who, gboolean typing); + + /** optional. called from mwService_free */ + void (*clear)(struct mwServiceConference *srvc); +}; + + +/** Allocate a new conferencing service, attaching the given handler + @param sess owning session + @param handler handler providing call-back functions for the service + */ +struct mwServiceConference * +mwServiceConference_new(struct mwSession *sess, + struct mwConferenceHandler *handler); + + +/** @returns the conference handler for the service */ +struct mwConferenceHandler * +mwServiceConference_getHandler(struct mwServiceConference *srvc); + + +/** a mwConference list of the conferences in this service. The GList + will need to be destroyed with g_list_free after use */ +GList *mwServiceConference_getConferences(struct mwServiceConference *srvc); + + +/** Allocate a new conference, in state NEW with the given title. + @see mwConference_create */ +struct mwConference *mwConference_new(struct mwServiceConference *srvc, + const char *title); + + +/** @returns the owning service of a conference */ +struct mwServiceConference *mwConference_getService(struct mwConference *conf); + + +/** @returns unique conference name */ +const char *mwConference_getName(struct mwConference *conf); + + +/** @returns conference title */ +const char *mwConference_getTitle(struct mwConference *conf); + + +/** a mwIdBlock list of the members of the conference. The GList will + need to be free'd after use */ +GList *mwConference_getMembers(struct mwConference *conf); + + +/** Initiate a conference. Conference must be in state NEW. If no name + or title for the conference has been set, they will be + generated. Conference will be placed into state PENDING. */ +int mwConference_open(struct mwConference *conf); + + +/** Leave and close an existing conference, or reject an invitation. + Triggers mwServiceConfHandler::conf_closed and free's the + conference. + */ +int mwConference_destroy(struct mwConference *conf, + guint32 reason, const char *text); + + +#define mwConference_reject(c,r,t) \ + mwConference_destroy((c),(r),(t)) + + +/** accept a conference invitation. Conference must be in the state + INVITED. */ +int mwConference_accept(struct mwConference *conf); + + +/** invite another user to an ACTIVE conference + @param conf conference + @param who user to invite + @param text invitation message + */ +int mwConference_invite(struct mwConference *conf, + struct mwIdBlock *who, const char *text); + + +/** send a text message over an open conference */ +int mwConference_sendText(struct mwConference *conf, const char *text); + + +/** send typing notification over an open conference */ +int mwConference_sendTyping(struct mwConference *conf, gboolean typing); + + +/** associate arbitrary client data and an optional cleanup function + with a conference. If there is already client data with a clear + function, it will not be called. */ +void mwConference_setClientData(struct mwConference *conf, + gpointer data, GDestroyNotify clear); + + +/** reference associated client data */ +gpointer mwConference_getClientData(struct mwConference *conf); + + +/** remove associated client data if any, and call the cleanup + function on the data as necessary */ +void mwConference_removeClientData(struct mwConference *conf); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_CONF_H */ + diff --git a/src/mw_srvc_dir.h b/src/mw_srvc_dir.h new file mode 100644 index 0000000..b9230bb --- /dev/null +++ b/src/mw_srvc_dir.h @@ -0,0 +1,212 @@ +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_SRVC_DIR_H +#define _MW_SERV_DIR_H + + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +struct mwSession; + + +#define SERVICE_DIRECTORY 0x0000001a + + +/** @struct mwServiceDirectory + + the directory service. */ +struct mwServiceDirectory; + + +/** @struct mwAddressBook + + server-side collection of users and groups. Open a directory + based on an address book to search or list its contents */ +struct mwAddressBook; + + +/** @struct mwDirectory + + searchable directory, based off of an address book */ +struct mwDirectory; + + +enum mwDirectoryState { + mwDirectory_NEW, /**< directory is created, but not open */ + mwDirectory_PENDING, /**< directory has in the process of opening */ + mwDirectory_OPEN, /**< directory is open */ + mwDirectory_ERROR, /**< error opening or using directory */ + mwDirectory_UNKNOWN, /**< error determining directory state */ +}; + + +/** return value of directory searches that fail */ +#define DIR_SEARCH_ERROR 0x00000000 + + +#define MW_DIRECTORY_IS_STATE(dir, state) \ + (mwDirectory_getState(dir) == (state)) + +#define MW_DIRECTORY_IS_NEW(dir) \ + MW_DIRECTORY_IS_STATE((dir), mwDirectory_NEW) + +#define MW_DIRECTORY_IS_PENDING(dir) \ + MW_DIRECTORY_IS_STATE((dir), mwDirectory_PENDING) + +#define MW_DIRECTORY_IS_OPEN(dir) \ + MW_DIRECTORY_IS_STATE((dir), mwDirectory_OPEN) + + +enum mwDirectoryMemberType { + mwDirectoryMember_USER = 0x0000, + mwDirectoryMember_GROUP = 0x0001, +}; + + +struct mwDirectoryMember { + guint16 type; /**< @see mwDirectoryMemberType */ + char *id; /**< proper ID for member */ + char *long_name; /**< full name of member (USER type only) */ + char *short_name; /**< short name of member */ + guint16 foo; /**< unknown */ +}; + + +/** Appropriate function signature for handling directory search results */ +typedef void (*mwSearchHandler) + (struct mwDirectory *dir, + guint32 code, guint32 offset, GList *members); + + +/** handles asynchronous events for a directory service instance */ +struct mwDirectoryHandler { + + /** handle receipt of the address book list from the service. + Initially triggered by mwServiceDirectory_refreshAddressBooks + and at service startup */ + void (*on_book_list)(struct mwServiceDirectory *srvc, GList *books); + + /** triggered when a directory has been successfully opened */ + void (*dir_opened)(struct mwDirectory *dir); + + /** triggered when a directory has been closed */ + void (*dir_closed)(struct mwDirectory *dir, guint32 reason); + + /** optional. called from mwService_free */ + void (*clear)(struct mwServiceDirectory *srvc); +}; + + +/** Allocate a new directory service instance for use with session */ +struct mwServiceDirectory * +mwServiceDirectory_new(struct mwSession *session, + struct mwDirectoryHandler *handler); + + +/** the handler associated with the service at its creation */ +struct mwDirectoryHandler * +mwServiceDirectory_getHandler(struct mwServiceDirectory *srvc); + + +/** most recent list of address books available in service */ +GList *mwServiceDirectory_getAddressBooks(struct mwServiceDirectory *srvc); + + +/** submit a request to obtain an updated list of address books from + service */ +int mwServiceDirectory_refreshAddressBooks(struct mwServiceDirectory *srvc); + + +/** list of directories in the service */ +GList *mwServiceDirectory_getDirectories(struct mwServiceDirectory *srvc); + + +/** list of directories associated with address book. Note that the + returned GList will need to be free'd after use */ +GList *mwAddressBook_getDirectories(struct mwAddressBook *book); + + +/** the name of the address book */ +const char *mwAddressBook_getName(struct mwAddressBook *book); + + +/** allocate a new directory based off the given address book */ +struct mwDirectory *mwDirectory_new(struct mwAddressBook *book); + + +enum mwDirectoryState mwDirectory_getState(struct mwDirectory *dir); + + +/** set client data. If there is an existing clear function, it will + not be called */ +void mwDirectory_setClientData(struct mwDirectory *dir, + gpointer data, GDestroyNotify clear); + + +/** reference associated client data */ +gpointer mwDirectory_getClientData(struct mwDirectory *dir); + + +/** remove and cleanup user data */ +void mwDirectory_removeClientData(struct mwDirectory *dir); + + +/** reference owning service */ +struct mwServiceDirectory *mwDirectory_getService(struct mwDirectory *dir); + + +/** reference owning address book */ +struct mwAddressBook *mwDirectory_getAddressBook(struct mwDirectory *dir); + + +/** initialize a directory. */ +int mwDirectory_open(struct mwDirectory *dir, mwSearchHandler cb); + + +/** continue a search into its next results */ +int mwDirectory_next(struct mwDirectory *dir); + + +/** continue a search into its previous results */ +int mwDirectory_previous(struct mwDirectory *dir); + + +/** initiate a search on an open directory */ +int mwDirectory_search(struct mwDirectory *dir, const char *query); + + +/** close and free the directory, and unassociate it with its owning + address book and service */ +int mwDirectory_destroy(struct mwDirectory *dir); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_DIR_H */ diff --git a/src/mw_srvc_ft.h b/src/mw_srvc_ft.h new file mode 100644 index 0000000..8c8efa6 --- /dev/null +++ b/src/mw_srvc_ft.h @@ -0,0 +1,249 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef _MW_SRVC_FT_H +#define _MW_SRVC_FT_H + + +/** @file mw_srvc_ft.h + + A file transfer is a simple way to get large chunks of binary data + from one client to another. +*/ + + +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** @struct mwServiceFileTransfer + File transfer service +*/ +struct mwServiceFileTransfer; + + +/** @struct mwFileTransfer + A single file trasfer session + */ +struct mwFileTransfer; + + +#define mwService_FILE_TRANSFER 0x00000038 + + +enum mwFileTransferState { + mwFileTransfer_NEW, /**< file transfer is not open */ + mwFileTransfer_PENDING, /**< file transfer is opening */ + mwFileTransfer_OPEN, /**< file transfer is open */ + mwFileTransfer_CANCEL_LOCAL, + mwFileTransfer_CANCEL_REMOTE, + mwFileTransfer_DONE, + mwFileTransfer_ERROR, /**< error in file transfer */ + mwFileTransfer_UNKNOWN, /**< unknown state */ +}; + + +#define mwFileTransfer_isState(ft, state) \ + (mwFileTransfer_getState(ft) == (state)) + +#define mwFileTransfer_isNew(ft) \ + mwFileTransfer_isState((ft), mwFileTransfer_NEW) + +#define mwFileTransfer_isPending(ft) \ + mwFileTransfer_isState((ft), mwFileTransfer_PENDING) + +#define mwFileTransfer_isOpen(ft) \ + mwFileTransfer_isState((ft), mwFileTransfer_OPEN) + +#define mwFileTransfer_isDone(ft) \ + mwFileTransfer_isState((ft), mwFileTransfer_DONE) + +#define mwFileTransfer_isCancelLocal(ft) \ + mwFileTransfer_isState((ft), mwFileTransfer_CANCEL_LOCAL) + +#define mwFileTransfer_isCancelRemote(ft) \ + mwFileTransfer_isState((ft), mwFileTransfer_CANCEL_REMOTE) + + +enum mwFileTranferCode { + mwFileTransfer_SUCCESS = 0x00000000, + mwFileTransfer_REJECTED = 0x08000606, +}; + + +struct mwFileTransferHandler { + + /** an incoming file transfer has been offered */ + void (*ft_offered)(struct mwFileTransfer *ft); + + /** a file transfer has been fully initiated */ + void (*ft_opened)(struct mwFileTransfer *ft); + + /** a file transfer has been closed. Check the status of the file + transfer to determine if the transfer was complete or if it had + been interrupted */ + void (*ft_closed)(struct mwFileTransfer *ft, guint32 code); + + /** receive a chunk of a file from an inbound file transfer. */ + void (*ft_recv)(struct mwFileTransfer *ft, struct mwOpaque *data); + + /** received an ack for a sent chunk on an outbound file transfer. + this indicates that a previous call to mwFileTransfer_send has + reached the target and that the target has responded. */ + void (*ft_ack)(struct mwFileTransfer *ft); + + /** optional. called from mwService_free */ + void (*clear)(struct mwServiceFileTransfer *srvc); +}; + + +struct mwServiceFileTransfer * +mwServiceFileTransfer_new(struct mwSession *session, + struct mwFileTransferHandler *handler); + + +struct mwFileTransferHandler * +mwServiceFileTransfer_getHandler(struct mwServiceFileTransfer *srvc); + + +const GList * +mwServiceFileTransfer_getTransfers(struct mwServiceFileTransfer *srvc); + + +struct mwFileTransfer * +mwFileTransfer_new(struct mwServiceFileTransfer *srvc, + const struct mwIdBlock *who, const char *msg, + const char *filename, guint32 filesize); + + +/** deallocate a file transfer. will call mwFileTransfer_close if + necessary */ +void +mwFileTransfer_free(struct mwFileTransfer *ft); + + +/** the status of this file transfer */ +enum mwFileTransferState +mwFileTransfer_getState(struct mwFileTransfer *ft); + + +struct mwServiceFileTransfer * +mwFileTransfer_getService(struct mwFileTransfer *ft); + + +/** the user on the other end of the file transfer */ +const struct mwIdBlock * +mwFileTransfer_getUser(struct mwFileTransfer *ft); + + +/** the message sent along with an offered file transfer */ +const char * +mwFileTransfer_getMessage(struct mwFileTransfer *ft); + + +/** the publicized file name. Not necessarily related to any actual + file on either system */ +const char * +mwFileTransfer_getFileName(struct mwFileTransfer *ft); + + +/** total bytes intended to be sent/received */ +guint32 mwFileTransfer_getFileSize(struct mwFileTransfer *ft); + + +/** bytes remaining to be received/send */ +guint32 mwFileTransfer_getRemaining(struct mwFileTransfer *ft); + + +/** count of bytes sent/received over this file transfer so far */ +#define mwFileTransfer_getSent(ft) \ + (mwFileTransfer_getFileSize(ft) - mwFileTransfer_getRemaining(ft)) + + +/** initiate an outgoing file transfer */ +int mwFileTransfer_offer(struct mwFileTransfer *ft); + + +/** accept an incoming file transfer */ +int mwFileTransfer_accept(struct mwFileTransfer *ft); + + +/** reject an incoming file transfer */ +#define mwFileTransfer_reject(ft) \ + mwFileTransfer_close((ft), mwFileTransfer_REJECTED) + + +/** cancel an open file transfer */ +#define mwFileTransfer_cancel(ft) \ + mwFileTransfer_close((ft), mwFileTransfer_SUCCESS); + + +/** Close a file transfer. This will trigger the ft_close function of the + session's handler. + + @see mwFileTransfer_reject + @see mwFileTransfer_cancel +*/ +int mwFileTransfer_close(struct mwFileTransfer *ft, guint32 code); + + +/** send a chunk of data over an outbound file transfer. The client at + the other end of the transfer should respond with an acknowledgement + message, which can be caught in the service's handler. + + @see mwFileTransferHandler::ft_ack +*/ +int mwFileTransfer_send(struct mwFileTransfer *ft, + struct mwOpaque *data); + + +/** acknowledge the receipt of a chunk of data from an inbound file + transfer. This should be done after every received chunk, or the + transfer will stall. However, not all clients will wait for an ack + after sending a chunk before sending the next chunk, so it is + possible to have the handler's ft_recv function triggered again + even if no ack was sent. + + @see mwFileTransferHandler::ft_recv +*/ +int mwFileTransfer_ack(struct mwFileTransfer *ft); + + +void mwFileTransfer_setClientData(struct mwFileTransfer *ft, + gpointer data, GDestroyNotify clean); + + +gpointer mwFileTransfer_getClientData(struct mwFileTransfer *ft); + + +void mwFileTransfer_removeClientData(struct mwFileTransfer *ft); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_FT_H */ diff --git a/src/mw_srvc_im.h b/src/mw_srvc_im.h new file mode 100644 index 0000000..d7ed775 --- /dev/null +++ b/src/mw_srvc_im.h @@ -0,0 +1,276 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_SRVC_IM_H +#define _MW_SRVC_IM_H + + +/** @file mw_srvc_im.h + + The IM service provides one-on-one communication between + users. Messages sent over conversations may relay different types + of information, in a variety of formats. The basic feature-set + provides plain-text chat with typing notification. More complex + features may be negotiated transparently by setting the IM Client + Type for a conversation, or for the service as a whole. +*/ + + +#include +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* identifier for the IM service */ +#define mwService_IM 0x00001000 + + +/** @struct mwServiceIm + + An instance of the IM service. This service provides simple + instant messaging functionality */ +struct mwServiceIm; + + +/** @struct mwConversation + + A conversation between the local service and a single other user */ +struct mwConversation; + + +enum mwImClientType { + mwImClient_PLAIN = 0x00000001, /**< text, typing */ + mwImClient_NOTESBUDDY = 0x00033453, /**< adds html, subject, mime */ + mwImClient_PRECONF = 0x00000019, /**< pre-conference, legacy */ + mwImClient_UNKNOWN = 0xffffffff, /**< trouble determining type */ +}; + + +/** + Types of supported messages. When a conversation is created, the + least common denominator of features between either side of the + conversation (based on what features are available in the IM + service itself) becomes the set of supported features for that + conversation. At any point, the feature set for the service may + change, without affecting any existing conversations. + + @see mwServiceIm_supports + @see mwServiceIm_setSupported + @see mwConversation_supports + @see mwConversation_send + @see mwServiceImHandler::conversation_recv + */ +enum mwImSendType { + mwImSend_PLAIN, /**< char *, plain-text message */ + mwImSend_TYPING, /**< gboolean, typing status */ + mwImSend_HTML, /**< char *, HTML formatted message (NOTESBUDDY) */ + mwImSend_SUBJECT, /**< char *, conversation subject (NOTESBUDDY) */ + mwImSend_MIME, /**< char *, MIME-encoded message (NOTESBUDDY) */ + mwImSend_TIMESTAMP, /**< char *, YYYY:MM:DD:HH:mm:SS format (NOTESBUDDY) */ +}; + + + +/** @see mwConversation_getState */ +enum mwConversationState { + mwConversation_CLOSED, /**< conversation is not open */ + mwConversation_PENDING, /**< conversation is opening */ + mwConversation_OPEN, /**< conversation is open */ + mwConversation_UNKNOWN, /**< unknown state */ +}; + + +#define mwConversation_isState(conv, state) \ + (mwConversation_getState(conv) == (state)) + +#define mwConversation_isClosed(conv) \ + mwConversation_isState((conv), mwConversation_CLOSED) + +#define mwConversation_isPending(conv) \ + mwConversation_isState((conv), mwConversation_PENDING) + +#define mwConversation_isOpen(conv) \ + mwConversation_isState((conv), mwConversation_OPEN) + + + +/** IM Service Handler. Provides functions for events triggered from an + IM service instance. */ +struct mwImHandler { + + /** A conversation has been successfully opened */ + void (*conversation_opened)(struct mwConversation *conv); + + /** A conversation has been closed */ + void (*conversation_closed)(struct mwConversation *conv, guint32 err); + + /** A message has been received on a conversation */ + void (*conversation_recv)(struct mwConversation *conv, + enum mwImSendType type, gconstpointer msg); + + /** Handle a Place invitation. Set this to NULL and we should end up + receiving a conference invitation instead. */ + void (*place_invite)(struct mwConversation *conv, + const char *message, + const char *title, const char *name); + + /** optional. called from mwService_free */ + void (*clear)(struct mwServiceIm *srvc); +}; + + +struct mwServiceIm *mwServiceIm_new(struct mwSession *session, + struct mwImHandler *handler); + + +struct mwImHandler *mwServiceIm_getHandler(struct mwServiceIm *srvc); + + +/** reference an existing conversation to target, or create a new + conversation to target if one does not already exist */ +struct mwConversation *mwServiceIm_getConversation(struct mwServiceIm *srvc, + struct mwIdBlock *target); + + +/** reference an existing conversation to target */ +struct mwConversation *mwServiceIm_findConversation(struct mwServiceIm *srvc, + struct mwIdBlock *target); + + +/** determine if the conversations created from this service will + support a given send type */ +gboolean mwServiceIm_supports(struct mwServiceIm *srvc, + enum mwImSendType type); + + +/** Set the default client type for the service. Newly created + conversations will attempt to meet this level of functionality + first. + + @param srvc the IM service + @param type the send type to enable/disable +*/ +void mwServiceIm_setClientType(struct mwServiceIm *srvc, + enum mwImClientType type); + + +enum mwImClientType mwServiceIm_getClientType(struct mwServiceIm *srvc); + + +/** attempt to open a conversation. If the conversation was not + already open and it is accepted, + mwServiceImHandler::conversation_opened will be triggered. Upon + failure, mwServiceImHandler::conversation_closed will be + triggered */ +void mwConversation_open(struct mwConversation *conv); + + +/** close a conversation. If the conversation was not already closed, + mwServiceImHandler::conversation_closed will be triggered */ +void mwConversation_close(struct mwConversation *conv, guint32 err); + + +/** determine whether a conversation supports the given message type */ +gboolean mwConversation_supports(struct mwConversation *conv, + enum mwImSendType type); + + +enum mwImClientType mwConversation_getClientType(struct mwConversation *conv); + + +/** get the state of a conversation + + @see mwConversation_isOpen + @see mwConversation_isClosed + @see mwConversation_isPending +*/ +enum mwConversationState mwConversation_getState(struct mwConversation *conv); + + +/** send a message over an open conversation */ +int mwConversation_send(struct mwConversation *conv, + enum mwImSendType type, gconstpointer send); + + +/** @returns owning service for a conversation */ +struct mwServiceIm *mwConversation_getService(struct mwConversation *conv); + + +/** login information for conversation partner. returns NULL if conversation + is not OPEN */ +struct mwLoginInfo *mwConversation_getTargetInfo(struct mwConversation *conv); + + +/** ID for conversation partner */ +struct mwIdBlock *mwConversation_getTarget(struct mwConversation *conv); + + +/** set whether outgoing messages should be encrypted using the + negotiated cipher, if any */ +void mwConversation_setEncrypted(struct mwConversation *conv, + gboolean useCipher); + + +/** determine whether outgoing messages are being encrypted */ +gboolean mwConversation_isEncrypted(struct mwConversation *conv); + + +/** Associates client data with a conversation. If there is existing data, + it will not have its cleanup function called. + + @see mwConversation_getClientData + @see mwConversation_removeClientData +*/ +void mwConversation_setClientData(struct mwConversation *conv, + gpointer data, GDestroyNotify clean); + + +/** Reference associated client data + + @see mwConversation_setClientData + @see mwConversation_removeClientData + */ +gpointer mwConversation_getClientData(struct mwConversation *conv); + + +/** Remove any associated client data, calling the optional cleanup + function if one was provided + + @see mwConversation_setClientData + @see mwConversation_getClientData +*/ +void mwConversation_removeClientData(struct mwConversation *conv); + + +/** close and destroy the conversation and its backing channel, and + call the optional client data cleanup function */ +void mwConversation_free(struct mwConversation *conv); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_IM_H */ diff --git a/src/mw_srvc_place.h b/src/mw_srvc_place.h new file mode 100644 index 0000000..e17aa71 --- /dev/null +++ b/src/mw_srvc_place.h @@ -0,0 +1,148 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_SRVC_PLACE_H +#define _MW_SRVC_PLACE_H + + +#include +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** Type identifier for the place service */ +#define mwService_PLACE 0x80000022 + + +/** @struct mwServicePlace */ +struct mwServicePlace; + + +/** @struct mwPlace */ +struct mwPlace; + + +struct mwPlaceHandler { + void (*opened)(struct mwPlace *place); + void (*closed)(struct mwPlace *place, guint32 code); + + void (*peerJoined)(struct mwPlace *place, + const struct mwIdBlock *peer); + + void (*peerParted)(struct mwPlace *place, + const struct mwIdBlock *peer); + + void (*peerSetAttribute)(struct mwPlace *place, + const struct mwIdBlock *peer, + guint32 attr, struct mwOpaque *o); + + void (*peerUnsetAttribute)(struct mwPlace *place, + const struct mwIdBlock *peer, + guint32 attr); + + void (*message)(struct mwPlace *place, + const struct mwIdBlock *who, + const char *msg); + + void (*clear)(struct mwServicePlace *srvc); +}; + + +enum mwPlacePeerAttribute { + mwPlacePeer_TYPING = 0x00000008, +}; + + +struct mwServicePlace * +mwServicePlace_new(struct mwSession *session, + struct mwPlaceHandler *handler); + + +struct mwPlaceHandler * +mwServicePlace_getHandler(struct mwServicePlace *srvc); + + +const GList *mwServicePlace_getPlaces(struct mwServicePlace *srvc); + + +struct mwPlace *mwPlace_new(struct mwServicePlace *srvc, + const char *name, const char *title); + + +struct mwServicePlace *mwPlace_getService(struct mwPlace *place); + + +const char *mwPlace_getName(struct mwPlace *place); + + +const char *mwPlace_getTitle(struct mwPlace *place); + + +int mwPlace_open(struct mwPlace *place); + + +int mwPlace_destroy(struct mwPlace *place, guint32 code); + + +/** returns a GList* of struct mwIdBlock*. The GList will need to be + freed after use, the mwIdBlock structures should not be modified + or freed */ +GList *mwPlace_getMembers(struct mwPlace *place); + + +int mwPlace_sendText(struct mwPlace *place, const char *msg); + + +/** send a legacy invitation for this place to a user. The user will + receive an apparent invitation from a Conference (rather than a + Place) */ +int mwPlace_legacyInvite(struct mwPlace *place, + struct mwIdBlock *idb, + const char *message); + + +int mwPlace_setAttribute(struct mwPlace *place, guint32 attrib, + struct mwOpaque *data); + + +int mwPlace_unsetAttribute(struct mwPlace *place, guint32 attrib); + + +void mwPlace_setClientData(struct mwPlace *place, + gpointer data, GDestroyNotify clean); + + +gpointer mwPlace_getClientData(struct mwPlace *place); + + +void mwPlace_removeClientData(struct mwPlace *place); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_PLACE_H */ + diff --git a/src/mw_srvc_resolve.h b/src/mw_srvc_resolve.h new file mode 100644 index 0000000..3b06b75 --- /dev/null +++ b/src/mw_srvc_resolve.h @@ -0,0 +1,150 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_SRVC_RESOLVE_H +#define _MW_SRVC_RESOLVE_H + + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** Type identifier for the conference service */ +#define mwService_RESOLVE 0x00000015 + + +/** Return value of mwServiceResolve_search indicating an error */ +#define SEARCH_ERROR 0x00 + + +/** @struct mwServiceResolve + User lookup service */ +struct mwServiceResolve; + + +enum mwResolveFlag { + /** return unique results or none at all */ + mwResolveFlag_UNIQUE = 0x00000001, + + /** return only the first result */ + mwResolveFlag_FIRST = 0x00000002, + + /** search all directories, not just the first with a match */ + mwResolveFlag_ALL_DIRS = 0x00000004, + + /** search for users */ + mwResolveFlag_USERS = 0x00000008, + + /** search for groups */ + mwResolveFlag_GROUPS = 0x00000010, +}; + + +/** @see mwResolveResult */ +enum mwResolveCode { + /** successful search */ + mwResolveCode_SUCCESS = 0x00000000, + + /** only some of the nested searches were successful */ + mwResolveCode_PARTIAL = 0x00010000, + + /** more than one result (occurs when mwResolveFlag_UNIQUE is used + and more than one result would have been otherwise returned) */ + mwResolveCode_MULTIPLE = 0x80020000, + + /** the name is not resolvable due to its format */ + mwResolveCode_BAD_FORMAT = 0x80030000, +}; + + +enum mwResolveMatchType { + mwResolveMatch_USER = 0x00000001, + mwResolveMatch_GROUP = 0x00000002, +}; + + +struct mwResolveMatch { + char *id; /**< user id */ + char *name; /**< user name */ + char *desc; /**< description */ + guint32 type; /**< @see mwResolveMatchType */ +}; + + +struct mwResolveResult { + guint32 code; /**< @see mwResolveCode */ + char *name; /**< name of the result */ + GList *matches; /**< list of mwResolveMatch */ +}; + + +/** Handle the results of a resolve request. If there was a cleanup + function specified to mwServiceResolve_search, it will be called + upon the user data after this callback returns. + + @param srvc the resolve service + @param id the resolve request ID + @param code return code + @param results list of mwResolveResult + @param data optional user data attached to the request +*/ +typedef void (*mwResolveHandler) + (struct mwServiceResolve *srvc, + guint32 id, guint32 code, GList *results, + gpointer data); + + +/** Allocate a new resolve service */ +struct mwServiceResolve *mwServiceResolve_new(struct mwSession *); + + +/** Inisitate a resolve request. + + @param srvc the resolve service + @param queries list query strings + @param flags search flags + @param handler result handling function + @param data optional user data attached to the request + @param cleanup optional function to clean up user data + @return generated ID for the search request, or SEARCH_ERROR +*/ +guint32 mwServiceResolve_resolve(struct mwServiceResolve *srvc, + GList *queries, enum mwResolveFlag flags, + mwResolveHandler handler, + gpointer data, GDestroyNotify cleanup); + + +/** Cancel a resolve request by its generated ID. The handler function + will not be called, and the optional cleanup function will be + called upon the optional user data for the request */ +void mwServiceResolve_cancelResolve(struct mwServiceResolve *, guint32); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_RESOLVE_H */ diff --git a/src/mw_srvc_store.h b/src/mw_srvc_store.h new file mode 100644 index 0000000..e5bcde2 --- /dev/null +++ b/src/mw_srvc_store.h @@ -0,0 +1,196 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_SRVC_STORE_H +#define _MW_SRVC_STORE_H + + +#include +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** Type identifier for the storage service */ +#define mwService_STORAGE 0x00000018 + + +/** @struct mwServiceStorage + @see mwServiceStorage_new + + Instance of the storage service */ +struct mwServiceStorage; + + +/** @struct mwStorage + + Unit Represents information intended for loading from or saving to + the storage service */ +struct mwStorageUnit; + + +/** The upper limit of reserved Lotus keys */ +#define LOTUS_RESERVED_LIMIT 0x186a0 + + +/** Check if a key is in the range of Lotus reserved keys */ +#define KEY_IS_LOTUS_RESERVED(key) \ + (((guint32) key) <= (LOTUS_RESERVED_LIMIT)) + + +/** Some common keys storage keys. Anything in the range 0x00 to + 0x186a0 (100000) is reserved for use by the Lotus + clients. */ +enum mwStorageKey { + + /** The buddy list, in the Sametime .dat file format. String */ + mwStore_AWARE_LIST = 0x00000000, + + /** Default text for chat invitations. String */ + mwStore_INVITE_CHAT = 0x00000006, + + /** Default text for meeting invitations. String */ + mwStore_INVITE_MEETING = 0x0000000e, + + /** Last five Away messages, separated by semicolon. String */ + mwStore_AWAY_MESSAGES = 0x00000050, + + /** Last five Busy (DND) messages, separated by semicolon. String */ + mwStore_BUSY_MESSAGES = 0x0000005a, + + /** Last five Active messages, separated by semicolon. String */ + mwStore_ACTIVE_MESSAGES = 0x00000064, +}; + + +/** Appropriate function type for load and store callbacks. + @param srvc the storage service + @param result the result value of the load or store call + @param item the storage unit loaded or saved + @param data optional user data +*/ +typedef void (*mwStorageCallback) + (struct mwServiceStorage *srvc, + guint32 result, struct mwStorageUnit *item, + gpointer data); + + +/** Allocates and initializes a storage service instance for use on + the passed session. */ +struct mwServiceStorage *mwServiceStorage_new(struct mwSession *); + + +/** create an empty storage unit */ +struct mwStorageUnit *mwStorageUnit_new(guint32 key); + + +/** creates a storage unit with the passed key, and a copy of data. */ +struct mwStorageUnit *mwStorageUnit_newOpaque(guint32 key, + struct mwOpaque *data); + + +/** creates a storage unit with the passed key, and an encapsulated + boolean value */ +struct mwStorageUnit *mwStorageUnit_newBoolean(guint32 key, + gboolean val); + + +struct mwStorageUnit *mwStorageUnit_newInteger(guint32 key, + guint32 val); + + +/** creates a storage unit with the passed key, and an encapsulated + string value. */ +struct mwStorageUnit *mwStorageUnit_newString(guint32 key, + const char *str); + + +/** get the key for the given storage unit */ +guint32 mwStorageUnit_getKey(struct mwStorageUnit *); + + +/** attempts to obtain a boolean value from a storage unit. If the + unit is empty, or does not contain the type in a recongnizable + format, val is returned instead */ +gboolean mwStorageUnit_asBoolean(struct mwStorageUnit *, gboolean val); + + +/** attempts to obtain a guint32 value from a storage unit. If the + unit is empty, or does not contain the type in a recognizable + format, val is returned instead */ +guint32 mwStorageUnit_asInteger(struct mwStorageUnit *, guint32 val); + + +/** attempts to obtain a string value from a storage unit. If the unit + is empty, or does not contain the type in a recognizable format, + NULL is returned instead. Note that the string returned is a copy, + and will need to be deallocated at some point. */ +char *mwStorageUnit_asString(struct mwStorageUnit *); + + +/** direct access to the opaque data backing the storage unit */ +struct mwOpaque *mwStorageUnit_asOpaque(struct mwStorageUnit *); + + +/** clears and frees a storage unit */ +void mwStorageUnit_free(struct mwStorageUnit *); + + +/** Initiates a load call to the storage service. If the service is + not currently available, the call will be cached and processed + when the service is started. + + @param srvc the storage service + @param item storage unit to load + @param cb callback function when the load call completes + @param data user data for callback + @param data_free optional cleanup function for user data +*/ +void mwServiceStorage_load(struct mwServiceStorage *srvc, + struct mwStorageUnit *item, + mwStorageCallback cb, + gpointer data, GDestroyNotify data_free); + + +/** Initiates a store call to the storage service. If the service is + not currently available, the call will be cached and processed + when the service is started. + + @param srvc the storage service + @param item storage unit to save + @param cb callback function when the load call completes + @param data optional user data for callback + @param data_free optional cleanup function for user data + */ +void mwServiceStorage_save(struct mwServiceStorage *srvc, + struct mwStorageUnit *item, + mwStorageCallback cb, + gpointer data, GDestroyNotify data_free); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_SRVC_STORE_H */ diff --git a/src/mw_st_list.h b/src/mw_st_list.h new file mode 100644 index 0000000..db0d0e1 --- /dev/null +++ b/src/mw_st_list.h @@ -0,0 +1,218 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_ST_LIST_H +#define _MW_ST_LIST_H + + +/** @file mw_st_list.h + + Parse and compose buddy lists in the format commonly used by Sametime + Connect clients. +*/ + + +#include +#include +#include "mw_common.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define ST_LIST_MAJOR 3 +#define ST_LIST_MINOR 1 +#define ST_LIST_MICRO 3 + + +enum mwSametimeGroupType { + mwSametimeGroup_NORMAL = 1, /**< a normal group of users */ + mwSametimeGroup_DYNAMIC = 2, /**< a server-side group */ + mwSametimeGroup_UNKNOWN = 0, /**< error determining group type */ +}; + + +enum mwSametimeUserType { + mwSametimeUser_NORMAL = 1, /**< user on same community */ + mwSametimeUser_EXTERNAL = 2, /**< external user */ + mwSametimeUser_UNKNOWN = 0, /**< error determining user type */ +}; + + +/** @struct mwSametimeList + + Represents a group-based buddy list. */ +struct mwSametimeList; + + +/** @struct mwSametimeGroup + + Represents a group in a buddy list */ +struct mwSametimeGroup; + + +/** @struct mwSametimeUser + + Represents a user in a group in a buddy list */ +struct mwSametimeUser; + + +/** Create a new list */ +struct mwSametimeList *mwSametimeList_new(void); + + +/** Free the list, all of its groups, and all of the groups' members */ +void mwSametimeList_free(struct mwSametimeList *l); + + +/** Load a sametime list from a buffer. The list must be encapsulated + as a string (eg, the first two bytes in the buffer should be the + length of the string) */ +void mwSametimeList_get(struct mwGetBuffer *b, struct mwSametimeList *l); + + +/** Write a sametime list onto a buffer. The list will be encapsulated + in a string (the first two bytes written will be the length of the + rest of the written list data) */ +void mwSametimeList_put(struct mwPutBuffer *b, struct mwSametimeList *l); + + +/** convert a plain string into a sametime list */ +struct mwSametimeList *mwSametimeList_load(const char *str); + + +/** convert a sametime list into a string */ +char *mwSametimeList_store(struct mwSametimeList *l); + + +void mwSametimeList_setMajor(struct mwSametimeList *l, guint v); + + +guint mwSametimeList_getMajor(struct mwSametimeList *l); + + +void mwSametimeList_setMinor(struct mwSametimeList *l, guint v); + + +guint mwSametimeList_getMinor(struct mwSametimeList *l); + + +void mwSametimeList_setMicro(struct mwSametimeList *l, guint v); + + +guint mwSametimeList_getMicro(struct mwSametimeList *l); + + +/** Get a GList snapshot of the groups in a list */ +GList *mwSametimeList_getGroups(struct mwSametimeList *l); + + +struct mwSametimeGroup * +mwSametimeList_findGroup(struct mwSametimeList *l, + const char *name); + + +/** Create a new group in a list */ +struct mwSametimeGroup * +mwSametimeGroup_new(struct mwSametimeList *l, + enum mwSametimeGroupType type, + const char *name); + + +/** Remove a group from its list, and free it. Also frees all users + contained in the group */ +void mwSametimeGroup_free(struct mwSametimeGroup *g); + + +enum mwSametimeGroupType mwSametimeGroup_getType(struct mwSametimeGroup *g); + + +const char *mwSametimeGroup_getName(struct mwSametimeGroup *g); + + +void mwSametimeGroup_setAlias(struct mwSametimeGroup *g, + const char *alias); + + +const char *mwSametimeGroup_getAlias(struct mwSametimeGroup *g); + + +void mwSametimeGroup_setOpen(struct mwSametimeGroup *g, gboolean open); + + +gboolean mwSametimeGroup_isOpen(struct mwSametimeGroup *g); + + +struct mwSametimeList *mwSametimeGroup_getList(struct mwSametimeGroup *g); + + +/** Get a GList snapshot of the users in a list */ +GList *mwSametimeGroup_getUsers(struct mwSametimeGroup *g); + + +struct mwSametimeUser * +mwSametimeGroup_findUser(struct mwSametimeGroup *g, + struct mwIdBlock *user); + + +/** Create a user in a group */ +struct mwSametimeUser * +mwSametimeUser_new(struct mwSametimeGroup *g, + enum mwSametimeUserType type, + struct mwIdBlock *user); + + +/** Remove user from its group, and free it */ +void mwSametimeUser_free(struct mwSametimeUser *u); + + +struct mwSametimeGroup *mwSametimeUser_getGroup(struct mwSametimeUser *u); + + +enum mwSametimeUserType mwSametimeUser_getType(struct mwSametimeUser *u); + + +const char *mwSametimeUser_getUser(struct mwSametimeUser *u); + + +const char *mwSametimeUser_getCommunity(struct mwSametimeUser *u); + + +void mwSametimeUser_setShortName(struct mwSametimeUser *u, const char *name); + + +const char *mwSametimeUser_getShortName(struct mwSametimeUser *u); + + +void mwSametimeUser_setAlias(struct mwSametimeUser *u, const char *alias); + + +const char *mwSametimeUser_getAlias(struct mwSametimeUser *u); + + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MW_ST_LIST_H */ diff --git a/src/mw_util.c b/src/mw_util.c new file mode 100644 index 0000000..57c1845 --- /dev/null +++ b/src/mw_util.c @@ -0,0 +1,80 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "mw_util.h" + + +static void collect_keys(gpointer key, gpointer val, gpointer data) { + GList **list = data; + *list = g_list_append(*list, key); +} + + +GList *map_collect_keys(GHashTable *ht) { + GList *ret = NULL; + g_hash_table_foreach(ht, collect_keys, &ret); + return ret; +} + + +static void collect_values(gpointer key, gpointer val, gpointer data) { + GList **list = data; + *list = g_list_append(*list, val); +} + + +GList *map_collect_values(GHashTable *ht) { + GList *ret = NULL; + g_hash_table_foreach(ht, collect_values, &ret); + return ret; +} + + +struct mw_datum *mw_datum_new(gpointer data, GDestroyNotify clear) { + struct mw_datum *d = g_new(struct mw_datum, 1); + mw_datum_set(d, data, clear); + return d; +} + + +void mw_datum_set(struct mw_datum *d, gpointer data, GDestroyNotify clear) { + d->data = data; + d->clear = clear; +} + + +gpointer mw_datum_get(struct mw_datum *d) { + return d->data; +} + + +void mw_datum_clear(struct mw_datum *d) { + if(d->clear) { + d->clear(d->data); + d->clear = NULL; + } + d->data = NULL; +} + + +void mw_datum_free(struct mw_datum *d) { + mw_datum_clear(d); + g_free(d); +} diff --git a/src/mw_util.h b/src/mw_util.h new file mode 100644 index 0000000..b8e0b7e --- /dev/null +++ b/src/mw_util.h @@ -0,0 +1,85 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _MW_UTIL_H +#define _MW_UTIL_H + + +#include +#include +#include + + +#define map_guint_new() \ + g_hash_table_new(g_direct_hash, g_direct_equal) + + +#define map_guint_new_full(valfree) \ + g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (valfree)) + + +#define map_guint_insert(ht, key, val) \ + g_hash_table_insert((ht), GUINT_TO_POINTER((guint)(key)), (val)) + + +#define map_guint_replace(ht, key, val) \ + g_hash_table_replace((ht), GUINT_TO_POINTER((guint)(key)), (val)) + + +#define map_guint_lookup(ht, key) \ + g_hash_table_lookup((ht), GUINT_TO_POINTER((guint)(key))) + + +#define map_guint_remove(ht, key) \ + g_hash_table_remove((ht), GUINT_TO_POINTER((guint)(key))) + + +#define map_guint_steal(ht, key) \ + g_hash_table_steal((ht), GUINT_TO_POINTER((guint)(key))) + + +GList *map_collect_keys(GHashTable *ht); + + +GList *map_collect_values(GHashTable *ht); + + +struct mw_datum { + gpointer data; + GDestroyNotify clear; +}; + + +struct mw_datum *mw_datum_new(gpointer data, GDestroyNotify clear); + + +void mw_datum_set(struct mw_datum *d, gpointer data, GDestroyNotify clear); + + +gpointer mw_datum_get(struct mw_datum *d); + + +void mw_datum_clear(struct mw_datum *d); + + +void mw_datum_free(struct mw_datum *d); + + +#endif diff --git a/src/service.c b/src/service.c new file mode 100644 index 0000000..9ff8d86 --- /dev/null +++ b/src/service.c @@ -0,0 +1,267 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "mw_channel.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" + + +/* I tried to be explicit with the g_return_* calls, to make the debug + logging a bit more sensible. Hence all the explicit "foo != NULL" + checks. */ + + +void mwService_recvCreate(struct mwService *s, struct mwChannel *chan, + struct mwMsgChannelCreate *msg) { + + /* ensure none are null, ensure that the service and channel belong + to the same session, and ensure that the message belongs on the + channel */ + g_return_if_fail(s != NULL); + g_return_if_fail(chan != NULL); + g_return_if_fail(msg != NULL); + g_return_if_fail(s->session == mwChannel_getSession(chan)); + g_return_if_fail(mwChannel_getId(chan) == msg->channel); + + if(s->recv_create) { + s->recv_create(s, chan, msg); + } else { + mwChannel_destroy(chan, ERR_FAILURE, NULL); + } +} + + +void mwService_recvAccept(struct mwService *s, struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + + /* ensure none are null, ensure that the service and channel belong + to the same session, and ensure that the message belongs on the + channel */ + g_return_if_fail(s != NULL); + g_return_if_fail(chan != NULL); + g_return_if_fail(msg != NULL); + g_return_if_fail(s->session == mwChannel_getSession(chan)); + g_return_if_fail(mwChannel_getId(chan) == msg->head.channel); + + if(s->recv_accept) + s->recv_accept(s, chan, msg); +} + + +void mwService_recvDestroy(struct mwService *s, struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + + /* ensure none are null, ensure that the service and channel belong + to the same session, and ensure that the message belongs on the + channel */ + g_return_if_fail(s != NULL); + g_return_if_fail(chan != NULL); + g_return_if_fail(msg != NULL); + g_return_if_fail(s->session == mwChannel_getSession(chan)); + g_return_if_fail(mwChannel_getId(chan) == msg->head.channel); + + if(s->recv_destroy) + s->recv_destroy(s, chan, msg); +} + + +void mwService_recv(struct mwService *s, struct mwChannel *chan, + guint16 msg_type, struct mwOpaque *data) { + + /* ensure that none are null. zero-length messages are acceptable */ + g_return_if_fail(s != NULL); + g_return_if_fail(chan != NULL); + g_return_if_fail(data != NULL); + g_return_if_fail(s->session == mwChannel_getSession(chan)); + + /* + g_message("mwService_recv: session = %p, service = %p, b = %p, n = %u", + mwService_getSession(s), s, data->data, data->len); + */ + + if(s->recv) + s->recv(s, chan, msg_type, data); +} + + +guint32 mwService_getType(struct mwService *s) { + g_return_val_if_fail(s != NULL, 0x00); + return s->type; +} + + +const char *mwService_getName(struct mwService *s) { + g_return_val_if_fail(s != NULL, NULL); + g_return_val_if_fail(s->get_name != NULL, NULL); + + return s->get_name(s); +} + + +const char *mwService_getDesc(struct mwService *s) { + g_return_val_if_fail(s != NULL, NULL); + g_return_val_if_fail(s->get_desc != NULL, NULL); + + return s->get_desc(s); +} + + +struct mwSession *mwService_getSession(struct mwService *s) { + g_return_val_if_fail(s != NULL, NULL); + return s->session; +} + + +void mwService_init(struct mwService *srvc, struct mwSession *sess, + guint32 type) { + + /* ensure nothing is null, and there's no such thing as a zero + service type */ + g_return_if_fail(srvc != NULL); + g_return_if_fail(sess != NULL); + g_return_if_fail(type != 0x00); + + srvc->session = sess; + srvc->type = type; + srvc->state = mwServiceState_STOPPED; +} + + +enum mwServiceState mwService_getState(struct mwService *srvc) { + g_return_val_if_fail(srvc != NULL, mwServiceState_STOPPED); + return srvc->state; +} + + +void mwService_start(struct mwService *srvc) { + g_return_if_fail(srvc != NULL); + + if(! MW_SERVICE_IS_STOPPED(srvc)) + return; + + srvc->state = mwServiceState_STARTING; + g_message("starting service %s", NSTR(mwService_getName(srvc))); + + if(srvc->start) { + srvc->start(srvc); + } else { + mwService_started(srvc); + } +} + + +void mwService_started(struct mwService *srvc) { + g_return_if_fail(srvc != NULL); + + srvc->state = mwServiceState_STARTED; + g_message("started service %s", NSTR(mwService_getName(srvc))); +} + + +void mwService_error(struct mwService *srvc) { + g_return_if_fail(srvc != NULL); + + if(MW_SERVICE_IS_DEAD(srvc)) + return; + + srvc->state = mwServiceState_ERROR; + g_message("error in service %s", NSTR(mwService_getName(srvc))); + + if(srvc->stop) { + srvc->stop(srvc); + } else { + mwService_stopped(srvc); + } +} + + +void mwService_stop(struct mwService *srvc) { + g_return_if_fail(srvc != NULL); + + if(MW_SERVICE_IS_DEAD(srvc)) + return; + + srvc->state = mwServiceState_STOPPING; + g_message("stopping service %s", NSTR(mwService_getName(srvc))); + + if(srvc->stop) { + srvc->stop(srvc); + } else { + mwService_stopped(srvc); + } +} + + +void mwService_stopped(struct mwService *srvc) { + g_return_if_fail(srvc != NULL); + + if(srvc->state != mwServiceState_STOPPED) { + srvc->state = mwServiceState_STOPPED; + g_message("stopped service %s", NSTR(mwService_getName(srvc))); + } +} + + +void mwService_free(struct mwService *srvc) { + g_return_if_fail(srvc != NULL); + + mwService_stop(srvc); + + if(srvc->clear) + srvc->clear(srvc); + + if(srvc->client_cleanup) + srvc->client_cleanup(srvc->client_data); + + g_free(srvc); +} + + +/** @todo switch the following to using mw_datum */ + +void mwService_setClientData(struct mwService *srvc, + gpointer data, GDestroyNotify cleanup) { + + g_return_if_fail(srvc != NULL); + + srvc->client_data = data; + srvc->client_cleanup = cleanup; +} + + +gpointer mwService_getClientData(struct mwService *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->client_data; +} + + +void mwService_removeClientData(struct mwService *srvc) { + g_return_if_fail(srvc != NULL); + + if(srvc->client_cleanup) { + srvc->client_cleanup(srvc->client_data); + srvc->client_cleanup = NULL; + } + + srvc->client_data = NULL; +} + diff --git a/src/session.c b/src/session.c new file mode 100644 index 0000000..e95291f --- /dev/null +++ b/src/session.c @@ -0,0 +1,1206 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include + +#include "mw_channel.h" +#include "mw_cipher.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_util.h" + + +/** the hash table key for a service, for mwSession::services */ +#define SERVICE_KEY(srvc) mwService_getType(srvc) + +/** the hash table key for a cipher, for mwSession::ciphers */ +#define CIPHER_KEY(ciph) mwCipher_getType(ciph) + + +#define GPOINTER(val) (GUINT_TO_POINTER((guint) (val))) +#define GUINT(val) (GPOINTER_TO_UINT((val))) + + +struct mwSession { + + /** provides I/O and callback functions */ + struct mwSessionHandler *handler; + + enum mwSessionState state; /**< session state */ + gpointer state_info; /**< additional state info */ + + /* input buffering for an incoming message */ + guchar *buf; /**< buffer for incoming message data */ + gsize buf_len; /**< length of buf */ + gsize buf_used; /**< offset to last-used byte of buf */ + + struct mwLoginInfo login; /**< login information */ + struct mwUserStatus status; /**< user status */ + struct mwPrivacyInfo privacy; /**< privacy list */ + + /** the collection of channels */ + struct mwChannelSet *channels; + + /** the collection of services, keyed to guint32 service id */ + GHashTable *services; + + /** the collection of ciphers, keyed to guint16 cipher type */ + GHashTable *ciphers; + + /** arbitrary key:value pairs */ + GHashTable *attributes; + + /** optional user data */ + struct mw_datum client_data; +}; + + +static void property_set(struct mwSession *s, const char *key, + gpointer val, GDestroyNotify clean) { + + g_hash_table_insert(s->attributes, g_strdup(key), + mw_datum_new(val, clean)); +} + + +static gpointer property_get(struct mwSession *s, const char *key) { + struct mw_datum *p = g_hash_table_lookup(s->attributes, key); + return p? p->data: NULL; +} + + +static void property_del(struct mwSession *s, const char *key) { + g_hash_table_remove(s->attributes, key); +} + + +/** + set up the default properties for a newly created session +*/ +static void session_defaults(struct mwSession *s) { + property_set(s, mwSession_CLIENT_VER_MAJOR, + GPOINTER(MW_PROTOCOL_VERSION_MAJOR), NULL); + + property_set(s, mwSession_CLIENT_VER_MINOR, + GPOINTER(MW_PROTOCOL_VERSION_MINOR), NULL); + + property_set(s, mwSession_CLIENT_TYPE_ID, + GPOINTER(mwLogin_MEANWHILE), NULL); +} + + +struct mwSession *mwSession_new(struct mwSessionHandler *handler) { + struct mwSession *s; + + g_return_val_if_fail(handler != NULL, NULL); + + /* consider io_write and io_close to be absolute necessities */ + g_return_val_if_fail(handler->io_write != NULL, NULL); + g_return_val_if_fail(handler->io_close != NULL, NULL); + + s = g_new0(struct mwSession, 1); + + s->state = mwSession_STOPPED; + + s->handler = handler; + + s->channels = mwChannelSet_new(s); + s->services = map_guint_new(); + s->ciphers = map_guint_new(); + + s->attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + (GDestroyNotify) mw_datum_free); + + session_defaults(s); + + return s; +} + + +/** free and reset the session buffer */ +static void session_buf_free(struct mwSession *s) { + g_return_if_fail(s != NULL); + + g_free(s->buf); + s->buf = NULL; + s->buf_len = 0; + s->buf_used = 0; +} + + +/** a polite string version of the session state enum */ +static const char *state_str(enum mwSessionState state) { + switch(state) { + case mwSession_STARTING: return "starting"; + case mwSession_HANDSHAKE: return "handshake sent"; + case mwSession_HANDSHAKE_ACK: return "handshake acknowledged"; + case mwSession_LOGIN: return "login sent"; + case mwSession_LOGIN_REDIR: return "login redirected"; + case mwSession_LOGIN_CONT: return "forcing login"; + case mwSession_LOGIN_ACK: return "login acknowledged"; + case mwSession_STARTED: return "started"; + case mwSession_STOPPING: return "stopping"; + case mwSession_STOPPED: return "stopped"; + + case mwSession_UNKNOWN: /* fall-through */ + default: return "UNKNOWN"; + } +} + + +void mwSession_free(struct mwSession *s) { + struct mwSessionHandler *h; + + g_return_if_fail(s != NULL); + + if(! mwSession_isStopped(s)) { + g_debug("session is not stopped (state: %s), proceeding with free", + state_str(s->state)); + } + + h = s->handler; + if(h && h->clear) h->clear(s); + s->handler = NULL; + + session_buf_free(s); + + mwChannelSet_free(s->channels); + g_hash_table_destroy(s->services); + g_hash_table_destroy(s->ciphers); + g_hash_table_destroy(s->attributes); + + mwLoginInfo_clear(&s->login); + mwUserStatus_clear(&s->status); + mwPrivacyInfo_clear(&s->privacy); + + g_free(s); +} + + +/** write data to the session handler */ +static int io_write(struct mwSession *s, const guchar *buf, gsize len) { + g_return_val_if_fail(s != NULL, -1); + g_return_val_if_fail(s->handler != NULL, -1); + g_return_val_if_fail(s->handler->io_write != NULL, -1); + + return s->handler->io_write(s, buf, len); +} + + +/** close the session handler */ +static void io_close(struct mwSession *s) { + g_return_if_fail(s != NULL); + g_return_if_fail(s->handler != NULL); + g_return_if_fail(s->handler->io_close != NULL); + + s->handler->io_close(s); +} + + +static void state(struct mwSession *s, enum mwSessionState state, + gpointer info) { + + struct mwSessionHandler *sh; + + g_return_if_fail(s != NULL); + g_return_if_fail(s->handler != NULL); + + if(mwSession_isState(s, state)) return; + + s->state = state; + s->state_info = info; + + switch(state) { + case mwSession_STOPPING: + case mwSession_STOPPED: + g_message("session state: %s (0x%08x)", state_str(state), + GPOINTER_TO_UINT(info)); + break; + + case mwSession_LOGIN_REDIR: + g_message("session state: %s (%s)", state_str(state), + (char *)info); + break; + + default: + g_message("session state: %s", state_str(state)); + } + + sh = s->handler; + if(sh && sh->on_stateChange) + sh->on_stateChange(s, state, info); +} + + +void mwSession_start(struct mwSession *s) { + struct mwMsgHandshake *msg; + int ret; + + g_return_if_fail(s != NULL); + g_return_if_fail(mwSession_isStopped(s)); + + if(mwSession_isStarted(s) || mwSession_isStarting(s)) { + g_debug("attempted to start session that is already started/starting"); + return; + } + + state(s, mwSession_STARTING, 0); + + msg = (struct mwMsgHandshake *) mwMessage_new(mwMessage_HANDSHAKE); + msg->major = GUINT(property_get(s, mwSession_CLIENT_VER_MAJOR)); + msg->minor = GUINT(property_get(s, mwSession_CLIENT_VER_MINOR)); + msg->login_type = GUINT(property_get(s, mwSession_CLIENT_TYPE_ID)); + + msg->loclcalc_addr = GUINT(property_get(s, mwSession_CLIENT_IP)); + + if(msg->major >= 0x001e && msg->minor >= 0x001d) { + msg->unknown_a = 0x0100; + msg->local_host = property_get(s, mwSession_CLIENT_HOST); + } + + ret = mwSession_send(s, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); + + if(ret) { + mwSession_stop(s, CONNECTION_BROKEN); + } else { + state(s, mwSession_HANDSHAKE, 0); + } +} + + +void mwSession_stop(struct mwSession *s, guint32 reason) { + GList *list, *l = NULL; + struct mwMsgChannelDestroy *msg; + + g_return_if_fail(s != NULL); + + if(mwSession_isStopped(s) || mwSession_isStopping(s)) { + g_debug("attempted to stop session that is already stopped/stopping"); + return; + } + + state(s, mwSession_STOPPING, GUINT_TO_POINTER(reason)); + + for(list = l = mwSession_getServices(s); l; l = l->next) + mwService_stop(MW_SERVICE(l->data)); + g_list_free(list); + + msg = (struct mwMsgChannelDestroy *) + mwMessage_new(mwMessage_CHANNEL_DESTROY); + + msg->head.channel = MW_MASTER_CHANNEL_ID; + msg->reason = reason; + + /* don't care if this fails, we're closing the connection anyway */ + mwSession_send(s, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); + + session_buf_free(s); + + /* close the connection */ + io_close(s); + + state(s, mwSession_STOPPED, GUINT_TO_POINTER(reason)); +} + + +/** compose authentication information into an opaque based on the + password, encrypted via RC2/40 */ +static void compose_auth_rc2_40(struct mwOpaque *auth, const char *pass) { + guchar iv[8], key[5]; + struct mwOpaque a, b, z; + struct mwPutBuffer *p; + + /* get an IV and a random five-byte key */ + mwIV_init(iv); + mwKeyRandom(key, 5); + + /* the opaque with the key */ + a.len = 5; + a.data = key; + + /* the opaque to receive the encrypted pass */ + b.len = 0; + b.data = NULL; + + /* the plain-text pass dressed up as an opaque */ + z.len = strlen(pass); + z.data = (guchar *) pass; + + /* the opaque with the encrypted pass */ + mwEncrypt(a.data, a.len, iv, &z, &b); + + /* an opaque containing the other two opaques */ + p = mwPutBuffer_new(); + mwOpaque_put(p, &a); + mwOpaque_put(p, &b); + mwPutBuffer_finalize(auth, p); + + /* this is the only one to clear, as the other uses a static buffer */ + mwOpaque_clear(&b); +} + + +static void compose_auth_rc2_128(struct mwOpaque *auth, const char *pass, + guint32 magic, struct mwOpaque *rkey) { + + guchar iv[8]; + struct mwOpaque a, b, c; + struct mwPutBuffer *p; + + struct mwMpi *private, *public; + struct mwMpi *remote; + struct mwMpi *shared; + + private = mwMpi_new(); + public = mwMpi_new(); + remote = mwMpi_new(); + shared = mwMpi_new(); + + mwIV_init(iv); + + mwMpi_randDHKeypair(private, public); + mwMpi_import(remote, rkey); + mwMpi_calculateDHShared(shared, remote, private); + + /* put the password in opaque a */ + p = mwPutBuffer_new(); + guint32_put(p, magic); + mwString_put(p, pass); + mwPutBuffer_finalize(&a, p); + + /* put the shared key in opaque b */ + mwMpi_export(shared, &b); + + /* encrypt the password (a) using the shared key (b), put the result + in opaque c */ + mwEncrypt(b.data+(b.len-16), 16, iv, &a, &c); + + /* don't need the shared key anymore, re-use opaque (b) as the + export of the public key */ + mwOpaque_clear(&b); + mwMpi_export(public, &b); + + p = mwPutBuffer_new(); + guint16_put(p, 0x0001); /* XXX: unknown */ + mwOpaque_put(p, &b); + mwOpaque_put(p, &c); + mwPutBuffer_finalize(auth, p); + + mwOpaque_clear(&a); + mwOpaque_clear(&b); + mwOpaque_clear(&c); + + mwMpi_free(private); + mwMpi_free(public); + mwMpi_free(remote); + mwMpi_free(shared); +} + + +/** handle the receipt of a handshake_ack message by sending the login + message */ +static void HANDSHAKE_ACK_recv(struct mwSession *s, + struct mwMsgHandshakeAck *msg) { + struct mwMsgLogin *log; + int ret; + + g_return_if_fail(s != NULL); + g_return_if_fail(msg != NULL); + g_return_if_fail(mwSession_isState(s, mwSession_HANDSHAKE) || + mwSession_isState(s, mwSession_LOGIN_CONT)); + + if(mwSession_isState(s, mwSession_LOGIN_CONT)) { + /* this is a login continuation, don't re-send the login. We + should receive a login ack in a moment */ + + state(s, mwSession_HANDSHAKE_ACK, 0); + state(s, mwSession_LOGIN, 0); + return; + + } else { + state(s, mwSession_HANDSHAKE_ACK, 0); + } + + /* record the major/minor versions from the server */ + property_set(s, mwSession_SERVER_VER_MAJOR, GPOINTER(msg->major), NULL); + property_set(s, mwSession_SERVER_VER_MINOR, GPOINTER(msg->minor), NULL); + + /* compose the login message */ + log = (struct mwMsgLogin *) mwMessage_new(mwMessage_LOGIN); + log->login_type = GUINT(property_get(s, mwSession_CLIENT_TYPE_ID)); + log->name = g_strdup(property_get(s, mwSession_AUTH_USER_ID)); + + /** @todo default to password for now. later use token optionally */ + { + const char *pw; + pw = property_get(s, mwSession_AUTH_PASSWORD); + + if(msg->data.len >= 64) { + /* good login encryption */ + log->auth_type = mwAuthType_RC2_128; + compose_auth_rc2_128(&log->auth_data, pw, msg->magic, &msg->data); + + } else { + /* BAD login encryption */ + log->auth_type = mwAuthType_RC2_40; + compose_auth_rc2_40(&log->auth_data, pw); + } + } + + /* send the login message */ + ret = mwSession_send(s, MW_MESSAGE(log)); + mwMessage_free(MW_MESSAGE(log)); + + if(! ret) { + /* sent login OK, set state appropriately */ + state(s, mwSession_LOGIN, 0); + } +} + + +/** handle the receipt of a login_ack message. This completes the + startup sequence for the session */ +static void LOGIN_ACK_recv(struct mwSession *s, + struct mwMsgLoginAck *msg) { + GList *ll, *l; + + g_return_if_fail(s != NULL); + g_return_if_fail(msg != NULL); + g_return_if_fail(mwSession_isState(s, mwSession_LOGIN)); + + /* store the login information in the session */ + mwLoginInfo_clear(&s->login); + mwLoginInfo_clone(&s->login, &msg->login); + + state(s, mwSession_LOGIN_ACK, 0); + + /* start up our services */ + for(ll = l = mwSession_getServices(s); l; l = l->next) { + mwService_start(l->data); + } + g_list_free(ll); + + /* @todo any further startup stuff? */ + + state(s, mwSession_STARTED, 0); +} + + +static void CHANNEL_CREATE_recv(struct mwSession *s, + struct mwMsgChannelCreate *msg) { + struct mwChannel *chan; + chan = mwChannel_newIncoming(s->channels, msg->channel); + + /* hand off to channel */ + mwChannel_recvCreate(chan, msg); +} + + +static void CHANNEL_ACCEPT_recv(struct mwSession *s, + struct mwMsgChannelAccept *msg) { + struct mwChannel *chan; + chan = mwChannel_find(s->channels, msg->head.channel); + + g_return_if_fail(chan != NULL); + + /* hand off to channel */ + mwChannel_recvAccept(chan, msg); +} + + +static void CHANNEL_DESTROY_recv(struct mwSession *s, + struct mwMsgChannelDestroy *msg) { + + /* the server can indicate that we should close the session by + destroying the zero channel */ + if(msg->head.channel == MW_MASTER_CHANNEL_ID) { + mwSession_stop(s, msg->reason); + + } else { + struct mwChannel *chan; + chan = mwChannel_find(s->channels, msg->head.channel); + + /* we don't have any such channel... so I guess we destroyed it. + This is to remove a warning from timing errors when two clients + both try to close a channel at about the same time. */ + if(! chan) return; + + /* hand off to channel */ + mwChannel_recvDestroy(chan, msg); + } +} + + +static void CHANNEL_SEND_recv(struct mwSession *s, + struct mwMsgChannelSend *msg) { + struct mwChannel *chan; + chan = mwChannel_find(s->channels, msg->head.channel); + + /* if we don't have any such channel, we're certainly not going to + accept data from it */ + if(! chan) return; + + /* hand off to channel */ + mwChannel_recv(chan, msg); +} + + +static void SET_PRIVACY_LIST_recv(struct mwSession *s, + struct mwMsgSetPrivacyList *msg) { + struct mwSessionHandler *sh = s->handler; + + g_info("SET_PRIVACY_LIST"); + + mwPrivacyInfo_clear(&s->privacy); + mwPrivacyInfo_clone(&s->privacy, &msg->privacy); + + if(sh && sh->on_setPrivacyInfo) + sh->on_setPrivacyInfo(s); +} + + +static void SET_USER_STATUS_recv(struct mwSession *s, + struct mwMsgSetUserStatus *msg) { + struct mwSessionHandler *sh = s->handler; + + mwUserStatus_clear(&s->status); + mwUserStatus_clone(&s->status, &msg->status); + + if(sh && sh->on_setUserStatus) + sh->on_setUserStatus(s); +} + + +static void SENSE_SERVICE_recv(struct mwSession *s, + struct mwMsgSenseService *msg) { + struct mwService *srvc; + + srvc = mwSession_getService(s, msg->service); + if(srvc) mwService_start(srvc); +} + + +static void ADMIN_recv(struct mwSession *s, struct mwMsgAdmin *msg) { + struct mwSessionHandler *sh = s->handler; + + if(sh && sh->on_admin) + sh->on_admin(s, msg->text); +} + + +static void ANNOUNCE_recv(struct mwSession *s, struct mwMsgAnnounce *msg) { + struct mwSessionHandler *sh = s->handler; + + if(sh && sh->on_announce) + sh->on_announce(s, &msg->sender, msg->may_reply, msg->text); +} + + +static void LOGIN_REDIRECT_recv(struct mwSession *s, + struct mwMsgLoginRedirect *msg) { + + state(s, mwSession_LOGIN_REDIR, msg->host); +} + + +#define CASE(var, type) \ +case mwMessage_ ## var: \ + var ## _recv(s, (struct type *) msg); \ + break; + + +static void session_process(struct mwSession *s, + const guchar *buf, gsize len) { + + struct mwOpaque o = { .len = len, .data = (guchar *) buf }; + struct mwGetBuffer *b; + struct mwMessage *msg; + + g_return_if_fail(s != NULL); + g_return_if_fail(buf != NULL); + + /* ignore zero-length messages */ + if(len == 0) return; + + /* wrap up buf */ + b = mwGetBuffer_wrap(&o); + + /* attempt to parse the message. */ + msg = mwMessage_get(b); + + if(mwGetBuffer_error(b)) { + mw_mailme_opaque(&o, "parsing of message failed"); + } + + mwGetBuffer_free(b); + + g_return_if_fail(msg != NULL); + + /* handle each of the appropriate incoming types of mwMessage */ + switch(msg->type) { + CASE(HANDSHAKE_ACK, mwMsgHandshakeAck); + CASE(LOGIN_REDIRECT, mwMsgLoginRedirect); + CASE(LOGIN_ACK, mwMsgLoginAck); + CASE(CHANNEL_CREATE, mwMsgChannelCreate); + CASE(CHANNEL_DESTROY, mwMsgChannelDestroy); + CASE(CHANNEL_SEND, mwMsgChannelSend); + CASE(CHANNEL_ACCEPT, mwMsgChannelAccept); + CASE(SET_PRIVACY_LIST, mwMsgSetPrivacyList); + CASE(SET_USER_STATUS, mwMsgSetUserStatus); + CASE(SENSE_SERVICE, mwMsgSenseService); + CASE(ADMIN, mwMsgAdmin); + CASE(ANNOUNCE, mwMsgAnnounce); + + default: + g_warning("unknown message type 0x%04x, no handler", msg->type); + } + + mwMessage_free(msg); +} + + +#undef CASE + + +#define ADVANCE(b, n, count) { b += count; n -= count; } + + +/* handle input to complete an existing buffer */ +static gsize session_recv_cont(struct mwSession *s, + const guchar *b, gsize n) { + + /* determine how many bytes still required */ + gsize x = s->buf_len - s->buf_used; + + /* g_message(" session_recv_cont: session = %p, b = %p, n = %u", + s, b, n); */ + + if(n < x) { + /* not quite enough; still need some more */ + memcpy(s->buf+s->buf_used, b, n); + s->buf_used += n; + return 0; + + } else { + /* enough to finish the buffer, at least */ + memcpy(s->buf+s->buf_used, b, x); + ADVANCE(b, n, x); + + if(s->buf_len == 4) { + /* if only the length bytes were being buffered, we'll now try + to complete an actual message */ + + struct mwOpaque o = { 4, s->buf }; + struct mwGetBuffer *gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + + if(n < x) { + /* there isn't enough to meet the demands of the length, so + we'll buffer it for next time */ + + guchar *t; + x += 4; + t = (guchar *) g_malloc(x); + memcpy(t, s->buf, 4); + memcpy(t+4, b, n); + + session_buf_free(s); + + s->buf = t; + s->buf_len = x; + s->buf_used = n + 4; + return 0; + + } else { + /* there's enough (maybe more) for a full message. don't need + the old session buffer (which recall, was only the length + bytes) any more */ + + session_buf_free(s); + session_process(s, b, x); + ADVANCE(b, n, x); + } + + } else { + /* process the now-complete buffer. remember to skip the first + four bytes, since they're just the size count */ + session_process(s, s->buf+4, s->buf_len-4); + session_buf_free(s); + } + } + + return n; +} + + +/* handle input when there's nothing previously buffered */ +static gsize session_recv_empty(struct mwSession *s, + const guchar *b, gsize n) { + + struct mwOpaque o = { n, (guchar *) b }; + struct mwGetBuffer *gb; + gsize x; + + if(n < 4) { + /* uh oh. less than four bytes means we've got an incomplete + length indicator. Have to buffer to get the rest of it. */ + s->buf = (guchar *) g_malloc0(4); + memcpy(s->buf, b, n); + s->buf_len = 4; + s->buf_used = n; + return 0; + } + + /* peek at the length indicator. if it's a zero length message, + don't process, just skip it */ + gb = mwGetBuffer_wrap(&o); + x = guint32_peek(gb); + mwGetBuffer_free(gb); + if(! x) return n - 4; + + if(n < (x + 4)) { + /* if the total amount of data isn't enough to cover the length + bytes and the length indicated by those bytes, then we'll need + to buffer. This is where the DOS mentioned below in + session_recv takes place */ + + x += 4; + s->buf = (guchar *) g_malloc(x); + memcpy(s->buf, b, n); + s->buf_len = x; + s->buf_used = n; + return 0; + + } else { + /* advance past length bytes */ + ADVANCE(b, n, 4); + + /* process and advance */ + session_process(s, b, x); + ADVANCE(b, n, x); + + /* return left-over count */ + return n; + } +} + + +static gsize session_recv(struct mwSession *s, + const guchar *b, gsize n) { + + /* This is messy and kind of confusing. I'd like to simplify it at + some point, but the constraints are as follows: + + - buffer up to a single full message on the session buffer + - buffer must contain the four length bytes + - the four length bytes indicate how much we'll need to buffer + - the four length bytes might not arrive all at once, so it's + possible that we'll need to buffer to get them. + - since our buffering includes the length bytes, we know we + still have an incomplete length if the buffer length is only + four. */ + + /** @todo we should allow a compiled-in upper limit to message + sizes, and just drop messages over that size. However, to do that + we'd need to keep track of the size of a message and keep + dropping bytes until we'd fulfilled the entire length. eg: if we + receive a message size of 10MB, we need to pass up exactly 10MB + before it's safe to start processing the rest as a new + message. As it stands, a malicious packet from the server can run + us out of memory by indicating it's going to send us some + obscenely long message (even if it never actually sends it) */ + + /* g_message(" session_recv: session = %p, b = %p, n = %u", + s, b, n); */ + + if(s->buf_len == 0) { + while(n && (*b & 0x80)) { + /* keep-alive and series bytes are ignored */ + ADVANCE(b, n, 1); + } + } + + if(n == 0) { + return 0; + + } else if(s->buf_len > 0) { + return session_recv_cont(s, b, n); + + } else { + return session_recv_empty(s, b, n); + } +} + + +#undef ADVANCE + + +void mwSession_recv(struct mwSession *s, const guchar *buf, gsize n) { + guchar *b = (guchar *) buf; + gsize remain = 0; + + g_return_if_fail(s != NULL); + + while(n > 0) { + remain = session_recv(s, b, n); + b += (n - remain); + n = remain; + } +} + + +int mwSession_send(struct mwSession *s, struct mwMessage *msg) { + struct mwPutBuffer *b; + struct mwOpaque o; + int ret = 0; + + g_return_val_if_fail(s != NULL, -1); + + /* writing nothing is easy */ + if(! msg) return 0; + + /* first we render the message into an opaque */ + b = mwPutBuffer_new(); + mwMessage_put(b, msg); + mwPutBuffer_finalize(&o, b); + + /* then we render the opaque into... another opaque! */ + b = mwPutBuffer_new(); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + mwPutBuffer_finalize(&o, b); + + /* then we use that opaque's data and length to write to the socket */ + ret = io_write(s, o.data, o.len); + mwOpaque_clear(&o); + + /* ensure we could actually write the message */ + if(! ret) { + + /* special case, as the server doesn't always respond to user + status messages. Thus, we trigger the event when we send the + messages as well as when we receive them */ + if(msg->type == mwMessage_SET_USER_STATUS) { + SET_USER_STATUS_recv(s, (struct mwMsgSetUserStatus *) msg); + } + } + + return ret; +} + + +int mwSession_sendKeepalive(struct mwSession *s) { + const guchar b = 0x80; + + g_return_val_if_fail(s != NULL, -1); + return io_write(s, &b, 1); +} + + +int mwSession_forceLogin(struct mwSession *s) { + struct mwMsgLoginContinue *msg; + int ret; + + g_return_val_if_fail(s != NULL, -1); + g_return_val_if_fail(mwSession_isState(s, mwSession_LOGIN_REDIR), -1); + + state(s, mwSession_LOGIN_CONT, 0x00); + + msg = (struct mwMsgLoginContinue *) + mwMessage_new(mwMessage_LOGIN_CONTINUE); + + ret = mwSession_send(s, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); + + return ret; +} + + +int mwSession_sendAnnounce(struct mwSession *s, gboolean may_reply, + const char *text, const GList *recipients) { + + struct mwMsgAnnounce *msg; + int ret; + + g_return_val_if_fail(s != NULL, -1); + g_return_val_if_fail(mwSession_isStarted(s), -1); + + msg = (struct mwMsgAnnounce *) mwMessage_new(mwMessage_ANNOUNCE); + + msg->recipients = (GList *) recipients; + msg->may_reply = may_reply; + msg->text = g_strdup(text); + + ret = mwSession_send(s, MW_MESSAGE(msg)); + + msg->recipients = NULL; /* don't kill our recipients param */ + mwMessage_free(MW_MESSAGE(msg)); + + return ret; +} + + +struct mwSessionHandler *mwSession_getHandler(struct mwSession *s) { + g_return_val_if_fail(s != NULL, NULL); + return s->handler; +} + + +struct mwLoginInfo *mwSession_getLoginInfo(struct mwSession *s) { + g_return_val_if_fail(s != NULL, NULL); + return &s->login; +} + + +int mwSession_setPrivacyInfo(struct mwSession *s, + struct mwPrivacyInfo *privacy) { + + struct mwMsgSetPrivacyList *msg; + int ret; + + g_return_val_if_fail(s != NULL, -1); + g_return_val_if_fail(privacy != NULL, -1); + + msg = (struct mwMsgSetPrivacyList *) + mwMessage_new(mwMessage_SET_PRIVACY_LIST); + + mwPrivacyInfo_clone(&msg->privacy, privacy); + + ret = mwSession_send(s, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); + + return ret; +} + + +struct mwPrivacyInfo *mwSession_getPrivacyInfo(struct mwSession *s) { + g_return_val_if_fail(s != NULL, NULL); + return &s->privacy; +} + + +int mwSession_setUserStatus(struct mwSession *s, + struct mwUserStatus *stat) { + + struct mwMsgSetUserStatus *msg; + int ret; + + g_return_val_if_fail(s != NULL, -1); + g_return_val_if_fail(stat != NULL, -1); + + msg = (struct mwMsgSetUserStatus *) + mwMessage_new(mwMessage_SET_USER_STATUS); + + mwUserStatus_clone(&msg->status, stat); + + ret = mwSession_send(s, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); + + return ret; +} + + +struct mwUserStatus *mwSession_getUserStatus(struct mwSession *s) { + g_return_val_if_fail(s != NULL, NULL); + return &s->status; +} + + +enum mwSessionState mwSession_getState(struct mwSession *s) { + g_return_val_if_fail(s != NULL, mwSession_UNKNOWN); + return s->state; +} + + +gpointer mwSession_getStateInfo(struct mwSession *s) { + g_return_val_if_fail(s != NULL, 0); + return s->state_info; +} + + +struct mwChannelSet *mwSession_getChannels(struct mwSession *session) { + g_return_val_if_fail(session != NULL, NULL); + return session->channels; +} + + +gboolean mwSession_addService(struct mwSession *s, struct mwService *srv) { + g_return_val_if_fail(s != NULL, FALSE); + g_return_val_if_fail(srv != NULL, FALSE); + g_return_val_if_fail(s->services != NULL, FALSE); + + if(map_guint_lookup(s->services, SERVICE_KEY(srv))) { + return FALSE; + + } else { + map_guint_insert(s->services, SERVICE_KEY(srv), srv); + if(mwSession_isState(s, mwSession_STARTED)) + mwSession_senseService(s, mwService_getType(srv)); + return TRUE; + } +} + + +struct mwService *mwSession_getService(struct mwSession *s, guint32 srv) { + g_return_val_if_fail(s != NULL, NULL); + g_return_val_if_fail(s->services != NULL, NULL); + + return map_guint_lookup(s->services, srv); +} + + +struct mwService *mwSession_removeService(struct mwSession *s, guint32 srv) { + struct mwService *svc; + + g_return_val_if_fail(s != NULL, NULL); + g_return_val_if_fail(s->services != NULL, NULL); + + svc = map_guint_lookup(s->services, srv); + if(svc) map_guint_remove(s->services, srv); + return svc; +} + + +GList *mwSession_getServices(struct mwSession *s) { + g_return_val_if_fail(s != NULL, NULL); + g_return_val_if_fail(s->services != NULL, NULL); + + return map_collect_values(s->services); +} + + +void mwSession_senseService(struct mwSession *s, guint32 srvc) { + struct mwMsgSenseService *msg; + + g_return_if_fail(s != NULL); + g_return_if_fail(srvc != 0x00); + g_return_if_fail(mwSession_isStarted(s)); + + msg = (struct mwMsgSenseService *) + mwMessage_new(mwMessage_SENSE_SERVICE); + msg->service = srvc; + + mwSession_send(s, MW_MESSAGE(msg)); + mwMessage_free(MW_MESSAGE(msg)); +} + + +gboolean mwSession_addCipher(struct mwSession *s, struct mwCipher *c) { + g_return_val_if_fail(s != NULL, FALSE); + g_return_val_if_fail(c != NULL, FALSE); + g_return_val_if_fail(s->ciphers != NULL, FALSE); + + if(map_guint_lookup(s->ciphers, mwCipher_getType(c))) { + g_message("cipher %s is already added, apparently", + NSTR(mwCipher_getName(c))); + return FALSE; + + } else { + g_message("adding cipher %s", NSTR(mwCipher_getName(c))); + map_guint_insert(s->ciphers, mwCipher_getType(c), c); + return TRUE; + } +} + + +struct mwCipher *mwSession_getCipher(struct mwSession *s, guint16 c) { + g_return_val_if_fail(s != NULL, NULL); + g_return_val_if_fail(s->ciphers != NULL, NULL); + + return map_guint_lookup(s->ciphers, c); +} + + +struct mwCipher *mwSession_removeCipher(struct mwSession *s, guint16 c) { + struct mwCipher *ciph; + + g_return_val_if_fail(s != NULL, NULL); + g_return_val_if_fail(s->ciphers != NULL, NULL); + + ciph = map_guint_lookup(s->ciphers, c); + if(ciph) map_guint_remove(s->ciphers, c); + return ciph; +} + + +GList *mwSession_getCiphers(struct mwSession *s) { + g_return_val_if_fail(s != NULL, NULL); + g_return_val_if_fail(s->ciphers != NULL, NULL); + + return map_collect_values(s->ciphers); +} + + +void mwSession_setProperty(struct mwSession *s, const char *key, + gpointer val, GDestroyNotify clean) { + + g_return_if_fail(s != NULL); + g_return_if_fail(s->attributes != NULL); + g_return_if_fail(key != NULL); + + property_set(s, key, val, clean); +} + + +gpointer mwSession_getProperty(struct mwSession *s, const char *key) { + + g_return_val_if_fail(s != NULL, NULL); + g_return_val_if_fail(s->attributes != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + + return property_get(s, key); +} + + +void mwSession_removeProperty(struct mwSession *s, const char *key) { + g_return_if_fail(s != NULL); + g_return_if_fail(s->attributes != NULL); + g_return_if_fail(key != NULL); + + property_del(s, key); +} + + +void mwSession_setClientData(struct mwSession *session, + gpointer data, GDestroyNotify clear) { + + g_return_if_fail(session != NULL); + mw_datum_set(&session->client_data, data, clear); +} + + +gpointer mwSession_getClientData(struct mwSession *session) { + g_return_val_if_fail(session != NULL, NULL); + return mw_datum_get(&session->client_data); +} + + +void mwSession_removeClientData(struct mwSession *session) { + g_return_if_fail(session != NULL); + mw_datum_clear(&session->client_data); +} + diff --git a/src/srvc_aware.c b/src/srvc_aware.c new file mode 100644 index 0000000..869f973 --- /dev/null +++ b/src/srvc_aware.c @@ -0,0 +1,1319 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include + +#include "mw_channel.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_srvc_aware.h" +#include "mw_util.h" + + +struct mwServiceAware { + struct mwService service; + + struct mwAwareHandler *handler; + + /** map of ENTRY_KEY(aware_entry):aware_entry */ + GHashTable *entries; + + /** set of guint32:attrib_watch_entry attribute keys */ + GHashTable *attribs; + + /** collection of lists of awareness for this service. Each item is + a mwAwareList */ + GList *lists; + + /** the buddy list channel */ + struct mwChannel *channel; +}; + + +struct mwAwareList { + + /** the owning service */ + struct mwServiceAware *service; + + /** map of ENTRY_KEY(aware_entry):aware_entry */ + GHashTable *entries; + + /** set of guint32:attrib_watch_entry attribute keys */ + GHashTable *attribs; + + struct mwAwareListHandler *handler; + struct mw_datum client_data; +}; + + +struct mwAwareAttribute { + guint32 key; + struct mwOpaque data; +}; + + +struct attrib_entry { + guint32 key; + GList *membership; +}; + + +/** an actual awareness entry, belonging to any number of aware lists */ +struct aware_entry { + struct mwAwareSnapshot aware; + + /** list of mwAwareList containing this entry */ + GList *membership; + + /** collection of attribute values for this entry. + map of ATTRIB_KEY(mwAwareAttribute):mwAwareAttribute */ + GHashTable *attribs; +}; + + +#define ENTRY_KEY(entry) &entry->aware.id + + +/** the channel send types used by this service */ +enum msg_types { + msg_AWARE_ADD = 0x0068, /**< remove an aware */ + msg_AWARE_REMOVE = 0x0069, /**< add an aware */ + + msg_OPT_DO_SET = 0x00c9, /**< set an attribute */ + msg_OPT_DO_UNSET = 0x00ca, /**< unset an attribute */ + msg_OPT_WATCH = 0x00cb, /**< set the attribute watch list */ + + msg_AWARE_SNAPSHOT = 0x01f4, /**< recv aware snapshot */ + msg_AWARE_UPDATE = 0x01f5, /**< recv aware update */ + msg_AWARE_GROUP = 0x01f6, /**< recv group aware */ + + msg_OPT_GOT_SET = 0x0259, /**< recv attribute set update */ + msg_OPT_GOT_UNSET = 0x025a, /**< recv attribute unset update */ + + msg_OPT_GOT_UNKNOWN = 0x025b, /**< UNKNOWN */ + + msg_OPT_DID_SET = 0x025d, /**< attribute set response */ + msg_OPT_DID_UNSET = 0x025e, /**< attribute unset response */ + msg_OPT_DID_ERROR = 0x025f, /**< attribute set/unset error */ +}; + + +static void aware_entry_free(struct aware_entry *ae) { + mwAwareSnapshot_clear(&ae->aware); + g_list_free(ae->membership); + g_hash_table_destroy(ae->attribs); + g_free(ae); +} + + +static void attrib_entry_free(struct attrib_entry *ae) { + g_list_free(ae->membership); + g_free(ae); +} + + +static void attrib_free(struct mwAwareAttribute *attrib) { + mwOpaque_clear(&attrib->data); + g_free(attrib); +} + + +static struct aware_entry *aware_find(struct mwServiceAware *srvc, + struct mwAwareIdBlock *srch) { + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(srvc->entries != NULL, NULL); + g_return_val_if_fail(srch != NULL, NULL); + + return g_hash_table_lookup(srvc->entries, srch); +} + + +static struct aware_entry *list_aware_find(struct mwAwareList *list, + struct mwAwareIdBlock *srch) { + g_return_val_if_fail(list != NULL, NULL); + g_return_val_if_fail(list->entries != NULL, NULL); + g_return_val_if_fail(srch != NULL, NULL); + + return g_hash_table_lookup(list->entries, srch); +} + + +static void compose_list(struct mwPutBuffer *b, GList *id_list) { + guint32_put(b, g_list_length(id_list)); + for(; id_list; id_list = id_list->next) + mwAwareIdBlock_put(b, id_list->data); +} + + +static int send_add(struct mwChannel *chan, GList *id_list) { + struct mwPutBuffer *b = mwPutBuffer_new(); + struct mwOpaque o; + int ret; + + g_return_val_if_fail(chan != NULL, 0); + + compose_list(b, id_list); + + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(chan, msg_AWARE_ADD, &o); + mwOpaque_clear(&o); + + return ret; +} + + +static int send_rem(struct mwChannel *chan, GList *id_list) { + struct mwPutBuffer *b = mwPutBuffer_new(); + struct mwOpaque o; + int ret; + + g_return_val_if_fail(chan != NULL, 0); + + compose_list(b, id_list); + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(chan, msg_AWARE_REMOVE, &o); + mwOpaque_clear(&o); + + return ret; +} + + +static gboolean collect_dead(gpointer key, gpointer val, gpointer data) { + struct aware_entry *aware = val; + GList **dead = data; + + if(aware->membership == NULL) { + g_info(" removing %s, %s", + NSTR(aware->aware.id.user), NSTR(aware->aware.id.community)); + *dead = g_list_append(*dead, aware); + return TRUE; + + } else { + return FALSE; + } +} + + +static int remove_unused(struct mwServiceAware *srvc) { + /* - create a GList of all the unused aware entries + - remove each unused aware from the service + - if the service is alive, send a removal message for the collected + unused. + */ + + int ret = 0; + GList *dead = NULL, *l; + + if(srvc->entries) { + g_info("bring out your dead *clang*"); + g_hash_table_foreach_steal(srvc->entries, collect_dead, &dead); + } + + if(dead) { + if(MW_SERVICE_IS_LIVE(srvc)) + ret = send_rem(srvc->channel, dead) || ret; + + for(l = dead; l; l = l->next) + aware_entry_free(l->data); + + g_list_free(dead); + } + + return ret; +} + + +static int send_attrib_list(struct mwServiceAware *srvc) { + struct mwPutBuffer *b; + struct mwOpaque o; + + int tmp; + GList *l; + + g_return_val_if_fail(srvc != NULL, -1); + g_return_val_if_fail(srvc->channel != NULL, 0); + + l = map_collect_keys(srvc->attribs); + tmp = g_list_length(l); + + b = mwPutBuffer_new(); + guint32_put(b, 0x00); + guint32_put(b, tmp); + + for(; l; l = g_list_delete_link(l, l)) { + guint32_put(b, GPOINTER_TO_UINT(l->data)); + } + + mwPutBuffer_finalize(&o, b); + tmp = mwChannel_send(srvc->channel, msg_OPT_WATCH, &o); + mwOpaque_clear(&o); + + return tmp; +} + + +static gboolean collect_attrib_dead(gpointer key, gpointer val, + gpointer data) { + + struct attrib_entry *attrib = val; + GList **dead = data; + + if(attrib->membership == NULL) { + g_info(" removing 0x%08x", GPOINTER_TO_UINT(key)); + *dead = g_list_append(*dead, attrib); + return TRUE; + + } else { + return FALSE; + } +} + + +static int remove_unused_attrib(struct mwServiceAware *srvc) { + GList *dead = NULL; + + if(srvc->attribs) { + g_info("collecting dead attributes"); + g_hash_table_foreach_steal(srvc->attribs, collect_attrib_dead, &dead); + } + + /* since we stole them, we'll have to clean 'em up manually */ + for(; dead; dead = g_list_delete_link(dead, dead)) { + attrib_entry_free(dead->data); + } + + return MW_SERVICE_IS_LIVE(srvc)? send_attrib_list(srvc): 0; +} + + +static void recv_accept(struct mwServiceAware *srvc, + struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + + g_return_if_fail(srvc->channel != NULL); + g_return_if_fail(srvc->channel == chan); + + if(MW_SERVICE_IS_STARTING(MW_SERVICE(srvc))) { + GList *list = NULL; + + list = map_collect_values(srvc->entries); + send_add(chan, list); + g_list_free(list); + + send_attrib_list(srvc); + + mwService_started(MW_SERVICE(srvc)); + + } else { + mwChannel_destroy(chan, ERR_FAILURE, NULL); + } +} + + +static void recv_destroy(struct mwServiceAware *srvc, + struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + + srvc->channel = NULL; + mwService_stop(MW_SERVICE(srvc)); + + /** @todo session sense service and mwService_start */ +} + + +/** called from SNAPSHOT_recv, UPDATE_recv, and + mwServiceAware_setStatus */ +static void status_recv(struct mwServiceAware *srvc, + struct mwAwareSnapshot *idb) { + + struct aware_entry *aware; + GList *l; + + aware = aware_find(srvc, &idb->id); + + if(! aware) { + /* we don't deal with receiving status for something we're not + monitoring, but it will happen sometimes, eg from manually set + status */ + return; + } + + /* clear the existing status, then clone in the new status */ + mwAwareSnapshot_clear(&aware->aware); + mwAwareSnapshot_clone(&aware->aware, idb); + + /* trigger each of the entry's lists */ + for(l = aware->membership; l; l = l->next) { + struct mwAwareList *alist = l->data; + struct mwAwareListHandler *handler = alist->handler; + + if(handler && handler->on_aware) + handler->on_aware(alist, idb); + } +} + + +static void attrib_recv(struct mwServiceAware *srvc, + struct mwAwareIdBlock *idb, + struct mwAwareAttribute *attrib) { + + struct aware_entry *aware; + struct mwAwareAttribute *old_attrib = NULL; + GList *l; + guint32 key; + gpointer k; + + aware = aware_find(srvc, idb); + g_return_if_fail(aware != NULL); + + key = attrib->key; + k = GUINT_TO_POINTER(key); + + if(aware->attribs) + old_attrib = g_hash_table_lookup(aware->attribs, k); + + if(! old_attrib) { + old_attrib = g_new0(struct mwAwareAttribute, 1); + old_attrib->key = key; + g_hash_table_insert(aware->attribs, k, old_attrib); + } + + mwOpaque_clear(&old_attrib->data); + mwOpaque_clone(&old_attrib->data, &attrib->data); + + for(l = aware->membership; l; l = l->next) { + struct mwAwareList *list = l->data; + struct mwAwareListHandler *h = list->handler; + + if(h && h->on_attrib && + list->attribs && g_hash_table_lookup(list->attribs, k)) + + h->on_attrib(list, idb, old_attrib); + } +} + + +static gboolean list_add(struct mwAwareList *list, + struct mwAwareIdBlock *id) { + + struct mwServiceAware *srvc = list->service; + struct aware_entry *aware; + + g_return_val_if_fail(id->user != NULL, FALSE); + g_return_val_if_fail(strlen(id->user) > 0, FALSE); + + if(! list->entries) + list->entries = g_hash_table_new((GHashFunc) mwAwareIdBlock_hash, + (GEqualFunc) mwAwareIdBlock_equal); + + aware = list_aware_find(list, id); + if(aware) return FALSE; + + aware = aware_find(srvc, id); + if(! aware) { + aware = g_new0(struct aware_entry, 1); + aware->attribs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, + (GDestroyNotify) attrib_free); + mwAwareIdBlock_clone(ENTRY_KEY(aware), id); + + g_hash_table_insert(srvc->entries, ENTRY_KEY(aware), aware); + } + + aware->membership = g_list_append(aware->membership, list); + + g_hash_table_insert(list->entries, ENTRY_KEY(aware), aware); + + return TRUE; +} + + +static void group_member_recv(struct mwServiceAware *srvc, + struct mwAwareSnapshot *idb) { + /* @todo + - look up group by id + - find each list group belongs to + - add user to lists + */ + + struct mwAwareIdBlock gsrch = { mwAware_GROUP, idb->group, NULL }; + struct aware_entry *grp; + GList *l, *m; + + grp = aware_find(srvc, &gsrch); + g_return_if_fail(grp != NULL); /* this could happen, with timing. */ + + l = g_list_prepend(NULL, &idb->id); + + for(m = grp->membership; m; m = m->next) { + + /* if we just list_add, we won't receive updates for attributes, + so annoyingly we have to turn around and send out an add aware + message for each incoming group member */ + + /* list_add(m->data, &idb->id); */ + mwAwareList_addAware(m->data, l); + } + + g_list_free(l); +} + + +static void recv_SNAPSHOT(struct mwServiceAware *srvc, + struct mwGetBuffer *b) { + + guint32 count; + + struct mwAwareSnapshot *snap; + snap = g_new0(struct mwAwareSnapshot, 1); + + guint32_get(b, &count); + + while(count--) { + mwAwareSnapshot_get(b, snap); + + if(mwGetBuffer_error(b)) { + mwAwareSnapshot_clear(snap); + break; + } + + if(snap->group) + group_member_recv(srvc, snap); + + status_recv(srvc, snap); + mwAwareSnapshot_clear(snap); + } + + g_free(snap); +} + + +static void recv_UPDATE(struct mwServiceAware *srvc, + struct mwGetBuffer *b) { + + struct mwAwareSnapshot *snap; + + snap = g_new0(struct mwAwareSnapshot, 1); + mwAwareSnapshot_get(b, snap); + + if(snap->group) + group_member_recv(srvc, snap); + + if(! mwGetBuffer_error(b)) + status_recv(srvc, snap); + + mwAwareSnapshot_clear(snap); + g_free(snap); +} + + +static void recv_GROUP(struct mwServiceAware *srvc, + struct mwGetBuffer *b) { + + struct mwAwareIdBlock idb = { 0, 0, 0 }; + + /* really nothing to be done with this. The group should have + already been added to the list and service, and is now simply + awaiting a snapshot/update with users listed as belonging in said + group. */ + + mwAwareIdBlock_get(b, &idb); + mwAwareIdBlock_clear(&idb); +} + + +static void recv_OPT_GOT_SET(struct mwServiceAware *srvc, + struct mwGetBuffer *b) { + + struct mwAwareAttribute attrib; + struct mwAwareIdBlock idb; + guint32 junk, check; + + guint32_get(b, &junk); + mwAwareIdBlock_get(b, &idb); + guint32_get(b, &junk); + guint32_get(b, &check); + guint32_get(b, &junk); + guint32_get(b, &attrib.key); + + if(check) { + mwOpaque_get(b, &attrib.data); + } else { + attrib.data.len = 0; + attrib.data.data = NULL; + } + + attrib_recv(srvc, &idb, &attrib); + + mwAwareIdBlock_clear(&idb); + mwOpaque_clear(&attrib.data); +} + + +static void recv_OPT_GOT_UNSET(struct mwServiceAware *srvc, + struct mwGetBuffer *b) { + + struct mwAwareAttribute attrib; + struct mwAwareIdBlock idb; + guint32 junk; + + attrib.key = 0; + attrib.data.len = 0; + attrib.data.data = NULL; + + guint32_get(b, &junk); + mwAwareIdBlock_get(b, &idb); + guint32_get(b, &attrib.key); + + attrib_recv(srvc, &idb, &attrib); + + mwAwareIdBlock_clear(&idb); +} + + +static void recv(struct mwService *srvc, struct mwChannel *chan, + guint16 type, struct mwOpaque *data) { + + struct mwServiceAware *srvc_aware = (struct mwServiceAware *) srvc; + struct mwGetBuffer *b; + + g_return_if_fail(srvc_aware->channel == chan); + g_return_if_fail(srvc->session == mwChannel_getSession(chan)); + g_return_if_fail(data != NULL); + + b = mwGetBuffer_wrap(data); + + switch(type) { + case msg_AWARE_SNAPSHOT: + recv_SNAPSHOT(srvc_aware, b); + break; + + case msg_AWARE_UPDATE: + recv_UPDATE(srvc_aware, b); + break; + + case msg_AWARE_GROUP: + recv_GROUP(srvc_aware, b); + break; + + case msg_OPT_GOT_SET: + recv_OPT_GOT_SET(srvc_aware, b); + break; + + case msg_OPT_GOT_UNSET: + recv_OPT_GOT_UNSET(srvc_aware, b); + break; + + case msg_OPT_GOT_UNKNOWN: + case msg_OPT_DID_SET: + case msg_OPT_DID_UNSET: + case msg_OPT_DID_ERROR: + break; + + default: + mw_mailme_opaque(data, "unknown message in aware service: 0x%04x", type); + } + + mwGetBuffer_free(b); +} + + +static void clear(struct mwService *srvc) { + struct mwServiceAware *srvc_aware = (struct mwServiceAware *) srvc; + + g_return_if_fail(srvc != NULL); + + while(srvc_aware->lists) + mwAwareList_free( (struct mwAwareList *) srvc_aware->lists->data ); + + g_hash_table_destroy(srvc_aware->entries); + srvc_aware->entries = NULL; + + g_hash_table_destroy(srvc_aware->attribs); + srvc_aware->attribs = NULL; +} + + +static const char *name(struct mwService *srvc) { + return "Presence Awareness"; +} + + +static const char *desc(struct mwService *srvc) { + return "Buddy list service with support for server-side groups"; +} + + +static struct mwChannel *make_blist(struct mwServiceAware *srvc, + struct mwChannelSet *cs) { + + struct mwChannel *chan = mwChannel_newOutgoing(cs); + + mwChannel_setService(chan, MW_SERVICE(srvc)); + mwChannel_setProtoType(chan, 0x00000011); + mwChannel_setProtoVer(chan, 0x00030005); + + return mwChannel_create(chan)? NULL: chan; +} + + +static void start(struct mwService *srvc) { + struct mwServiceAware *srvc_aware; + struct mwChannel *chan = NULL; + + srvc_aware = (struct mwServiceAware *) srvc; + chan = make_blist(srvc_aware, mwSession_getChannels(srvc->session)); + + if(chan != NULL) { + srvc_aware->channel = chan; + } else { + mwService_stopped(srvc); + } +} + + +static void stop(struct mwService *srvc) { + struct mwServiceAware *srvc_aware; + + srvc_aware = (struct mwServiceAware *) srvc; + + if(srvc_aware->channel) { + mwChannel_destroy(srvc_aware->channel, ERR_SUCCESS, NULL); + srvc_aware->channel = NULL; + } + + mwService_stopped(srvc); +} + + +struct mwServiceAware * +mwServiceAware_new(struct mwSession *session, + struct mwAwareHandler *handler) { + + struct mwService *service; + struct mwServiceAware *srvc; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(handler != NULL, NULL); + + srvc = g_new0(struct mwServiceAware, 1); + srvc->handler = handler; + srvc->entries = g_hash_table_new_full((GHashFunc) mwAwareIdBlock_hash, + (GEqualFunc) mwAwareIdBlock_equal, + NULL, + (GDestroyNotify) aware_entry_free); + + srvc->attribs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, + (GDestroyNotify) attrib_entry_free); + + service = MW_SERVICE(srvc); + mwService_init(service, session, mwService_AWARE); + + service->recv_accept = (mwService_funcRecvAccept) recv_accept; + service->recv_destroy = (mwService_funcRecvDestroy) recv_destroy; + service->recv = recv; + service->start = start; + service->stop = stop; + service->clear = clear; + service->get_name = name; + service->get_desc = desc; + + return srvc; +} + + +int mwServiceAware_setAttribute(struct mwServiceAware *srvc, + guint32 key, struct mwOpaque *data) { + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + b = mwPutBuffer_new(); + + guint32_put(b, 0x00); + guint32_put(b, data->len); + guint32_put(b, 0x00); + guint32_put(b, key); + mwOpaque_put(b, data); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_send(srvc->channel, msg_OPT_DO_SET, &o); + mwOpaque_clear(&o); + + return ret; +} + + +int mwServiceAware_setAttributeBoolean(struct mwServiceAware *srvc, + guint32 key, gboolean val) { + int ret; + struct mwPutBuffer *b; + struct mwOpaque o; + + b = mwPutBuffer_new(); + + gboolean_put(b, FALSE); + gboolean_put(b, val); + + mwPutBuffer_finalize(&o, b); + + ret = mwServiceAware_setAttribute(srvc, key, &o); + mwOpaque_clear(&o); + + return ret; +} + + +int mwServiceAware_setAttributeInteger(struct mwServiceAware *srvc, + guint32 key, guint32 val) { + int ret; + struct mwPutBuffer *b; + struct mwOpaque o; + + b = mwPutBuffer_new(); + guint32_put(b, val); + + mwPutBuffer_finalize(&o, b); + + ret = mwServiceAware_setAttribute(srvc, key, &o); + mwOpaque_clear(&o); + + return ret; +} + + +int mwServiceAware_setAttributeString(struct mwServiceAware *srvc, + guint32 key, const char *str) { + int ret; + struct mwPutBuffer *b; + struct mwOpaque o; + + b = mwPutBuffer_new(); + mwString_put(b, str); + + mwPutBuffer_finalize(&o, b); + + ret = mwServiceAware_setAttribute(srvc, key, &o); + mwOpaque_clear(&o); + + return ret; +} + + +int mwServiceAware_unsetAttribute(struct mwServiceAware *srvc, + guint32 key) { + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + b = mwPutBuffer_new(); + + guint32_put(b, 0x00); + guint32_put(b, key); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_send(srvc->channel, msg_OPT_DO_UNSET, &o); + mwOpaque_clear(&o); + + return ret; +} + + +guint32 mwAwareAttribute_getKey(const struct mwAwareAttribute *attrib) { + g_return_val_if_fail(attrib != NULL, 0x00); + return attrib->key; +} + + +gboolean mwAwareAttribute_asBoolean(const struct mwAwareAttribute *attrib) { + struct mwGetBuffer *b; + gboolean ret; + + if(! attrib) return FALSE; + + b = mwGetBuffer_wrap(&attrib->data); + if(attrib->data.len >= 4) { + guint32 r32 = 0x00; + guint32_get(b, &r32); + ret = !! r32; + + } else if(attrib->data.len >= 2) { + guint16 r16 = 0x00; + guint16_get(b, &r16); + ret = !! r16; + + } else if(attrib->data.len) { + gboolean_get(b, &ret); + } + + mwGetBuffer_free(b); + + return ret; +} + + +guint32 mwAwareAttribute_asInteger(const struct mwAwareAttribute *attrib) { + struct mwGetBuffer *b; + guint32 r32 = 0x00; + + if(! attrib) return 0x00; + + b = mwGetBuffer_wrap(&attrib->data); + if(attrib->data.len >= 4) { + guint32_get(b, &r32); + + } else if(attrib->data.len == 3) { + gboolean rb = FALSE; + guint16 r16 = 0x00; + gboolean_get(b, &rb); + guint16_get(b, &r16); + r32 = (guint32) r16; + + } else if(attrib->data.len == 2) { + guint16 r16 = 0x00; + guint16_get(b, &r16); + r32 = (guint32) r16; + + } else if(attrib->data.len) { + gboolean rb = FALSE; + gboolean_get(b, &rb); + r32 = (guint32) rb; + } + + mwGetBuffer_free(b); + + return r32; +} + + +char *mwAwareAttribute_asString(const struct mwAwareAttribute *attrib) { + struct mwGetBuffer *b; + char *ret = NULL; + + if(! attrib) return NULL; + + b = mwGetBuffer_wrap(&attrib->data); + mwString_get(b, &ret); + mwGetBuffer_free(b); + + return ret; +} + + +const struct mwOpaque * +mwAwareAttribute_asOpaque(const struct mwAwareAttribute *attrib) { + g_return_val_if_fail(attrib != NULL, NULL); + return &attrib->data; +} + + +struct mwAwareList * +mwAwareList_new(struct mwServiceAware *srvc, + struct mwAwareListHandler *handler) { + + struct mwAwareList *al; + + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(handler != NULL, NULL); + + al = g_new0(struct mwAwareList, 1); + al->service = srvc; + al->handler = handler; + + srvc->lists = g_list_prepend(srvc->lists, al); + + return al; +} + + +void mwAwareList_free(struct mwAwareList *list) { + struct mwServiceAware *srvc; + struct mwAwareListHandler *handler; + + g_return_if_fail(list != NULL); + g_return_if_fail(list->service != NULL); + + srvc = list->service; + srvc->lists = g_list_remove_all(srvc->lists, list); + + handler = list->handler; + if(handler && handler->clear) { + handler->clear(list); + list->handler = NULL; + } + + mw_datum_clear(&list->client_data); + + mwAwareList_unwatchAllAttributes(list); + mwAwareList_removeAllAware(list); + + list->service = NULL; + + g_free(list); +} + + +struct mwAwareListHandler *mwAwareList_getHandler(struct mwAwareList *list) { + g_return_val_if_fail(list != NULL, NULL); + return list->handler; +} + + +static void watch_add(struct mwAwareList *list, guint32 key) { + struct mwServiceAware *srvc; + struct attrib_entry *watch; + gpointer k = GUINT_TO_POINTER(key); + + if(! list->attribs) + list->attribs = g_hash_table_new(g_direct_hash, g_direct_equal); + + if(g_hash_table_lookup(list->attribs, k)) + return; + + srvc = list->service; + + watch = g_hash_table_lookup(srvc->attribs, k); + if(! watch) { + watch = g_new0(struct attrib_entry, 1); + watch->key = key; + g_hash_table_insert(srvc->attribs, k, watch); + } + + g_hash_table_insert(list->attribs, k, watch); + + watch->membership = g_list_prepend(watch->membership, list); +} + + +static void watch_remove(struct mwAwareList *list, guint32 key) { + struct attrib_entry *watch = NULL; + gpointer k = GUINT_TO_POINTER(key); + + if(list->attribs) + watch = g_hash_table_lookup(list->attribs, k); + + g_return_if_fail(watch != NULL); + + g_hash_table_remove(list->attribs, k); + watch->membership = g_list_remove(watch->membership, list); +} + + +int mwAwareList_watchAttributeArray(struct mwAwareList *list, + guint32 *keys) { + guint32 k; + + g_return_val_if_fail(list != NULL, -1); + g_return_val_if_fail(list->service != NULL, -1); + + if(! keys) return 0; + + for(k = *keys; k; keys++) + watch_add(list, k); + + return send_attrib_list(list->service); +} + + +int mwAwareList_watchAttributes(struct mwAwareList *list, + guint32 key, ...) { + guint32 k; + va_list args; + + g_return_val_if_fail(list != NULL, -1); + g_return_val_if_fail(list->service != NULL, -1); + + va_start(args, key); + for(k = key; k; k = va_arg(args, guint32)) + watch_add(list, k); + va_end(args); + + return send_attrib_list(list->service); +} + + +int mwAwareList_unwatchAttributeArray(struct mwAwareList *list, + guint32 *keys) { + guint32 k; + + g_return_val_if_fail(list != NULL, -1); + g_return_val_if_fail(list->service != NULL, -1); + + if(! keys) return 0; + + for(k = *keys; k; keys++) + watch_add(list, k); + + return remove_unused_attrib(list->service); +} + + +int mwAwareList_unwatchAttributes(struct mwAwareList *list, + guint32 key, ...) { + guint32 k; + va_list args; + + g_return_val_if_fail(list != NULL, -1); + g_return_val_if_fail(list->service != NULL, -1); + + va_start(args, key); + for(k = key; k; k = va_arg(args, guint32)) + watch_remove(list, k); + va_end(args); + + return remove_unused_attrib(list->service); +} + + +static void dismember_attrib(gpointer k, struct attrib_entry *watch, + struct mwAwareList *list) { + + watch->membership = g_list_remove(watch->membership, list); +} + + +int mwAwareList_unwatchAllAttributes(struct mwAwareList *list) { + + struct mwServiceAware *srvc; + + g_return_val_if_fail(list != NULL, -1); + srvc = list->service; + + if(list->attribs) { + g_hash_table_foreach(list->attribs, (GHFunc) dismember_attrib, list); + g_hash_table_destroy(list->attribs); + } + + return remove_unused_attrib(srvc); +} + + +static void collect_attrib_keys(gpointer key, struct attrib_entry *attrib, + guint32 **ck) { + guint32 *keys = (*ck)++; + *keys = GPOINTER_TO_UINT(key); +} + + +guint32 *mwAwareList_getWatchedAttributes(struct mwAwareList *list) { + guint32 *keys, **ck; + guint count; + + g_return_val_if_fail(list != NULL, NULL); + g_return_val_if_fail(list->attribs != NULL, NULL); + + count = g_hash_table_size(list->attribs); + keys = g_new0(guint32, count + 1); + + ck = &keys; + g_hash_table_foreach(list->attribs, (GHFunc) collect_attrib_keys, ck); + + return keys; +} + + +int mwAwareList_addAware(struct mwAwareList *list, GList *id_list) { + + /* for each awareness id: + - if it's already in the list, continue + - if it's not in the service list: + - create an awareness + - add it to the service list + - add this list to the membership + - add to the list + */ + + struct mwServiceAware *srvc; + GList *additions = NULL; + int ret = 0; + + g_return_val_if_fail(list != NULL, -1); + + srvc = list->service; + g_return_val_if_fail(srvc != NULL, -1); + + for(; id_list; id_list = id_list->next) { + if(list_add(list, id_list->data)) + additions = g_list_prepend(additions, id_list->data); + } + + /* if the service is alive-- or getting there-- we'll need to send + these additions upstream */ + if(MW_SERVICE_IS_LIVE(srvc) && additions) + ret = send_add(srvc->channel, additions); + + g_list_free(additions); + return ret; +} + + +int mwAwareList_removeAware(struct mwAwareList *list, GList *id_list) { + + /* for each awareness id: + - if it's not in the list, forget it + - remove from the list + - remove list from the membership + + - call remove round + */ + + struct mwServiceAware *srvc; + struct mwAwareIdBlock *id; + struct aware_entry *aware; + + g_return_val_if_fail(list != NULL, -1); + + srvc = list->service; + g_return_val_if_fail(srvc != NULL, -1); + + for(; id_list; id_list = id_list->next) { + id = id_list->data; + aware = list_aware_find(list, id); + + if(! aware) { + g_warning("buddy %s, %s not in list", + NSTR(id->user), + NSTR(id->community)); + continue; + } + + aware->membership = g_list_remove(aware->membership, list); + g_hash_table_remove(list->entries, id); + } + + return remove_unused(srvc); +} + + +static void dismember_aware(gpointer k, struct aware_entry *aware, + struct mwAwareList *list) { + + aware->membership = g_list_remove(aware->membership, list); +} + + +int mwAwareList_removeAllAware(struct mwAwareList *list) { + struct mwServiceAware *srvc; + + g_return_val_if_fail(list != NULL, -1); + srvc = list->service; + + g_return_val_if_fail(srvc != NULL, -1); + + /* for each entry, remove the aware list from the service entry's + membership collection */ + if(list->entries) { + g_hash_table_foreach(list->entries, (GHFunc) dismember_aware, list); + g_hash_table_destroy(list->entries); + } + + return remove_unused(srvc); +} + + +void mwAwareList_setClientData(struct mwAwareList *list, + gpointer data, GDestroyNotify clear) { + + g_return_if_fail(list != NULL); + mw_datum_set(&list->client_data, data, clear); +} + + +gpointer mwAwareList_getClientData(struct mwAwareList *list) { + g_return_val_if_fail(list != NULL, NULL); + return mw_datum_get(&list->client_data); +} + + +void mwAwareList_removeClientData(struct mwAwareList *list) { + g_return_if_fail(list != NULL); + mw_datum_clear(&list->client_data); +} + + +void mwServiceAware_setStatus(struct mwServiceAware *srvc, + struct mwAwareIdBlock *user, + struct mwUserStatus *stat) { + + struct mwAwareSnapshot idb; + + g_return_if_fail(srvc != NULL); + g_return_if_fail(user != NULL); + g_return_if_fail(stat != NULL); + + /* just reference the strings. then we don't need to free them */ + idb.id.type = user->type; + idb.id.user = user->user; + idb.id.community = user->community; + + idb.group = NULL; + idb.online = TRUE; + idb.alt_id = NULL; + + idb.status.status = stat->status; + idb.status.time = stat->time; + idb.status.desc = stat->desc; + + idb.name = NULL; + + status_recv(srvc, &idb); +} + + +const struct mwAwareAttribute * +mwServiceAware_getAttribute(struct mwServiceAware *srvc, + struct mwAwareIdBlock *user, + guint32 key) { + + struct aware_entry *aware; + + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(user != NULL, NULL); + g_return_val_if_fail(key != 0x00, NULL); + + aware = aware_find(srvc, user); + g_return_val_if_fail(aware != NULL, NULL); + + return g_hash_table_lookup(aware->attribs, GUINT_TO_POINTER(key)); +} + + +const char *mwServiceAware_getText(struct mwServiceAware *srvc, + struct mwAwareIdBlock *user) { + + struct aware_entry *aware; + + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(user != NULL, NULL); + + aware = aware_find(srvc, user); + if(! aware) return NULL; + + return aware->aware.status.desc; +} + + diff --git a/src/srvc_conf.c b/src/srvc_conf.c new file mode 100644 index 0000000..7a10d22 --- /dev/null +++ b/src/srvc_conf.c @@ -0,0 +1,867 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include "mw_channel.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_srvc_conf.h" +#include "mw_util.h" + + +/* This thing needs a re-write. More than anything else, I need to + re-examine the conferencing service protocol from more modern + clients */ + + +#define PROTOCOL_TYPE 0x00000010 +#define PROTOCOL_VER 0x00000002 + + +/** @see mwMsgChannelSend::type + @see recv */ +enum msg_type { + msg_WELCOME = 0x0000, /**< welcome message */ + msg_INVITE = 0x0001, /**< outgoing invitation */ + msg_JOIN = 0x0002, /**< someone joined */ + msg_PART = 0x0003, /**< someone left */ + msg_MESSAGE = 0x0004, /**< conference message */ +}; + + +/** the conferencing service */ +struct mwServiceConference { + struct mwService service; + + /** call-back handler for this service */ + struct mwConferenceHandler *handler; + + /** collection of conferences in this service */ + GList *confs; +}; + + +/** a conference and its members */ +struct mwConference { + enum mwConferenceState state; /**< state of the conference */ + struct mwServiceConference *service; /**< owning service */ + struct mwChannel *channel; /**< conference's channel */ + + char *name; /**< server identifier for the conference */ + char *title; /**< topic for the conference */ + + struct mwLoginInfo owner; /**< person who created this conference */ + GHashTable *members; /**< mapping guint16:mwLoginInfo */ + struct mw_datum client_data; +}; + + +#define MEMBER_FIND(conf, id) \ + g_hash_table_lookup(conf->members, GUINT_TO_POINTER((guint) id)) + + +#define MEMBER_ADD(conf, id, member) \ + g_hash_table_insert(conf->members, GUINT_TO_POINTER((guint) id), member) + + +#define MEMBER_REM(conf, id) \ + g_hash_table_remove(conf->members, GUINT_TO_POINTER((guint) id)); + + +/** clear and free a login info block */ +static void login_free(struct mwLoginInfo *li) { + mwLoginInfo_clear(li); + g_free(li); +} + + +/** generates a random conference name built around a user name */ +static char *conf_generate_name(const char *user) { + guint a, b; + char *ret; + + user = user? user: ""; + + srand(clock() + rand()); + a = ((rand() & 0xff) << 8) | (rand() & 0xff); + b = time(NULL); + + ret = g_strdup_printf("%s(%08x,%04x)", user, b, a); + g_debug("generated random conference name: '%s'", ret); + return ret; +} + + + + + +static struct mwConference *conf_new(struct mwServiceConference *srvc) { + + struct mwConference *conf; + struct mwSession *session; + const char *user; + + conf = g_new0(struct mwConference, 1); + conf->state = mwConference_NEW; + conf->service = srvc; + conf->members = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, + (GDestroyNotify) login_free); + + session = mwService_getSession(MW_SERVICE(srvc)); + user = mwSession_getProperty(session, mwSession_AUTH_USER_ID); + + srvc->confs = g_list_prepend(srvc->confs, conf); + + return conf; +} + + +/** clean and free a conference structure */ +static void conf_free(struct mwConference *conf) { + struct mwServiceConference *srvc; + + /* this shouldn't ever happen, but just to be sure */ + g_return_if_fail(conf != NULL); + + srvc = conf->service; + + if(conf->members) + g_hash_table_destroy(conf->members); + + srvc->confs = g_list_remove_all(srvc->confs, conf); + + mw_datum_clear(&conf->client_data); + + g_free(conf->name); + g_free(conf->title); + g_free(conf); +} + + +static struct mwConference *conf_find(struct mwServiceConference *srvc, + struct mwChannel *chan) { + GList *l; + + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(chan != NULL, NULL); + + for(l = srvc->confs; l; l = l->next) { + struct mwConference *conf = l->data; + if(conf->channel == chan) return conf; + } + + return NULL; +} + + +static const char *conf_state_str(enum mwConferenceState state) { + switch(state) { + case mwConference_NEW: return "new"; + case mwConference_PENDING: return "pending"; + case mwConference_INVITED: return "invited"; + case mwConference_OPEN: return "open"; + case mwConference_CLOSING: return "closing"; + case mwConference_ERROR: return "error"; + + case mwConference_UNKNOWN: /* fall through */ + default: return "UNKNOWN"; + } +} + + +static void conf_state(struct mwConference *conf, + enum mwConferenceState state) { + g_return_if_fail(conf != NULL); + + if(conf->state == state) return; + + conf->state = state; + g_message("conference %s state: %s", + NSTR(conf->name), conf_state_str(state)); +} + + +static void recv_channelCreate(struct mwService *srvc, + struct mwChannel *chan, + struct mwMsgChannelCreate *msg) { + + /* - this is how we really receive invitations + - create a conference and associate it with the channel + - obtain the invite data from the msg addtl info + - mark the conference as INVITED + - trigger the got_invite event + */ + + struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc; + struct mwConference *conf; + + struct mwGetBuffer *b; + + char *invite = NULL; + guint tmp; + + conf = conf_new(srvc_conf); + conf->channel = chan; + + b = mwGetBuffer_wrap(&msg->addtl); + + guint32_get(b, &tmp); + mwString_get(b, &conf->name); + mwString_get(b, &conf->title); + guint32_get(b, &tmp); + mwLoginInfo_get(b, &conf->owner); + guint32_get(b, &tmp); + mwString_get(b, &invite); + + if(mwGetBuffer_error(b)) { + g_warning("failure parsing addtl for conference invite"); + mwConference_destroy(conf, ERR_FAILURE, NULL); + + } else { + struct mwConferenceHandler *h = srvc_conf->handler; + conf_state(conf, mwConference_INVITED); + if(h->on_invited) + h->on_invited(conf, &conf->owner, invite); + } + + mwGetBuffer_free(b); + g_free(invite); +} + + +static void recv_channelAccept(struct mwService *srvc, + struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + + ; +} + + +static void recv_channelDestroy(struct mwService *srvc, + struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + + /* - find conference from channel + - trigger got_closed + - remove conference, dealloc + */ + + struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc; + struct mwConference *conf = conf_find(srvc_conf, chan); + struct mwConferenceHandler *h = srvc_conf->handler; + + /* if there's no such conference, then I guess there's nothing to worry + about. Except of course for the fact that we should never receive a + channel destroy for a conference that doesn't exist. */ + if(! conf) return; + + conf->channel = NULL; + + conf_state(conf, msg->reason? mwConference_ERROR: mwConference_CLOSING); + + if(h->conf_closed) + h->conf_closed(conf, msg->reason); + + mwConference_destroy(conf, ERR_SUCCESS, NULL); +} + + +static void WELCOME_recv(struct mwServiceConference *srvc, + struct mwConference *conf, + struct mwGetBuffer *b) { + + struct mwConferenceHandler *h; + guint16 tmp16; + guint32 tmp32; + guint32 count; + GList *l = NULL; + + /* re-read name and title */ + g_free(conf->name); + g_free(conf->title); + conf->name = NULL; + conf->title = NULL; + mwString_get(b, &conf->name); + mwString_get(b, &conf->title); + + /* some numbers we don't care about, then a count of members */ + guint16_get(b, &tmp16); + guint32_get(b, &tmp32); + guint32_get(b, &count); + + if(mwGetBuffer_error(b)) { + g_warning("error parsing welcome message for conference"); + mwConference_destroy(conf, ERR_FAILURE, NULL); + return; + } + + while(count--) { + guint16 member_id; + struct mwLoginInfo *member = g_new0(struct mwLoginInfo, 1); + + guint16_get(b, &member_id); + mwLoginInfo_get(b, member); + + if(mwGetBuffer_error(b)) { + login_free(member); + break; + } + + MEMBER_ADD(conf, member_id, member); + l = g_list_append(l, member); + } + + conf_state(conf, mwConference_OPEN); + + h = srvc->handler; + if(h->conf_opened) + h->conf_opened(conf, l); + + /* get rid of the GList, but not its contents */ + g_list_free(l); +} + + +static void JOIN_recv(struct mwServiceConference *srvc, + struct mwConference *conf, + struct mwGetBuffer *b) { + + struct mwConferenceHandler *h; + guint16 m_id; + struct mwLoginInfo *m; + + /* for some inane reason, conferences we create will send a join + message for ourselves before the welcome message. Since the + welcome message will list our ID among those in the channel, + we're going to just pretend that these join messages don't + exist */ + if(conf->state == mwConference_PENDING) + return; + + m = g_new0(struct mwLoginInfo, 1); + + guint16_get(b, &m_id); + mwLoginInfo_get(b, m); + + if(mwGetBuffer_error(b)) { + g_warning("failed parsing JOIN message in conference"); + login_free(m); + return; + } + + MEMBER_ADD(conf, m_id, m); + + h = srvc->handler; + if(h->on_peer_joined) + h->on_peer_joined(conf, m); +} + + +static void PART_recv(struct mwServiceConference *srvc, + struct mwConference *conf, + struct mwGetBuffer *b) { + + /* - parse who left + - look up their membership + - remove them from the members list + - trigger the event + */ + + struct mwConferenceHandler *h; + guint16 id = 0; + struct mwLoginInfo *m; + + guint16_get(b, &id); + + if(mwGetBuffer_error(b)) return; + + m = MEMBER_FIND(conf, id); + if(! m) return; + + h = srvc->handler; + if(h->on_peer_parted) + h->on_peer_parted(conf, m); + + MEMBER_REM(conf, id); +} + + +static void text_recv(struct mwServiceConference *srvc, + struct mwConference *conf, + struct mwLoginInfo *m, + struct mwGetBuffer *b) { + + /* this function acts a lot like receiving an IM Text message. The text + message contains only a string */ + + char *text = NULL; + struct mwConferenceHandler *h; + + mwString_get(b, &text); + + if(mwGetBuffer_error(b)) { + g_warning("failed to parse text message in conference"); + g_free(text); + return; + } + + h = srvc->handler; + if(text && h->on_text) { + h->on_text(conf, m, text); + } + + g_free(text); +} + + +static void data_recv(struct mwServiceConference *srvc, + struct mwConference *conf, + struct mwLoginInfo *m, + struct mwGetBuffer *b) { + + /* this function acts a lot like receiving an IM Data message. The + data message has a type, a subtype, and an opaque. We only + support typing notification though. */ + + /** @todo it's possible that some clients send text in a data + message, as we've seen rarely in the IM service. Have to add + support for that here */ + + guint32 type, subtype; + struct mwConferenceHandler *h; + + guint32_get(b, &type); + guint32_get(b, &subtype); + + if(mwGetBuffer_error(b)) return; + + /* don't know how to deal with any others yet */ + if(type != 0x01) { + g_message("unknown data message type (0x%08x, 0x%08x)", type, subtype); + return; + } + + h = srvc->handler; + if(h->on_typing) { + h->on_typing(conf, m, !subtype); + } +} + + +static void MESSAGE_recv(struct mwServiceConference *srvc, + struct mwConference *conf, + struct mwGetBuffer *b) { + + /* - look up who send the message by their id + - trigger the event + */ + + guint16 id; + guint32 type; + struct mwLoginInfo *m; + + /* an empty buffer isn't an error, just ignored */ + if(! mwGetBuffer_remaining(b)) return; + + guint16_get(b, &id); + guint32_get(b, &type); /* reuse type variable */ + guint32_get(b, &type); + + if(mwGetBuffer_error(b)) return; + + m = MEMBER_FIND(conf, id); + if(! m) { + g_warning("received message type 0x%04x from" + " unknown conference member %u", type, id); + return; + } + + switch(type) { + case 0x01: /* type is text */ + text_recv(srvc, conf, m, b); + break; + + case 0x02: /* type is data */ + data_recv(srvc, conf, m, b); + break; + + default: + g_warning("unknown message type 0x%4x received in conference", type); + } +} + + +static void recv(struct mwService *srvc, struct mwChannel *chan, + guint16 type, struct mwOpaque *data) { + + struct mwServiceConference *srvc_conf = (struct mwServiceConference *) srvc; + struct mwConference *conf = conf_find(srvc_conf, chan); + struct mwGetBuffer *b; + + g_return_if_fail(conf != NULL); + + b = mwGetBuffer_wrap(data); + + switch(type) { + case msg_WELCOME: + WELCOME_recv(srvc_conf, conf, b); + break; + + case msg_JOIN: + JOIN_recv(srvc_conf, conf, b); + break; + + case msg_PART: + PART_recv(srvc_conf, conf, b); + break; + + case msg_MESSAGE: + MESSAGE_recv(srvc_conf, conf, b); + break; + + default: + ; /* hrm. should log this. TODO */ + } +} + + +static void clear(struct mwServiceConference *srvc) { + struct mwConferenceHandler *h; + + while(srvc->confs) + conf_free(srvc->confs->data); + + h = srvc->handler; + if(h && h->clear) + h->clear(srvc); + srvc->handler = NULL; +} + + +static const char *name(struct mwService *srvc) { + return "Basic Conferencing"; +} + + +static const char *desc(struct mwService *srvc) { + return "Multi-user plain-text conferencing"; +} + + +static void start(struct mwService *srvc) { + mwService_started(srvc); +} + + +static void stop(struct mwServiceConference *srvc) { + while(srvc->confs) + mwConference_destroy(srvc->confs->data, ERR_SUCCESS, NULL); + + mwService_stopped(MW_SERVICE(srvc)); +} + + +struct mwServiceConference * +mwServiceConference_new(struct mwSession *session, + struct mwConferenceHandler *handler) { + + struct mwServiceConference *srvc_conf; + struct mwService *srvc; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(handler != NULL, NULL); + + srvc_conf = g_new0(struct mwServiceConference, 1); + srvc = &srvc_conf->service; + + mwService_init(srvc, session, mwService_CONFERENCE); + srvc->start = start; + srvc->stop = (mwService_funcStop) stop; + srvc->recv_create = recv_channelCreate; + srvc->recv_accept = recv_channelAccept; + srvc->recv_destroy = recv_channelDestroy; + srvc->recv = recv; + srvc->clear = (mwService_funcClear) clear; + srvc->get_name = name; + srvc->get_desc = desc; + + srvc_conf->handler = handler; + + return srvc_conf; +} + + +struct mwConference *mwConference_new(struct mwServiceConference *srvc, + const char *title) { + struct mwConference *conf; + + g_return_val_if_fail(srvc != NULL, NULL); + + conf = conf_new(srvc); + conf->title = g_strdup(title); + + return conf; +} + + +struct mwServiceConference * +mwConference_getService(struct mwConference *conf) { + g_return_val_if_fail(conf != NULL, NULL); + return conf->service; +} + + +const char *mwConference_getName(struct mwConference *conf) { + g_return_val_if_fail(conf != NULL, NULL); + return conf->name; +} + + +const char *mwConference_getTitle(struct mwConference *conf) { + g_return_val_if_fail(conf != NULL, NULL); + return conf->title; +} + + +GList *mwConference_getMembers(struct mwConference *conf) { + g_return_val_if_fail(conf != NULL, NULL); + g_return_val_if_fail(conf->members != NULL, NULL); + + return map_collect_values(conf->members); +} + + +int mwConference_open(struct mwConference *conf) { + struct mwSession *session; + struct mwChannel *chan; + struct mwPutBuffer *b; + int ret; + + g_return_val_if_fail(conf != NULL, -1); + g_return_val_if_fail(conf->service != NULL, -1); + g_return_val_if_fail(conf->state == mwConference_NEW, -1); + g_return_val_if_fail(conf->channel == NULL, -1); + + session = mwService_getSession(MW_SERVICE(conf->service)); + g_return_val_if_fail(session != NULL, -1); + + if(! conf->name) { + char *user = mwSession_getProperty(session, mwSession_AUTH_USER_ID); + conf->name = conf_generate_name(user? user: "meanwhile"); + } + + chan = mwChannel_newOutgoing(mwSession_getChannels(session)); + mwChannel_setService(chan, MW_SERVICE(conf->service)); + mwChannel_setProtoType(chan, PROTOCOL_TYPE); + mwChannel_setProtoVer(chan, PROTOCOL_VER); + +#if 0 + /* offer all known ciphers */ + mwChannel_populateSupportedCipherInstances(chan); +#endif + + b = mwPutBuffer_new(); + mwString_put(b, conf->name); + mwString_put(b, conf->title); + guint32_put(b, 0x00); + mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b); + + ret = mwChannel_create(chan); + if(ret) { + conf_state(conf, mwConference_ERROR); + } else { + conf_state(conf, mwConference_PENDING); + conf->channel = chan; + } + + return ret; +} + + +int mwConference_destroy(struct mwConference *conf, + guint32 reason, const char *text) { + + struct mwServiceConference *srvc; + struct mwOpaque info = { 0, 0 }; + int ret = 0; + + g_return_val_if_fail(conf != NULL, -1); + + srvc = conf->service; + g_return_val_if_fail(srvc != NULL, -1); + + /* remove conference from the service */ + srvc->confs = g_list_remove_all(srvc->confs, conf); + + /* close the channel if applicable */ + if(conf->channel) { + if(text && *text) { + info.len = strlen(text); + info.data = (guchar *) text; + } + + ret = mwChannel_destroy(conf->channel, reason, &info); + } + + /* free the conference */ + conf_free(conf); + + return ret; +} + + +int mwConference_accept(struct mwConference *conf) { + /* - if conference is not INVITED, return -1 + - accept the conference channel + - send an empty JOIN message + */ + + struct mwChannel *chan; + int ret; + + g_return_val_if_fail(conf != NULL, -1); + g_return_val_if_fail(conf->state == mwConference_INVITED, -1); + + chan = conf->channel; + ret = mwChannel_accept(chan); + + if(! ret) + ret = mwChannel_sendEncrypted(chan, msg_JOIN, NULL, FALSE); + + return ret; +} + + +int mwConference_invite(struct mwConference *conf, + struct mwIdBlock *who, + const char *text) { + + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + g_return_val_if_fail(conf != NULL, -1); + g_return_val_if_fail(conf->channel != NULL, -1); + g_return_val_if_fail(who != NULL, -1); + + b = mwPutBuffer_new(); + + mwIdBlock_put(b, who); + guint16_put(b, 0x00); + guint32_put(b, 0x00); + mwString_put(b, text); + mwString_put(b, who->user); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_sendEncrypted(conf->channel, msg_INVITE, &o, FALSE); + mwOpaque_clear(&o); + + return ret; +} + + +int mwConference_sendText(struct mwConference *conf, const char *text) { + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + g_return_val_if_fail(conf != NULL, -1); + g_return_val_if_fail(conf->channel != NULL, -1); + + b = mwPutBuffer_new(); + + guint32_put(b, 0x01); + mwString_put(b, text); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_sendEncrypted(conf->channel, msg_MESSAGE, &o, FALSE); + mwOpaque_clear(&o); + + return ret; +} + + +int mwConference_sendTyping(struct mwConference *conf, gboolean typing) { + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + g_return_val_if_fail(conf != NULL, -1); + g_return_val_if_fail(conf->channel != NULL, -1); + g_return_val_if_fail(conf->state == mwConference_OPEN, -1); + + b = mwPutBuffer_new(); + + guint32_put(b, 0x02); + guint32_put(b, 0x01); + guint32_put(b, !typing); + mwOpaque_put(b, NULL); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_sendEncrypted(conf->channel, msg_MESSAGE, &o, FALSE); + mwOpaque_clear(&o); + + return ret; +} + + +void mwConference_setClientData(struct mwConference *conference, + gpointer data, GDestroyNotify clear) { + + g_return_if_fail(conference != NULL); + mw_datum_set(&conference->client_data, data, clear); +} + + +gpointer mwConference_getClientData(struct mwConference *conference) { + g_return_val_if_fail(conference != NULL, NULL); + return mw_datum_get(&conference->client_data); +} + + +void mwConference_removeClientData(struct mwConference *conference) { + g_return_if_fail(conference != NULL); + mw_datum_clear(&conference->client_data); +} + + +struct mwConferenceHandler * +mwServiceConference_getHandler(struct mwServiceConference *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->handler; +} + + +GList *mwServiceConference_getConferences(struct mwServiceConference *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return g_list_copy(srvc->confs); +} + diff --git a/src/srvc_dir.c b/src/srvc_dir.c new file mode 100644 index 0000000..cf49152 --- /dev/null +++ b/src/srvc_dir.c @@ -0,0 +1,664 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#include "mw_channel.h" +#include "mw_common.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_srvc_dir.h" +#include "mw_util.h" + + +#define PROTOCOL_TYPE 0x0000001c +#define PROTOCOL_VER 0x00000005 + + +enum dir_action { + action_list = 0x0000, /**< list address books */ + action_open = 0x0001, /**< open an addressbook as a directory */ + action_close = 0x0002, /**< close a directory */ + action_search = 0x0003, /**< search an open directory */ +}; + + +struct mwServiceDirectory { + struct mwService service; + + struct mwDirectoryHandler *handler; + + struct mwChannel *channel; + + guint32 counter; /**< counter of request IDs */ + GHashTable *requests; /**< map of request ID:directory */ + GHashTable *books; /**< book->name:mwAddressBook */ +}; + + +struct mwAddressBook { + struct mwServiceDirectory *service; + + guint32 id; /**< id or type or something */ + char *name; /**< name of address book */ + GHashTable *dirs; /**< dir->id:mwDirectory */ +}; + + +struct mwDirectory { + struct mwServiceDirectory *service; + struct mwAddressBook *book; + + enum mwDirectoryState state; + + guint32 id; /**< id of directory, assigned by server */ + guint32 search_id; /**< id of current search, from srvc->counter++ */ + + mwSearchHandler handler; + struct mw_datum client_data; +}; + + +#define next_request_id(srvc) ( ++((srvc)->counter) ) + + +static guint32 map_request(struct mwDirectory *dir) { + struct mwServiceDirectory *srvc = dir->service; + guint32 id = next_request_id(srvc); + + dir->search_id = id; + map_guint_insert(srvc->requests, id, dir); + + return id; +} + + +/** called when directory is removed from the service directory map */ +static void dir_free(struct mwDirectory *dir) { + map_guint_remove(dir->service->requests, dir->search_id); + g_free(dir); +} + + +/** remove the directory from the service list and its owning address + book, then frees the directory */ +static void dir_remove(struct mwDirectory *dir) { + struct mwAddressBook *book = dir->book; + map_guint_remove(book->dirs, dir->id); +} + + +__attribute__((used)) +static struct mwDirectory *dir_new(struct mwAddressBook *book, guint32 id) { + struct mwDirectory *dir = g_new0(struct mwDirectory, 1); + dir->service = book->service; + dir->book = book; + dir->id = id; + map_guint_insert(book->dirs, id, dir); + return dir; +} + + +/** called when book is removed from the service book map. Removed all + directories as well */ +static void book_free(struct mwAddressBook *book) { + g_hash_table_destroy(book->dirs); + g_free(book->name); +} + + +__attribute__((used)) +static void book_remove(struct mwAddressBook *book) { + struct mwServiceDirectory *srvc = book->service; + g_hash_table_remove(srvc->books, book->name); +} + + +static struct mwAddressBook *book_new(struct mwServiceDirectory *srvc, + const char *name, guint32 id) { + struct mwAddressBook *book = g_new0(struct mwAddressBook, 1); + book->service = srvc; + book->id = id; + book->name = g_strdup(name); + book->dirs = map_guint_new_full((GDestroyNotify) dir_free); + g_hash_table_insert(srvc->books, book->name, book); + return book; +} + + +static const char *getName(struct mwService *srvc) { + return "Address Book and Directory"; +} + + +static const char *getDesc(struct mwService *srvc) { + return "Address book directory service for user and group lookups"; +} + + +static struct mwChannel *make_channel(struct mwServiceDirectory *srvc) { + struct mwSession *session; + struct mwChannelSet *cs; + struct mwChannel *chan; + + session = mwService_getSession(MW_SERVICE(srvc)); + cs = mwSession_getChannels(session); + chan = mwChannel_newOutgoing(cs); + + mwChannel_setService(chan, MW_SERVICE(srvc)); + mwChannel_setProtoType(chan, PROTOCOL_TYPE); + mwChannel_setProtoVer(chan, PROTOCOL_VER); + + return mwChannel_create(chan)? NULL: chan; +} + + +static void start(struct mwServiceDirectory *srvc) { + struct mwChannel *chan; + + chan = make_channel(srvc); + if(chan) { + srvc->channel = chan; + } else { + mwService_stopped(MW_SERVICE(srvc)); + return; + } +} + + +static void stop(struct mwServiceDirectory *srvc) { + /* XXX */ + + if(srvc->channel) { + mwChannel_destroy(srvc->channel, ERR_SUCCESS, NULL); + srvc->channel = NULL; + } +} + + +static void clear(struct mwServiceDirectory *srvc) { + struct mwDirectoryHandler *handler; + + if(srvc->books) { + g_hash_table_destroy(srvc->books); + srvc->books = NULL; + } + + /* clear the handler */ + handler = srvc->handler; + if(handler && handler->clear) + handler->clear(srvc); + srvc->handler = NULL; +} + + +static void recv_create(struct mwServiceDirectory *srvc, + struct mwChannel *chan, + struct mwMsgChannelCreate *msg) { + + /* no way man, we call the shots around here */ + mwChannel_destroy(chan, ERR_FAILURE, NULL); +} + + +static void recv_accept(struct mwServiceDirectory *srvc, + struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + + g_return_if_fail(srvc->channel != NULL); + g_return_if_fail(srvc->channel == chan); + + if(MW_SERVICE_IS_STARTING(srvc)) { + mwService_started(MW_SERVICE(srvc)); + + } else { + mwChannel_destroy(chan, ERR_FAILURE, NULL); + } +} + + +static void recv_destroy(struct mwServiceDirectory *srvc, + struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + + srvc->channel = NULL; + mwService_stop(MW_SERVICE(srvc)); + /** @todo session sense service */ +} + + +static void recv_list(struct mwServiceDirectory *srvc, + struct mwOpaque *data) { + + struct mwGetBuffer *b; + guint32 request, code, count; + gboolean foo_1; + guint16 foo_2; + + b = mwGetBuffer_wrap(data); + + guint32_get(b, &request); + guint32_get(b, &code); + guint32_get(b, &count); + + gboolean_get(b, &foo_1); + guint16_get(b, &foo_2); + + if(foo_1 || foo_2) { + mw_mailme_opaque(data, "received strange address book list"); + mwGetBuffer_free(b); + return; + } + + while(!mwGetBuffer_error(b) && count--) { + guint32 id; + char *name = NULL; + + guint32_get(b, &id); + mwString_get(b, &name); + + book_new(srvc, name, id); + g_free(name); + } +} + + +static void recv_open(struct mwServiceDirectory *srvc, + struct mwOpaque *data) { + + /* look up the directory associated with this request id, + mark it as open, and trigger the event */ +} + + +static void recv_search(struct mwServiceDirectory *srvc, + struct mwOpaque *data) { + + /* look up the directory associated with this request id, + trigger the event */ +} + + +static void recv(struct mwServiceDirectory *srvc, + struct mwChannel *chan, + guint16 msg_type, struct mwOpaque *data) { + + g_return_if_fail(srvc != NULL); + g_return_if_fail(chan != NULL); + g_return_if_fail(chan == srvc->channel); + g_return_if_fail(data != NULL); + + switch(msg_type) { + case action_list: + recv_list(srvc, data); + break; + + case action_open: + recv_open(srvc, data); + break; + + case action_close: + ; /* I don't think we should receive these */ + break; + + case action_search: + recv_search(srvc, data); + break; + + default: + mw_mailme_opaque(data, "msg type 0x%04x in directory service", msg_type); + } +} + + +struct mwServiceDirectory * +mwServiceDirectory_new(struct mwSession *session, + struct mwDirectoryHandler *handler) { + + struct mwServiceDirectory *srvc; + struct mwService *service; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(handler != NULL, NULL); + + srvc = g_new0(struct mwServiceDirectory, 1); + service = MW_SERVICE(srvc); + + mwService_init(service, session, SERVICE_DIRECTORY); + service->get_name = getName; + service->get_desc = getDesc; + service->start = (mwService_funcStart) start; + service->stop = (mwService_funcStop) stop; + service->clear = (mwService_funcClear) clear; + service->recv_create = (mwService_funcRecvCreate) recv_create; + service->recv_accept = (mwService_funcRecvAccept) recv_accept; + service->recv_destroy = (mwService_funcRecvDestroy) recv_destroy; + service->recv = (mwService_funcRecv) recv; + + srvc->handler = handler; + srvc->requests = map_guint_new(); + srvc->books = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, (GDestroyNotify) book_free); + return srvc; +} + + +struct mwDirectoryHandler * +mwServiceDirectory_getHandler(struct mwServiceDirectory *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->handler; +} + + +int mwServiceDirectory_refreshAddressBooks(struct mwServiceDirectory *srvc) { + struct mwChannel *chan; + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + g_return_val_if_fail(srvc != NULL, -1); + + chan = srvc->channel; + g_return_val_if_fail(chan != NULL, -1); + + b = mwPutBuffer_new(); + guint32_put(b, next_request_id(srvc)); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_send(chan, action_list, &o); + mwOpaque_clear(&o); + + return ret; +} + + +GList *mwServiceDirectory_getAddressBooks(struct mwServiceDirectory *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(srvc->books != NULL, NULL); + + return map_collect_values(srvc->books); +} + + +GList *mwServiceDirectory_getDirectories(struct mwServiceDirectory *srvc) { + GList *bl, *ret = NULL; + + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(srvc->books != NULL, NULL); + + bl = map_collect_values(srvc->books); + for( ; bl; bl = g_list_delete_link(bl, bl)) { + struct mwAddressBook *book = bl->data; + ret = g_list_concat(ret, map_collect_values(book->dirs)); + } + + return ret; +} + + +GList *mwAddressBook_getDirectories(struct mwAddressBook *book) { + g_return_val_if_fail(book != NULL, NULL); + g_return_val_if_fail(book->dirs != NULL, NULL); + + return map_collect_values(book->dirs); +} + + +const char *mwAddressBook_getName(struct mwAddressBook *book) { + g_return_val_if_fail(book != NULL, NULL); + return book->name; +} + + +struct mwDirectory *mwDirectory_new(struct mwAddressBook *book) { + struct mwDirectory *dir; + + g_return_val_if_fail(book != NULL, NULL); + g_return_val_if_fail(book->service != NULL, NULL); + + dir = g_new0(struct mwDirectory, 1); + dir->service = book->service; + dir->book = book; + dir->state = mwDirectory_NEW; + + return dir; +} + + +enum mwDirectoryState mwDirectory_getState(struct mwDirectory *dir) { + g_return_val_if_fail(dir != NULL, mwDirectory_UNKNOWN); + return dir->state; +} + + +void mwDirectory_setClientData(struct mwDirectory *dir, + gpointer data, GDestroyNotify clear) { + + g_return_if_fail(dir != NULL); + mw_datum_set(&dir->client_data, data, clear); +} + + +gpointer mwDirectory_getClientData(struct mwDirectory *dir) { + g_return_val_if_fail(dir != NULL, NULL); + return mw_datum_get(&dir->client_data); +} + + +void mwDirectory_removeClientData(struct mwDirectory *dir) { + g_return_if_fail(dir != NULL); + mw_datum_clear(&dir->client_data); +} + + +struct mwServiceDirectory *mwDirectory_getService(struct mwDirectory *dir) { + g_return_val_if_fail(dir != NULL, NULL); + g_return_val_if_fail(dir->book != NULL, NULL); + return dir->book->service; +} + + +struct mwAddressBook *mwDirectory_getAddressBook(struct mwDirectory *dir) { + g_return_val_if_fail(dir != NULL, NULL); + return dir->book; +} + + +static int dir_open(struct mwDirectory *dir) { + struct mwServiceDirectory *srvc; + struct mwChannel *chan; + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + g_return_val_if_fail(dir != NULL, -1); + + srvc = dir->service; + g_return_val_if_fail(srvc != NULL, -1); + + chan = srvc->channel; + g_return_val_if_fail(chan != NULL, -1); + + b = mwPutBuffer_new(); + guint32_put(b, map_request(dir)); + + /* unsure about these three bytes */ + gboolean_put(b, FALSE); + guint16_put(b, 0x0000); + + guint32_put(b, dir->book->id); + mwString_put(b, dir->book->name); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_send(chan, action_open, &o); + mwOpaque_clear(&o); + + return ret; +} + + +int mwDirectory_open(struct mwDirectory *dir, mwSearchHandler cb) { + g_return_val_if_fail(dir != NULL, -1); + g_return_val_if_fail(cb != NULL, -1); + g_return_val_if_fail(MW_DIRECTORY_IS_NEW(dir), -1); + + dir->state = mwDirectory_PENDING; + dir->handler = cb; + + return dir_open(dir); +} + + +int mwDirectory_next(struct mwDirectory *dir) { + struct mwServiceDirectory *srvc; + struct mwChannel *chan; + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + g_return_val_if_fail(dir != NULL, -1); + g_return_val_if_fail(MW_DIRECTORY_IS_OPEN(dir), -1); + + srvc = dir->service; + g_return_val_if_fail(srvc != NULL, -1); + + chan = srvc->channel; + g_return_val_if_fail(chan != NULL, -1); + + b = mwPutBuffer_new(); + guint32_put(b, map_request(dir)); + guint32_put(b, dir->id); + guint16_put(b, 0xffff); /* some magic? */ + guint32_put(b, 0x00000000); /* next results */ + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_send(chan, action_search, &o); + mwOpaque_clear(&o); + + return ret; +} + + +int mwDirectory_previous(struct mwDirectory *dir) { + struct mwServiceDirectory *srvc; + struct mwChannel *chan; + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + g_return_val_if_fail(dir != NULL, -1); + g_return_val_if_fail(MW_DIRECTORY_IS_OPEN(dir), -1); + + srvc = dir->service; + g_return_val_if_fail(srvc != NULL, -1); + + chan = srvc->channel; + g_return_val_if_fail(chan != NULL, -1); + + b = mwPutBuffer_new(); + guint32_put(b, map_request(dir)); + guint32_put(b, dir->id); + guint16_put(b, 0x0061); /* some magic? */ + guint32_put(b, 0x00000001); /* prev results */ + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_send(chan, action_search, &o); + mwOpaque_clear(&o); + + return ret; +} + + +int mwDirectory_search(struct mwDirectory *dir, const char *query) { + struct mwServiceDirectory *srvc; + struct mwChannel *chan; + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + g_return_val_if_fail(dir != NULL, -1); + g_return_val_if_fail(MW_DIRECTORY_IS_OPEN(dir), -1); + g_return_val_if_fail(query != NULL, -1); + g_return_val_if_fail(*query != '\0', -1); + + srvc = dir->service; + g_return_val_if_fail(srvc != NULL, -1); + + chan = srvc->channel; + g_return_val_if_fail(chan != NULL, -1); + + b = mwPutBuffer_new(); + guint32_put(b, map_request(dir)); + guint32_put(b, dir->id); + guint16_put(b, 0x0061); /* some magic? */ + guint32_put(b, 0x00000008); /* seek results */ + mwString_put(b, query); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_send(chan, action_search, &o); + mwOpaque_clear(&o); + + return ret; +} + + +static int dir_close(struct mwDirectory *dir) { + struct mwServiceDirectory *srvc; + struct mwChannel *chan; + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + g_return_val_if_fail(dir != NULL, -1); + + srvc = dir->service; + g_return_val_if_fail(srvc != NULL, -1); + + chan = srvc->channel; + g_return_val_if_fail(chan != NULL, -1); + + b = mwPutBuffer_new(); + guint32_put(b, next_request_id(dir->service)); + guint32_put(b, dir->id); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_send(chan, action_close, &o); + mwOpaque_clear(&o); + + return ret; +} + + +int mwDirectory_destroy(struct mwDirectory *dir) { + int ret = 0; + + g_return_val_if_fail(dir != NULL, -1); + + if(MW_DIRECTORY_IS_OPEN(dir) || MW_DIRECTORY_IS_PENDING(dir)) { + ret = dir_close(dir); + } + dir_remove(dir); + + return ret; +} + diff --git a/src/srvc_ft.c b/src/srvc_ft.c new file mode 100644 index 0000000..0564732 --- /dev/null +++ b/src/srvc_ft.c @@ -0,0 +1,654 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include + +#include "mw_channel.h" +#include "mw_common.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_srvc_ft.h" +#include "mw_util.h" + + +#define PROTOCOL_TYPE 0x00000000 +#define PROTOCOL_VER 0x00000001 + + +/** send-on-channel type: FT transfer data */ +#define msg_TRANSFER 0x0001 + + +/** ack received transfer data */ +#define msg_RECEIVED 0x0002 + + +struct mwServiceFileTransfer { + struct mwService service; + + struct mwFileTransferHandler *handler; + GList *transfers; +}; + + +struct mwFileTransfer { + struct mwServiceFileTransfer *service; + + struct mwChannel *channel; + struct mwIdBlock who; + + enum mwFileTransferState state; + + char *filename; + char *message; + + guint32 size; + guint32 remaining; + + struct mw_datum client_data; +}; + + +/** momentarily places a mwLoginInfo into a mwIdBlock */ +static void login_into_id(struct mwIdBlock *to, struct mwLoginInfo *from) { + to->user = from->user_id; + to->community = from->community; +} + + +static const char *ft_state_str(enum mwFileTransferState state) { + switch(state) { + case mwFileTransfer_NEW: + return "new"; + + case mwFileTransfer_PENDING: + return "pending"; + + case mwFileTransfer_OPEN: + return "open"; + + case mwFileTransfer_CANCEL_LOCAL: + return "cancelled locally"; + + case mwFileTransfer_CANCEL_REMOTE: + return "cancelled remotely"; + + case mwFileTransfer_DONE: + return "done"; + + case mwFileTransfer_ERROR: + return "error"; + + case mwFileTransfer_UNKNOWN: + default: + return "UNKNOWN"; + } +} + + +static void ft_state(struct mwFileTransfer *ft, + enum mwFileTransferState state) { + + g_return_if_fail(ft != NULL); + + if(ft->state == state) return; + + g_info("setting ft (%s, %s) state: %s", + NSTR(ft->who.user), NSTR(ft->who.community), + ft_state_str(state)); + + ft->state = state; +} + + +static void recv_channelCreate(struct mwServiceFileTransfer *srvc, + struct mwChannel *chan, + struct mwMsgChannelCreate *msg) { + + struct mwFileTransferHandler *handler; + struct mwGetBuffer *b; + + char *fnm, *txt; + guint32 size, junk; + gboolean b_err; + + g_return_if_fail(srvc->handler != NULL); + handler = srvc->handler; + + b = mwGetBuffer_wrap(&msg->addtl); + + guint32_get(b, &junk); /* unknown */ + mwString_get(b, &fnm); /* offered filename */ + mwString_get(b, &txt); /* offering message */ + guint32_get(b, &size); /* size of offered file */ + guint32_get(b, &junk); /* unknown */ + /* and we just skip an unknown guint16 at the end */ + + b_err = mwGetBuffer_error(b); + mwGetBuffer_free(b); + + if(b_err) { + g_warning("bad/malformed addtl in File Transfer service"); + mwChannel_destroy(chan, ERR_FAILURE, NULL); + + } else { + struct mwIdBlock idb; + struct mwFileTransfer *ft; + + login_into_id(&idb, mwChannel_getUser(chan)); + ft = mwFileTransfer_new(srvc, &idb, txt, fnm, size); + ft->channel = chan; + ft_state(ft, mwFileTransfer_PENDING); + + mwChannel_setServiceData(chan, ft, NULL); + + if(handler->ft_offered) + handler->ft_offered(ft); + } + + g_free(fnm); + g_free(txt); +} + + +static void recv_channelAccept(struct mwServiceFileTransfer *srvc, + struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + + struct mwFileTransferHandler *handler; + struct mwFileTransfer *ft; + + g_return_if_fail(srvc->handler != NULL); + handler = srvc->handler; + + ft = mwChannel_getServiceData(chan); + g_return_if_fail(ft != NULL); + + ft_state(ft, mwFileTransfer_OPEN); + + if(handler->ft_opened) + handler->ft_opened(ft); +} + + +static void recv_channelDestroy(struct mwServiceFileTransfer *srvc, + struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + + struct mwFileTransferHandler *handler; + struct mwFileTransfer *ft; + guint32 code; + + code = msg->reason; + + g_return_if_fail(srvc->handler != NULL); + handler = srvc->handler; + + ft = mwChannel_getServiceData(chan); + g_return_if_fail(ft != NULL); + + ft->channel = NULL; + + if(! mwFileTransfer_isDone(ft)) + ft_state(ft, mwFileTransfer_CANCEL_REMOTE); + + mwFileTransfer_close(ft, code); +} + + +static void recv_TRANSFER(struct mwFileTransfer *ft, + struct mwOpaque *data) { + + struct mwServiceFileTransfer *srvc; + struct mwFileTransferHandler *handler; + + srvc = ft->service; + handler = srvc->handler; + + g_return_if_fail(mwFileTransfer_isOpen(ft)); + + if(data->len > ft->remaining) { + /* @todo handle error */ + + } else { + ft->remaining -= data->len; + + if(! ft->remaining) + ft_state(ft, mwFileTransfer_DONE); + + if(handler->ft_recv) + handler->ft_recv(ft, data); + } +} + + +static void recv_RECEIVED(struct mwFileTransfer *ft, + struct mwOpaque *data) { + + struct mwServiceFileTransfer *srvc; + struct mwFileTransferHandler *handler; + + srvc = ft->service; + handler = srvc->handler; + + if(! ft->remaining) + ft_state(ft, mwFileTransfer_DONE); + + if(handler->ft_ack) + handler->ft_ack(ft); + + if(! ft->remaining) + mwFileTransfer_close(ft, mwFileTransfer_SUCCESS); +} + + +static void recv(struct mwService *srvc, struct mwChannel *chan, + guint16 type, struct mwOpaque *data) { + + struct mwFileTransfer *ft; + + ft = mwChannel_getServiceData(chan); + g_return_if_fail(ft != NULL); + + switch(type) { + case msg_TRANSFER: + recv_TRANSFER(ft, data); + break; + + case msg_RECEIVED: + recv_RECEIVED(ft, data); + break; + + default: + mw_mailme_opaque(data, "unknown message in ft service: 0x%04x", type); + } +} + + +static void clear(struct mwServiceFileTransfer *srvc) { + struct mwFileTransferHandler *h; + + h = srvc->handler; + if(h && h->clear) + h->clear(srvc); + srvc->handler = NULL; +} + + +static const char *name(struct mwService *srvc) { + return "File Transfer"; +} + + +static const char *desc(struct mwService *srvc) { + return "Provides file transfer capabilities through the community server"; +} + + +static void start(struct mwService *srvc) { + mwService_started(srvc); +} + + +static void stop(struct mwServiceFileTransfer *srvc) { + while(srvc->transfers) { + mwFileTransfer_free(srvc->transfers->data); + } + + mwService_stopped(MW_SERVICE(srvc)); +} + + +struct mwServiceFileTransfer * +mwServiceFileTransfer_new(struct mwSession *session, + struct mwFileTransferHandler *handler) { + + struct mwServiceFileTransfer *srvc_ft; + struct mwService *srvc; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(handler != NULL, NULL); + + srvc_ft = g_new0(struct mwServiceFileTransfer, 1); + srvc = MW_SERVICE(srvc_ft); + + mwService_init(srvc, session, mwService_FILE_TRANSFER); + srvc->recv_create = (mwService_funcRecvCreate) recv_channelCreate; + srvc->recv_accept = (mwService_funcRecvAccept) recv_channelAccept; + srvc->recv_destroy = (mwService_funcRecvDestroy) recv_channelDestroy; + srvc->recv = recv; + srvc->clear = (mwService_funcClear) clear; + srvc->get_name = name; + srvc->get_desc = desc; + srvc->start = start; + srvc->stop = (mwService_funcStop) stop; + + srvc_ft->handler = handler; + + return srvc_ft; +} + + +struct mwFileTransferHandler * +mwServiceFileTransfer_getHandler(struct mwServiceFileTransfer *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->handler; +} + + +const GList * +mwServiceFileTransfer_getTransfers(struct mwServiceFileTransfer *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->transfers; +} + + +struct mwFileTransfer * +mwFileTransfer_new(struct mwServiceFileTransfer *srvc, + const struct mwIdBlock *who, const char *msg, + const char *filename, guint32 filesize) { + + struct mwFileTransfer *ft; + + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(who != NULL, NULL); + + ft = g_new0(struct mwFileTransfer, 1); + ft->service = srvc; + mwIdBlock_clone(&ft->who, who); + ft->filename = g_strdup(filename); + ft->message = g_strdup(msg); + ft->size = ft->remaining = filesize; + + ft_state(ft, mwFileTransfer_NEW); + + /* stick a reference in the service */ + srvc->transfers = g_list_prepend(srvc->transfers, ft); + + return ft; +} + + +struct mwServiceFileTransfer * +mwFileTransfer_getService(struct mwFileTransfer *ft) { + g_return_val_if_fail(ft != NULL, NULL); + return ft->service; +} + + +enum mwFileTransferState +mwFileTransfer_getState(struct mwFileTransfer *ft) { + g_return_val_if_fail(ft != NULL, mwFileTransfer_UNKNOWN); + return ft->state; +} + + +const struct mwIdBlock * +mwFileTransfer_getUser(struct mwFileTransfer *ft) { + g_return_val_if_fail(ft != NULL, NULL); + return &ft->who; +} + + +const char * +mwFileTransfer_getMessage(struct mwFileTransfer *ft) { + g_return_val_if_fail(ft != NULL, NULL); + return ft->message; +} + + +const char * +mwFileTransfer_getFileName(struct mwFileTransfer *ft) { + g_return_val_if_fail(ft != NULL, NULL); + return ft->filename; +} + + +guint32 mwFileTransfer_getFileSize(struct mwFileTransfer *ft) { + g_return_val_if_fail(ft != NULL, 0); + return ft->size; +} + + +guint32 mwFileTransfer_getRemaining(struct mwFileTransfer *ft) { + g_return_val_if_fail(ft != NULL, 0); + return ft->remaining; +} + + +int mwFileTransfer_accept(struct mwFileTransfer *ft) { + struct mwServiceFileTransfer *srvc; + struct mwFileTransferHandler *handler; + int ret; + + g_return_val_if_fail(ft != NULL, -1); + g_return_val_if_fail(ft->channel != NULL, -1); + g_return_val_if_fail(mwFileTransfer_isPending(ft), -1); + g_return_val_if_fail(mwChannel_isIncoming(ft->channel), -1); + g_return_val_if_fail(mwChannel_isState(ft->channel, mwChannel_WAIT), -1); + + g_return_val_if_fail(ft->service != NULL, -1); + srvc = ft->service; + + g_return_val_if_fail(srvc->handler != NULL, -1); + handler = srvc->handler; + + ret = mwChannel_accept(ft->channel); + + if(ret) { + mwFileTransfer_close(ft, ERR_FAILURE); + + } else { + ft_state(ft, mwFileTransfer_OPEN); + if(handler->ft_opened) + handler->ft_opened(ft); + } + + return ret; +} + + +static void ft_create_chan(struct mwFileTransfer *ft) { + struct mwSession *s; + struct mwChannelSet *cs; + struct mwChannel *chan; + struct mwLoginInfo *login; + struct mwPutBuffer *b; + + /* we only should be calling this if there isn't a channel already + associated with the conversation */ + g_return_if_fail(ft != NULL); + g_return_if_fail(mwFileTransfer_isNew(ft)); + g_return_if_fail(ft->channel == NULL); + + s = mwService_getSession(MW_SERVICE(ft->service)); + cs = mwSession_getChannels(s); + + chan = mwChannel_newOutgoing(cs); + mwChannel_setService(chan, MW_SERVICE(ft->service)); + mwChannel_setProtoType(chan, PROTOCOL_TYPE); + mwChannel_setProtoVer(chan, PROTOCOL_VER); + + /* offer all known ciphers */ + mwChannel_populateSupportedCipherInstances(chan); + + /* set the target */ + login = mwChannel_getUser(chan); + login->user_id = g_strdup(ft->who.user); + login->community = g_strdup(ft->who.community); + + /* compose the addtl create */ + b = mwPutBuffer_new(); + guint32_put(b, 0x00); + mwString_put(b, ft->filename); + mwString_put(b, ft->message); + guint32_put(b, ft->size); + guint32_put(b, 0x00); + guint16_put(b, 0x00); + + mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b); + + ft->channel = mwChannel_create(chan)? NULL: chan; + if(ft->channel) { + mwChannel_setServiceData(ft->channel, ft, NULL); + } +} + + +int mwFileTransfer_offer(struct mwFileTransfer *ft) { + struct mwServiceFileTransfer *srvc; + struct mwFileTransferHandler *handler; + + g_return_val_if_fail(ft != NULL, -1); + g_return_val_if_fail(ft->channel == NULL, -1); + g_return_val_if_fail(mwFileTransfer_isNew(ft), -1); + + g_return_val_if_fail(ft->service != NULL, -1); + srvc = ft->service; + + g_return_val_if_fail(srvc->handler != NULL, -1); + handler = srvc->handler; + + ft_create_chan(ft); + if(ft->channel) { + ft_state(ft, mwFileTransfer_PENDING); + } else { + ft_state(ft, mwFileTransfer_ERROR); + mwFileTransfer_close(ft, ERR_FAILURE); + } + + return 0; +} + + +int mwFileTransfer_close(struct mwFileTransfer *ft, guint32 code) { + struct mwServiceFileTransfer *srvc; + struct mwFileTransferHandler *handler; + int ret = 0; + + g_return_val_if_fail(ft != NULL, -1); + + if(mwFileTransfer_isOpen(ft)) + ft_state(ft, mwFileTransfer_CANCEL_LOCAL); + + if(ft->channel) { + ret = mwChannel_destroy(ft->channel, code, NULL); + ft->channel = NULL; + } + + srvc = ft->service; + g_return_val_if_fail(srvc != NULL, ret); + + handler = srvc->handler; + g_return_val_if_fail(handler != NULL, ret); + + if(handler->ft_closed) + handler->ft_closed(ft, code); + + return ret; +} + + +void mwFileTransfer_free(struct mwFileTransfer *ft) { + struct mwServiceFileTransfer *srvc; + + if(! ft) return; + + srvc = ft->service; + if(srvc) + srvc->transfers = g_list_remove(srvc->transfers, ft); + + if(ft->channel) { + mwChannel_destroy(ft->channel, mwFileTransfer_SUCCESS, NULL); + ft->channel = NULL; + } + + mwFileTransfer_removeClientData(ft); + + mwIdBlock_clear(&ft->who); + g_free(ft->filename); + g_free(ft->message); + g_free(ft); +} + + +int mwFileTransfer_send(struct mwFileTransfer *ft, + struct mwOpaque *data) { + + struct mwChannel *chan; + int ret; + + g_return_val_if_fail(ft != NULL, -1); + g_return_val_if_fail(mwFileTransfer_isOpen(ft), -1); + g_return_val_if_fail(ft->channel != NULL, -1); + chan = ft->channel; + + g_return_val_if_fail(mwChannel_isOutgoing(chan), -1); + + if(data->len > ft->remaining) { + /* @todo handle error */ + return -1; + } + + ret = mwChannel_send(chan, msg_TRANSFER, data); + if(! ret) ft->remaining -= data->len; + + /* we're not done until we receive an ACK for the last piece of + outgoing data */ + + return ret; +} + + +int mwFileTransfer_ack(struct mwFileTransfer *ft) { + struct mwChannel *chan; + + g_return_val_if_fail(ft != NULL, -1); + + chan = ft->channel; + g_return_val_if_fail(chan != NULL, -1); + g_return_val_if_fail(mwChannel_isIncoming(chan), -1); + + return mwChannel_sendEncrypted(chan, msg_RECEIVED, NULL, FALSE); +} + + +void mwFileTransfer_setClientData(struct mwFileTransfer *ft, + gpointer data, GDestroyNotify clean) { + g_return_if_fail(ft != NULL); + mw_datum_set(&ft->client_data, data, clean); +} + + +gpointer mwFileTransfer_getClientData(struct mwFileTransfer *ft) { + g_return_val_if_fail(ft != NULL, NULL); + return mw_datum_get(&ft->client_data); +} + + +void mwFileTransfer_removeClientData(struct mwFileTransfer *ft) { + g_return_if_fail(ft != NULL); + mw_datum_clear(&ft->client_data); +} + diff --git a/src/srvc_im.c b/src/srvc_im.c new file mode 100644 index 0000000..5930687 --- /dev/null +++ b/src/srvc_im.c @@ -0,0 +1,1075 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include + +#include "mw_channel.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_srvc_im.h" +#include "mw_util.h" + + +#define PROTOCOL_TYPE 0x00001000 +#define PROTOCOL_VER 0x00000003 + + +/* data for the addtl blocks of channel creation */ +#define mwImAddtlA_NORMAL 0x00000001 + +#define mwImAddtlB_NORMAL 0x00000001 /**< standard */ +#define mwImAddtlB_PRECONF 0x00000019 /**< pre-conference chat */ +#define mwImAddtlB_NOTESBUDDY 0x00033453 /**< notesbuddy */ + +#define mwImAddtlC_NORMAL 0x00000002 + + +/* send-on-channel message type */ +#define msg_MESSAGE 0x0064 /**< IM message */ + + +#define BREAKUP 2048 + + +/* which type of im? */ +enum mwImType { + mwIm_TEXT = 0x00000001, /**< text message */ + mwIm_DATA = 0x00000002, /**< status message (usually) */ +}; + + +/* which type of data im? */ +enum mwImDataType { + mwImData_TYPING = 0x00000001, /**< common use typing indicator */ + mwImData_SUBJECT = 0x00000003, /**< notesbuddy IM topic */ + mwImData_HTML = 0x00000004, /**< notesbuddy HTML message */ + mwImData_MIME = 0x00000005, /**< notesbuddy MIME message, w/image */ + mwImData_TIMESTAMP = 0x00000006, /**< notesbuddy timestamp */ + + mwImData_INVITE = 0x0000000a, /**< Places invitation */ + + mwImData_MULTI_START = 0x00001388, + mwImData_MULTI_STOP = 0x00001389, +}; + + +/** @todo might be appropriate to make a couple of hashtables to + reference conversations by channel and target */ +struct mwServiceIm { + struct mwService service; + + enum mwImClientType features; + + struct mwImHandler *handler; + GList *convs; /**< list of struct im_convo */ +}; + + +struct mwConversation { + struct mwServiceIm *service; /**< owning service */ + struct mwChannel *channel; /**< channel */ + struct mwIdBlock target; /**< conversation target */ + + gboolean ext_id; /**< special treatment, external ID */ + + /** state of the conversation, based loosely on the state of its + underlying channel */ + enum mwConversationState state; + + enum mwImClientType features; + + GString *multi; /**< buffer for multi-chunk message */ + enum mwImSendType multi_type; /**< type of incoming multi-chunk message */ + + struct mw_datum client_data; +}; + + +/** momentarily places a mwLoginInfo into a mwIdBlock */ +static void login_into_id(struct mwIdBlock *to, struct mwLoginInfo *from) { + to->user = from->user_id; + to->community = from->community; +} + + +static struct mwConversation *convo_find_by_user(struct mwServiceIm *srvc, + struct mwIdBlock *to) { + GList *l; + + for(l = srvc->convs; l; l = l->next) { + struct mwConversation *c = l->data; + if(mwIdBlock_equal(&c->target, to)) + return c; + } + + return NULL; +} + + +static const char *conv_state_str(enum mwConversationState state) { + switch(state) { + case mwConversation_CLOSED: + return "closed"; + + case mwConversation_OPEN: + return "open"; + + case mwConversation_PENDING: + return "pending"; + + case mwConversation_UNKNOWN: + default: + return "UNKNOWN"; + } +} + + +static void convo_set_state(struct mwConversation *conv, + enum mwConversationState state) { + + g_return_if_fail(conv != NULL); + + if(conv->state != state) { + g_info("setting conversation (%s, %s) state: %s", + NSTR(conv->target.user), NSTR(conv->target.community), + conv_state_str(state)); + conv->state = state; + } +} + + +struct mwConversation *mwServiceIm_findConversation(struct mwServiceIm *srvc, + struct mwIdBlock *to) { + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(to != NULL, NULL); + + return convo_find_by_user(srvc, to); +} + + +struct mwConversation *mwServiceIm_getConversation(struct mwServiceIm *srvc, + struct mwIdBlock *to) { + struct mwConversation *c; + + g_return_val_if_fail(srvc != NULL, NULL); + g_return_val_if_fail(to != NULL, NULL); + + c = convo_find_by_user(srvc, to); + if(! c) { + c = g_new0(struct mwConversation, 1); + c->service = srvc; + mwIdBlock_clone(&c->target, to); + c->state = mwConversation_CLOSED; + c->features = srvc->features; + + /* mark external users */ + /* c->ext_id = g_str_has_prefix(to->user, "@E "); */ + + srvc->convs = g_list_prepend(srvc->convs, c); + } + + return c; +} + + +static void convo_create_chan(struct mwConversation *c) { + struct mwSession *s; + struct mwChannelSet *cs; + struct mwChannel *chan; + struct mwLoginInfo *login; + struct mwPutBuffer *b; + + /* we only should be calling this if there isn't a channel already + associated with the conversation */ + g_return_if_fail(c != NULL); + g_return_if_fail(mwConversation_isPending(c)); + g_return_if_fail(c->channel == NULL); + + s = mwService_getSession(MW_SERVICE(c->service)); + cs = mwSession_getChannels(s); + + chan = mwChannel_newOutgoing(cs); + mwChannel_setService(chan, MW_SERVICE(c->service)); + mwChannel_setProtoType(chan, PROTOCOL_TYPE); + mwChannel_setProtoVer(chan, PROTOCOL_VER); + + /* offer all known ciphers */ + mwChannel_populateSupportedCipherInstances(chan); + + /* set the target */ + login = mwChannel_getUser(chan); + login->user_id = g_strdup(c->target.user); + login->community = g_strdup(c->target.community); + + /* compose the addtl create, with optional FANCY HTML! */ + b = mwPutBuffer_new(); + guint32_put(b, mwImAddtlA_NORMAL); + guint32_put(b, c->features); + mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b); + + c->channel = mwChannel_create(chan)? NULL: chan; + if(c->channel) { + mwChannel_setServiceData(c->channel, c, NULL); + } +} + + +void mwConversation_open(struct mwConversation *conv) { + g_return_if_fail(conv != NULL); + g_return_if_fail(mwConversation_isClosed(conv)); + + convo_set_state(conv, mwConversation_PENDING); + convo_create_chan(conv); +} + + +static void convo_opened(struct mwConversation *conv) { + struct mwImHandler *h = NULL; + + g_return_if_fail(conv != NULL); + g_return_if_fail(conv->service != NULL); + + convo_set_state(conv, mwConversation_OPEN); + h = conv->service->handler; + + g_return_if_fail(h != NULL); + + if(h->conversation_opened) + h->conversation_opened(conv); +} + + +static void convo_free(struct mwConversation *conv) { + struct mwServiceIm *srvc; + + mwConversation_removeClientData(conv); + + srvc = conv->service; + srvc->convs = g_list_remove_all(srvc->convs, conv); + + mwIdBlock_clear(&conv->target); + g_free(conv); +} + + +static int send_accept(struct mwConversation *c) { + + struct mwChannel *chan = c->channel; + struct mwSession *s = mwChannel_getSession(chan); + struct mwUserStatus *stat = mwSession_getUserStatus(s); + + struct mwPutBuffer *b; + struct mwOpaque *o; + + b = mwPutBuffer_new(); + guint32_put(b, mwImAddtlA_NORMAL); + guint32_put(b, c->features); + guint32_put(b, mwImAddtlC_NORMAL); + mwUserStatus_put(b, stat); + + o = mwChannel_getAddtlAccept(chan); + mwOpaque_clear(o); + mwPutBuffer_finalize(o, b); + + return mwChannel_accept(chan); +} + + +static void recv_channelCreate(struct mwService *srvc, + struct mwChannel *chan, + struct mwMsgChannelCreate *msg) { + + /* - ensure it's the right service,proto,and proto ver + - check the opaque for the right opaque junk + - if not, close channel + - compose & send a channel accept + */ + + struct mwServiceIm *srvc_im = (struct mwServiceIm *) srvc; + struct mwImHandler *handler; + struct mwSession *s; + struct mwUserStatus *stat; + guint32 x, y, z; + struct mwGetBuffer *b; + struct mwConversation *c = NULL; + struct mwIdBlock idb; + + handler = srvc_im->handler; + s = mwChannel_getSession(chan); + stat = mwSession_getUserStatus(s); + + /* ensure the appropriate service/proto/ver */ + x = mwChannel_getServiceId(chan); + y = mwChannel_getProtoType(chan); + z = mwChannel_getProtoVer(chan); + + if( (x != mwService_IM) || (y != PROTOCOL_TYPE) || (z != PROTOCOL_VER) ) { + g_warning("unacceptable service, proto, ver:" + " 0x%08x, 0x%08x, 0x%08x", x, y, z); + mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL); + return; + } + + /* act upon the values in the addtl block */ + b = mwGetBuffer_wrap(&msg->addtl); + guint32_get(b, &x); + guint32_get(b, &y); + z = (guint) mwGetBuffer_error(b); + mwGetBuffer_free(b); + + if(z /* buffer error, BOOM! */ ) { + g_warning("bad/malformed addtl in IM service"); + mwChannel_destroy(chan, ERR_FAILURE, NULL); + return; + + } else if(x != mwImAddtlA_NORMAL) { + g_message("unknown params: 0x%08x, 0x%08x", x, y); + mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL); + return; + + } else if(y == mwImAddtlB_PRECONF) { + if(! handler->place_invite) { + g_info("rejecting place-invite channel"); + mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL); + return; + + } else { + g_info("accepting place-invite channel"); + } + + } else if(y != mwImClient_PLAIN && y != srvc_im->features) { + /** reject what we do not understand */ + mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL); + return; + + } else if(stat->status == mwStatus_BUSY) { + g_info("rejecting IM channel due to DND status"); + mwChannel_destroy(chan, ERR_CLIENT_USER_DND, NULL); + return; + } + + login_into_id(&idb, mwChannel_getUser(chan)); + +#if 0 + c = convo_find_by_user(srvc_im, &idb); +#endif + + if(! c) { + c = g_new0(struct mwConversation, 1); + c->service = srvc_im; + srvc_im->convs = g_list_prepend(srvc_im->convs, c); + } + +#if 0 + /* we're going to re-associate any existing conversations with this + new channel. That means closing any existing ones */ + if(c->channel) { + g_info("closing existing IM channel 0x%08x", mwChannel_getId(c->channel)); + mwConversation_close(c, ERR_SUCCESS); + } +#endif + + /* set up the conversation with this channel, target, and be fancy + if the other side requested it */ + c->channel = chan; + mwIdBlock_clone(&c->target, &idb); + c->features = y; + convo_set_state(c, mwConversation_PENDING); + mwChannel_setServiceData(c->channel, c, NULL); + + if(send_accept(c)) { + g_warning("sending IM channel accept failed"); + mwConversation_free(c); + + } else { + convo_opened(c); + } +} + + +static void recv_channelAccept(struct mwService *srvc, struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + + struct mwConversation *conv; + + conv = mwChannel_getServiceData(chan); + if(! conv) { + g_warning("received channel accept for non-existant conversation"); + mwChannel_destroy(chan, ERR_FAILURE, NULL); + return; + } + + convo_opened(conv); +} + + +static void recv_channelDestroy(struct mwService *srvc, struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + + struct mwConversation *c; + + c = mwChannel_getServiceData(chan); + g_return_if_fail(c != NULL); + + c->channel = NULL; + + if(mwChannel_isState(chan, mwChannel_ERROR)) { + + /* checking for failure on the receiving end to accept html + messages. Fail-over to a non-html format on a new channel for + the convo */ + if(c->features != mwImClient_PLAIN + && (msg->reason == ERR_IM_NOT_REGISTERED || + msg->reason == ERR_SERVICE_NO_SUPPORT)) { + + g_debug("falling back on a plaintext conversation"); + c->features = mwImClient_PLAIN; + convo_create_chan(c); + return; + } + } + + mwConversation_close(c, msg->reason); +} + + +static void convo_recv(struct mwConversation *conv, enum mwImSendType type, + gconstpointer msg) { + + struct mwServiceIm *srvc; + struct mwImHandler *handler; + + g_return_if_fail(conv != NULL); + + srvc = conv->service; + g_return_if_fail(srvc != NULL); + + handler = srvc->handler; + if(handler && handler->conversation_recv) + handler->conversation_recv(conv, type, msg); +} + + +static void convo_multi_start(struct mwConversation *conv) { + g_return_if_fail(conv->multi == NULL); + conv->multi = g_string_new(NULL); +} + + +static void convo_multi_stop(struct mwConversation *conv) { + + g_return_if_fail(conv->multi != NULL); + + /* actually send it */ + convo_recv(conv, conv->multi_type, conv->multi->str); + + /* clear up the multi buffer */ + g_string_free(conv->multi, TRUE); + conv->multi = NULL; +} + + +static void recv_text(struct mwServiceIm *srvc, struct mwChannel *chan, + struct mwGetBuffer *b) { + + struct mwConversation *c; + char *text = NULL; + + mwString_get(b, &text); + + if(! text) return; + + c = mwChannel_getServiceData(chan); + if(c) { + if(c->multi) { + g_string_append(c->multi, text); + + } else { + convo_recv(c, mwImSend_PLAIN, text); + } + } + + g_free(text); +} + + +static void convo_invite(struct mwConversation *conv, + struct mwOpaque *o) { + + struct mwServiceIm *srvc; + struct mwImHandler *handler; + + struct mwGetBuffer *b; + char *title, *name, *msg; + char *unkn, *host; + guint16 with_who; + + g_info("convo_invite"); + + srvc = conv->service; + handler = srvc->handler; + + g_return_if_fail(handler != NULL); + g_return_if_fail(handler->place_invite != NULL); + + b = mwGetBuffer_wrap(o); + mwGetBuffer_advance(b, 4); + mwString_get(b, &title); + mwString_get(b, &msg); + mwGetBuffer_advance(b, 19); + mwString_get(b, &name); + + /* todo: add a mwString_skip */ + mwString_get(b, &unkn); + mwString_get(b, &host); + g_free(unkn); + g_free(host); + + /* hack. Sometimes incoming convo invitation channels won't have the + owner id block filled in */ + guint16_get(b, &with_who); + if(with_who && !conv->target.user) { + char *login; + mwString_get(b, &conv->target.user); + mwString_get(b, &login); g_free(login); + mwString_get(b, &conv->target.community); + } + + if(mwGetBuffer_error(b)) { + mw_mailme_opaque(o, "problem with place invite over IM service"); + } else { + handler->place_invite(conv, msg, title, name); + } + + mwGetBuffer_free(b); + g_free(msg); + g_free(title); + g_free(name); +} + + +static void recv_data(struct mwServiceIm *srvc, struct mwChannel *chan, + struct mwGetBuffer *b) { + + struct mwConversation *conv; + guint32 type, subtype; + struct mwOpaque o = { 0, 0 }; + char *x; + + guint32_get(b, &type); + guint32_get(b, &subtype); + mwOpaque_get(b, &o); + + if(mwGetBuffer_error(b)) { + mwOpaque_clear(&o); + return; + } + + conv = mwChannel_getServiceData(chan); + if(! conv) return; + + switch(type) { + case mwImData_TYPING: + convo_recv(conv, mwImSend_TYPING, GINT_TO_POINTER(! subtype)); + break; + + case mwImData_HTML: + if(o.len) { + if(conv->multi) { + g_string_append_len(conv->multi, (char *) o.data, o.len); + conv->multi_type = mwImSend_HTML; + + } else { + x = g_strndup((char *) o.data, o.len); + convo_recv(conv, mwImSend_HTML, x); + g_free(x); + } + } + break; + + case mwImData_SUBJECT: + x = g_strndup((char *) o.data, o.len); + convo_recv(conv, mwImSend_SUBJECT, x); + g_free(x); + break; + + case mwImData_MIME: + if(conv->multi) { + g_string_append_len(conv->multi, (char *) o.data, o.len); + conv->multi_type = mwImSend_MIME; + + } else { + x = g_strndup((char *) o.data, o.len); + convo_recv(conv, mwImSend_MIME, x); + g_free(x); + } + break; + + case mwImData_TIMESTAMP: + /* todo */ + break; + + case mwImData_INVITE: + convo_invite(conv, &o); + break; + + case mwImData_MULTI_START: + convo_multi_start(conv); + break; + + case mwImData_MULTI_STOP: + convo_multi_stop(conv); + break; + + default: + + mw_mailme_opaque(&o, "unknown data message type in IM service:" + " (0x%08x, 0x%08x)", type, subtype); + } + + mwOpaque_clear(&o); +} + + +static void recv(struct mwService *srvc, struct mwChannel *chan, + guint16 type, struct mwOpaque *data) { + + /* - ensure message type is something we want + - parse message type into either mwIMText or mwIMData + - handle + */ + + struct mwGetBuffer *b; + guint32 mt; + + g_return_if_fail(type == msg_MESSAGE); + + b = mwGetBuffer_wrap(data); + guint32_get(b, &mt); + + if(mwGetBuffer_error(b)) { + g_warning("failed to parse message for IM service"); + mwGetBuffer_free(b); + return; + } + + switch(mt) { + case mwIm_TEXT: + recv_text((struct mwServiceIm *) srvc, chan, b); + break; + + case mwIm_DATA: + recv_data((struct mwServiceIm *) srvc, chan, b); + break; + + default: + g_warning("unknown message type 0x%08x for IM service", mt); + } + + if(mwGetBuffer_error(b)) + g_warning("failed to parse message type 0x%08x for IM service", mt); + + mwGetBuffer_free(b); +} + + +static void clear(struct mwServiceIm *srvc) { + struct mwImHandler *h; + + while(srvc->convs) + convo_free(srvc->convs->data); + + h = srvc->handler; + if(h && h->clear) + h->clear(srvc); + srvc->handler = NULL; +} + + +static const char *name(struct mwService *srvc) { + return "Instant Messaging"; +} + + +static const char *desc(struct mwService *srvc) { + return "IM service with Standard and NotesBuddy features"; +} + + +static void start(struct mwService *srvc) { + mwService_started(srvc); +} + + +static void stop(struct mwServiceIm *srvc) { + + while(srvc->convs) + mwConversation_free(srvc->convs->data); + + mwService_stopped(MW_SERVICE(srvc)); +} + + +struct mwServiceIm *mwServiceIm_new(struct mwSession *session, + struct mwImHandler *hndl) { + + struct mwServiceIm *srvc_im; + struct mwService *srvc; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(hndl != NULL, NULL); + + srvc_im = g_new0(struct mwServiceIm, 1); + srvc = MW_SERVICE(srvc_im); + + mwService_init(srvc, session, mwService_IM); + srvc->recv_create = recv_channelCreate; + srvc->recv_accept = recv_channelAccept; + srvc->recv_destroy = recv_channelDestroy; + srvc->recv = recv; + srvc->clear = (mwService_funcClear) clear; + srvc->get_name = name; + srvc->get_desc = desc; + srvc->start = start; + srvc->stop = (mwService_funcStop) stop; + + srvc_im->features = mwImClient_PLAIN; + srvc_im->handler = hndl; + + return srvc_im; +} + + +struct mwImHandler *mwServiceIm_getHandler(struct mwServiceIm *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->handler; +} + + +gboolean mwServiceIm_supports(struct mwServiceIm *srvc, + enum mwImSendType type) { + + g_return_val_if_fail(srvc != NULL, FALSE); + + switch(type) { + case mwImSend_PLAIN: + case mwImSend_TYPING: + return TRUE; + + case mwImSend_SUBJECT: + case mwImSend_HTML: + case mwImSend_MIME: + case mwImSend_TIMESTAMP: + return srvc->features == mwImClient_NOTESBUDDY; + + default: + return FALSE; + } +} + + +void mwServiceIm_setClientType(struct mwServiceIm *srvc, + enum mwImClientType type) { + + g_return_if_fail(srvc != NULL); + srvc->features = type; +} + + +enum mwImClientType mwServiceIm_getClientType(struct mwServiceIm *srvc) { + g_return_val_if_fail(srvc != NULL, mwImClient_UNKNOWN); + return srvc->features; +} + + +static int convo_send_data(struct mwConversation *conv, + guint32 type, guint32 subtype, + struct mwOpaque *data) { + struct mwPutBuffer *b; + struct mwOpaque o; + struct mwChannel *chan; + int ret; + + chan = conv->channel; + g_return_val_if_fail(chan != NULL, -1); + + b = mwPutBuffer_new(); + + guint32_put(b, mwIm_DATA); + guint32_put(b, type); + guint32_put(b, subtype); + mwOpaque_put(b, data); + + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_sendEncrypted(chan, msg_MESSAGE, &o, !conv->ext_id); + mwOpaque_clear(&o); + + return ret; +} + + +static int convo_send_multi_start(struct mwConversation *conv) { + return convo_send_data(conv, mwImData_MULTI_START, 0x00, NULL); +} + + +static int convo_send_multi_stop(struct mwConversation *conv) { + return convo_send_data(conv, mwImData_MULTI_STOP, 0x00, NULL); +} + + +/* breaks up a large message into segments, sends a start_segment + message, then sends each segment in turn, then sends a stop_segment + message */ +static int +convo_sendSegmented(struct mwConversation *conv, const char *message, + int (*send)(struct mwConversation *conv, + const char *msg)) { + char *buf = (char *) message; + gsize len; + int ret = 0; + + len = strlen(buf); + ret = convo_send_multi_start(conv); + + while(len && !ret) { + char tail; + gsize seg; + + seg = BREAKUP; + if(len < BREAKUP) + seg = len; + + /* temporarily NUL-terminate this segment */ + tail = buf[seg]; + buf[seg] = 0x00; + + ret = send(conv, buf); + + /* restore this segment */ + buf[seg] = tail; + + buf += seg; + len -= seg; + } + + if(! ret) + ret = convo_send_multi_stop(conv); + + return ret; +} + + +static int convo_sendText(struct mwConversation *conv, const char *text) { + struct mwPutBuffer *b; + struct mwOpaque o; + int ret; + + b = mwPutBuffer_new(); + + guint32_put(b, mwIm_TEXT); + mwString_put(b, text); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_sendEncrypted(conv->channel, msg_MESSAGE, &o, !conv->ext_id); + mwOpaque_clear(&o); + + return ret; +} + + +static int convo_sendTyping(struct mwConversation *conv, gboolean typing) { + return convo_send_data(conv, mwImData_TYPING, !typing, NULL); +} + + +static int convo_sendSubject(struct mwConversation *conv, + const char *subject) { + struct mwOpaque o; + + o.len = strlen(subject); + o.data = (guchar *) subject; + + return convo_send_data(conv, mwImData_SUBJECT, 0x00, &o); +} + + +static int convo_sendHtml(struct mwConversation *conv, const char *html) { + struct mwOpaque o; + + o.len = strlen(html); + o.data = (guchar *) html; + + if(o.len > BREAKUP) { + return convo_sendSegmented(conv, html, convo_sendHtml); + } else { + return convo_send_data(conv, mwImData_HTML, 0x00, &o); + } +} + + +static int convo_sendMime(struct mwConversation *conv, const char *mime) { + struct mwOpaque o; + + o.len = strlen(mime); + o.data = (guchar *) mime; + + if(o.len > BREAKUP) { + return convo_sendSegmented(conv, mime, convo_sendMime); + } else { + return convo_send_data(conv, mwImData_MIME, 0x00, &o); + } +} + + +int mwConversation_send(struct mwConversation *conv, enum mwImSendType type, + gconstpointer msg) { + + g_return_val_if_fail(conv != NULL, -1); + g_return_val_if_fail(mwConversation_isOpen(conv), -1); + g_return_val_if_fail(conv->channel != NULL, -1); + + switch(type) { + case mwImSend_PLAIN: + return convo_sendText(conv, msg); + case mwImSend_TYPING: + return convo_sendTyping(conv, GPOINTER_TO_INT(msg)); + case mwImSend_SUBJECT: + return convo_sendSubject(conv, msg); + case mwImSend_HTML: + return convo_sendHtml(conv, msg); + case mwImSend_MIME: + return convo_sendMime(conv, msg); + + default: + g_warning("unsupported IM Send Type, 0x%x", type); + return -1; + } +} + + +enum mwConversationState mwConversation_getState(struct mwConversation *conv) { + g_return_val_if_fail(conv != NULL, mwConversation_UNKNOWN); + return conv->state; +} + + +struct mwServiceIm *mwConversation_getService(struct mwConversation *conv) { + g_return_val_if_fail(conv != NULL, NULL); + return conv->service; +} + + +gboolean mwConversation_supports(struct mwConversation *conv, + enum mwImSendType type) { + g_return_val_if_fail(conv != NULL, FALSE); + + switch(type) { + case mwImSend_PLAIN: + case mwImSend_TYPING: + return TRUE; + + case mwImSend_SUBJECT: + case mwImSend_HTML: + case mwImSend_MIME: + return conv->features == mwImClient_NOTESBUDDY; + + default: + return FALSE; + } +} + + +enum mwImClientType +mwConversation_getClientType(struct mwConversation *conv) { + g_return_val_if_fail(conv != NULL, mwImClient_UNKNOWN); + return conv->features; +} + + +struct mwIdBlock *mwConversation_getTarget(struct mwConversation *conv) { + g_return_val_if_fail(conv != NULL, NULL); + return &conv->target; +} + + +struct mwLoginInfo *mwConversation_getTargetInfo(struct mwConversation *conv) { + g_return_val_if_fail(conv != NULL, NULL); + g_return_val_if_fail(conv->channel != NULL, NULL); + return mwChannel_getUser(conv->channel); +} + + +void mwConversation_setClientData(struct mwConversation *conv, + gpointer data, GDestroyNotify clean) { + g_return_if_fail(conv != NULL); + mw_datum_set(&conv->client_data, data, clean); +} + + +gpointer mwConversation_getClientData(struct mwConversation *conv) { + g_return_val_if_fail(conv != NULL, NULL); + return mw_datum_get(&conv->client_data); +} + + +void mwConversation_removeClientData(struct mwConversation *conv) { + g_return_if_fail(conv != NULL); + mw_datum_clear(&conv->client_data); +} + + +void mwConversation_close(struct mwConversation *conv, guint32 reason) { + struct mwServiceIm *srvc; + struct mwImHandler *h; + + g_return_if_fail(conv != NULL); + + convo_set_state(conv, mwConversation_CLOSED); + + srvc = conv->service; + g_return_if_fail(srvc != NULL); + + h = srvc->handler; + if(h && h->conversation_closed) + h->conversation_closed(conv, reason); + + if(conv->channel) { + mwChannel_destroy(conv->channel, reason, NULL); + conv->channel = NULL; + } +} + + +void mwConversation_free(struct mwConversation *conv) { + g_return_if_fail(conv != NULL); + + if(! mwConversation_isClosed(conv)) + mwConversation_close(conv, ERR_SUCCESS); + + convo_free(conv); +} + diff --git a/src/srvc_place.c b/src/srvc_place.c new file mode 100644 index 0000000..fc979ff --- /dev/null +++ b/src/srvc_place.c @@ -0,0 +1,1075 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include + +#include +#include + +#include "mw_channel.h" +#include "mw_common.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_srvc_place.h" +#include "mw_util.h" + + +#define PROTOCOL_TYPE 0x00 +#define PROTOCOL_VER 0x05 + + +enum incoming_msg { + msg_in_JOIN_RESPONSE = 0x0000, /* ? */ + msg_in_INFO = 0x0002, + msg_in_MESSAGE = 0x0004, + msg_in_SECTION = 0x0014, /* see in_section_subtype */ + msg_in_UNKNOWNa = 0x0015, +}; + + +enum in_section_subtype { + msg_in_SECTION_LIST = 0x0000, /* list of section members */ + msg_in_SECTION_PEER = 0x0001, /* see in_section_peer_subtye */ + msg_in_SECTION_PART = 0x0003, +}; + + +enum in_section_peer_subtype { + msg_in_SECTION_PEER_JOIN = 0x0000, + msg_in_SECTION_PEER_PART = 0x0001, /* after msg_in_SECTION_PART */ + msg_in_SECTION_PEER_CLEAR_ATTR = 0x0003, + msg_in_SECTION_PEER_SET_ATTR = 0x0004, +}; + + +enum outgoing_msg { + msg_out_JOIN_PLACE = 0x0000, /* ? */ + msg_out_PEER_INFO = 0x0002, /* ? */ + msg_out_MESSAGE = 0x0003, + msg_out_OLD_INVITE = 0x0005, /* old-style conf. invitation */ + msg_out_SET_ATTR = 0x000a, + msg_out_CLEAR_ATTR = 0x000b, + msg_out_SECTION = 0x0014, /* see out_section_subtype */ + msg_out_UNKNOWNb = 0x001e, /* ? maybe enter stage ? */ +}; + + +enum out_section_subtype { + msg_out_SECTION_LIST = 0x0002, /* req list of members */ + msg_out_SECTION_PART = 0x0003, +}; + + +/* + : allocate section + : state = NEW + + : create channel + : state = PENDING + + : channel accepted + : msg_out_JOIN_PLACE (maybe create?) + : state = JOINING + + : msg_in_JOIN_RESPONSE (contains our place member ID and section ID) + : msg_in_INFO (for place, not peer) + : state = JOINED + + : msg_out_SECTION_LIST (asking for all sections) (optional) + : msg_in_SECTION_LIST (listing all sections, as requested above) + + : msg_out_PEER_INFO (with our place member ID) (optional) + : msg_in_INFO (peer info as requested above) + + : msg_out_SECTION_LIST (with our section ID) (sorta optional) + : msg_in_SECTION_LIST (section listing as requested above) + + : msg_out_UNKNOWNb + : msg_in_SECTION_PEER_JOINED (empty, with our place member ID) + : state = OPEN + + : stuff... (invites, joins, parts, messages, attr) + + : state = CLOSING + : msg_out_SECTION_PART + : destroy channel + : deallocate section +*/ + + +struct mwServicePlace { + struct mwService service; + struct mwPlaceHandler *handler; + GList *places; +}; + + +enum mwPlaceState { + mwPlace_NEW, + mwPlace_PENDING, + mwPlace_JOINING, + mwPlace_JOINED, + mwPlace_OPEN, + mwPlace_CLOSING, + mwPlace_ERROR, + mwPlace_UNKNOWN, +}; + + +struct mwPlace { + struct mwServicePlace *service; + + enum mwPlaceState state; + struct mwChannel *channel; + + char *name; + char *title; + GHashTable *members; /* mapping of member ID: place_member */ + guint32 our_id; /* our member ID */ + guint32 section; /* the section we're using */ + + guint32 requests; /* counter for requests */ + + struct mw_datum client_data; +}; + + +struct place_member { + guint32 place_id; + guint16 member_type; + struct mwIdBlock idb; + char *login_id; + char *name; + guint16 login_type; + guint32 unknown_a; + guint32 unknown_b; +}; + + +#define GET_MEMBER(place, id) \ + (g_hash_table_lookup(place->members, GUINT_TO_POINTER(id))) + + +#define PUT_MEMBER(place, member) \ + (g_hash_table_insert(place->members, \ + GUINT_TO_POINTER(member->place_id), member)) + + +#define REMOVE_MEMBER_ID(place, id) \ + (g_hash_table_remove(place->members, GUINT_TO_POINTER(id))) + + +#define REMOVE_MEMBER(place, member) \ + REMOVE_MEMBER_ID(place, member->place_id) + + +static void member_free(struct place_member *p) { + mwIdBlock_clear(&p->idb); + g_free(p->login_id); + g_free(p->name); + g_free(p); +} + + +__attribute__((used)) +static const struct mwLoginInfo * +member_as_login_info(struct place_member *p) { + static struct mwLoginInfo li; + + li.login_id = p->login_id; + li.type = p->login_type; + li.user_id = p->idb.user; + li.user_name = p->name; + li.community = p->idb.community; + li.full = FALSE; + + return &li; +} + + +static const char *place_state_str(enum mwPlaceState s) { + switch(s) { + case mwPlace_NEW: return "new"; + case mwPlace_PENDING: return "pending"; + case mwPlace_JOINING: return "joining"; + case mwPlace_JOINED: return "joined"; + case mwPlace_OPEN: return "open"; + case mwPlace_CLOSING: return "closing"; + case mwPlace_ERROR: return "error"; + + case mwPlace_UNKNOWN: /* fall-through */ + default: return "UNKNOWN"; + } +} + + +static void place_state(struct mwPlace *place, enum mwPlaceState s) { + g_return_if_fail(place != NULL); + + if(place->state == s) return; + + place->state = s; + g_message("place %s state: %s", NSTR(place->name), place_state_str(s)); +} + + +static void place_free(struct mwPlace *place) { + struct mwServicePlace *srvc; + + if(! place) return; + + srvc = place->service; + g_return_if_fail(srvc != NULL); + + srvc->places = g_list_remove_all(srvc->places, place); + + mw_datum_clear(&place->client_data); + + g_hash_table_destroy(place->members); + + g_free(place->name); + g_free(place->title); + g_free(place); +} + + +static int recv_JOIN_RESPONSE(struct mwPlace *place, + struct mwGetBuffer *b) { + + int ret = 0; + guint32 our_id, section; + + guint32_get(b, &our_id); + guint32_get(b, §ion); + + place->our_id = our_id; + place->section = section; + + return ret; +} + + +static int send_SECTION_LIST(struct mwPlace *place, guint32 section) { + int ret = 0; + struct mwOpaque o = {0, 0}; + struct mwPutBuffer *b; + + b = mwPutBuffer_new(); + guint16_put(b, msg_out_SECTION_LIST); + guint32_put(b, section); + gboolean_put(b, FALSE); + guint32_put(b, ++place->requests); + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(place->channel, msg_out_SECTION, &o); + mwOpaque_clear(&o); + + return ret; +} + + +static int recv_INFO(struct mwPlace *place, + struct mwGetBuffer *b) { + + int ret = 0; + guint32 skip = 0; + guint32 section = 0; + + guint32_get(b, &skip); + guint32_get(b, §ion); + mwGetBuffer_advance(b, skip); + + if(! section) { + /* this is a place info rather than member info */ + if(place->title) g_free(place->title); + mwGetBuffer_advance(b, 2); + mwString_get(b, &place->title); + + place_state(place, mwPlace_JOINED); + ret = send_SECTION_LIST(place, place->section); + } + + return ret; +} + + +static int recv_MESSAGE(struct mwPlace *place, + struct mwGetBuffer *b) { + + struct mwServicePlace *srvc; + guint32 pm_id; + guint32 unkn_a, unkn_b, ign; + struct place_member *pm; + char *msg = NULL; + int ret = 0; + + srvc = place->service; + + /* no messages before becoming fully open, please */ + g_return_val_if_fail(place->state == mwPlace_OPEN, -1); + + /* regarding unkn_a and unkn_b: + + they're probably a section indicator and a message count, I'm + just not sure which is which. Until this implementation supports + place sections in the API, it really doesn't matter. */ + + guint32_get(b, &pm_id); + pm = GET_MEMBER(place, pm_id); + g_return_val_if_fail(pm != NULL, -1); + + guint32_get(b, &unkn_a); + guint32_get(b, &ign); /* actually an opaque length */ + + if(! ign) return ret; + + guint32_get(b, &unkn_b); + mwString_get(b, &msg); + + if(srvc->handler && srvc->handler->message) + srvc->handler->message(place, &pm->idb, msg); + + g_free(msg); + + return ret; +} + + +static void place_opened(struct mwPlace *place) { + struct mwServicePlace *srvc; + + place_state(place, mwPlace_OPEN); + + srvc = place->service; + if(srvc->handler && srvc->handler->opened) + srvc->handler->opened(place); +} + + +static int recv_SECTION_PEER_JOIN(struct mwPlace *place, + struct mwGetBuffer *b) { + struct mwServicePlace *srvc; + struct place_member *pm; + guint32 section; + int ret = 0; + + srvc = place->service; + + guint32_get(b, §ion); + if(! section) { + g_info("SECTION_PEER_JOIN with section 0x00"); + return 0; + } + + mwGetBuffer_advance(b, 4); + + pm = g_new0(struct place_member, 1); + guint32_get(b, &pm->place_id); + guint16_get(b, &pm->member_type); + mwIdBlock_get(b, &pm->idb); + mwString_get(b, &pm->login_id); + mwString_get(b, &pm->name); + guint16_get(b, &pm->login_type); + guint32_get(b, &pm->unknown_a); + guint32_get(b, &pm->unknown_b); + + PUT_MEMBER(place, pm); + if(srvc->handler && srvc->handler->peerJoined) + srvc->handler->peerJoined(place, &pm->idb); + + if(pm->place_id == place->our_id) + place_opened(place); + + return ret; +} + + +static int recv_SECTION_PEER_PART(struct mwPlace *place, + struct mwGetBuffer *b) { + struct mwServicePlace *srvc; + int ret = 0; + guint32 section, id; + struct place_member *pm; + + srvc = place->service; + + guint32_get(b, §ion); + g_return_val_if_fail(section == place->section, 0); + + guint32_get(b, &id); + pm = GET_MEMBER(place, id); + + /* SECTION_PART may have been called already */ + if(! pm) return 0; + + if(srvc->handler && srvc->handler->peerParted) + srvc->handler->peerParted(place, &pm->idb); + + REMOVE_MEMBER(place, pm); + + return ret; +} + + +static int recv_SECTION_PEER_CLEAR_ATTR(struct mwPlace *place, + struct mwGetBuffer *b) { + struct mwServicePlace *srvc; + int ret = 0; + guint32 id, attr; + struct place_member *pm; + + srvc = place->service; + + guint32_get(b, &id); + guint32_get(b, &attr); + + pm = GET_MEMBER(place, id); + g_return_val_if_fail(pm != NULL, -1); + + if(srvc->handler && srvc->handler->peerUnsetAttribute) + srvc->handler->peerUnsetAttribute(place, &pm->idb, attr); + + return ret; +} + + +static int recv_SECTION_PEER_SET_ATTR(struct mwPlace *place, + struct mwGetBuffer *b) { + struct mwServicePlace *srvc; + int ret = 0; + guint32 id, attr; + struct mwOpaque o = {0,0}; + struct place_member *pm; + + srvc = place->service; + + guint32_get(b, &id); + mwGetBuffer_advance(b, 4); + mwOpaque_get(b, &o); + mwGetBuffer_advance(b, 4); + guint32_get(b, &attr); + + pm = GET_MEMBER(place, id); + g_return_val_if_fail(pm != NULL, -1); + + if(srvc->handler && srvc->handler->peerSetAttribute) + srvc->handler->peerSetAttribute(place, &pm->idb, attr, &o); + + mwOpaque_clear(&o); + + return ret; +} + + +static int recv_SECTION_PEER(struct mwPlace *place, + struct mwGetBuffer *b) { + guint16 subtype; + int res; + + guint16_get(b, &subtype); + + g_return_val_if_fail(! mwGetBuffer_error(b), -1); + + switch(subtype) { + case msg_in_SECTION_PEER_JOIN: + res = recv_SECTION_PEER_JOIN(place, b); + break; + + case msg_in_SECTION_PEER_PART: + res = recv_SECTION_PEER_PART(place, b); + break; + + case msg_in_SECTION_PEER_CLEAR_ATTR: + res = recv_SECTION_PEER_CLEAR_ATTR(place, b); + break; + + case msg_in_SECTION_PEER_SET_ATTR: + res = recv_SECTION_PEER_SET_ATTR(place, b); + break; + + default: + res = -1; + } + + return res; +} + + +static int recv_SECTION_LIST(struct mwPlace *place, + struct mwGetBuffer *b) { + int ret = 0; + guint32 sec, count; + + mwGetBuffer_advance(b, 4); + guint32_get(b, &sec); + + g_return_val_if_fail(sec == place->section, -1); + + mwGetBuffer_advance(b, 8); + guint32_get(b, &count); + mwGetBuffer_advance(b, 8); + + while(count--) { + struct place_member *m; + + m = g_new0(struct place_member, 1); + mwGetBuffer_advance(b, 4); + guint32_get(b, &m->place_id); + guint16_get(b, &m->member_type); + mwIdBlock_get(b, &m->idb); + mwString_get(b, &m->login_id); + mwString_get(b, &m->name); + guint16_get(b, &m->login_type); + guint32_get(b, &m->unknown_a); + guint32_get(b, &m->unknown_b); + + PUT_MEMBER(place, m); + } + + if(place->state != mwPlace_OPEN) + place_opened(place); + + return ret; +} + + +static int recv_SECTION_PART(struct mwPlace *place, + struct mwGetBuffer *b) { + /* look up user in place + remove user from place + trigger event */ + + struct mwServicePlace *srvc; + guint32 pm_id; + struct place_member *pm; + + srvc = place->service; + + guint32_get(b, &pm_id); + pm = GET_MEMBER(place, pm_id); + + /* SECTION_PEER_PART may have been called already */ + if(! pm) return 0; + + if(srvc->handler && srvc->handler->peerParted) + srvc->handler->peerParted(place, &pm->idb); + + REMOVE_MEMBER(place, pm); + + return 0; +} + + +static int recv_SECTION(struct mwPlace *place, struct mwGetBuffer *b) { + guint16 subtype; + int res; + + guint16_get(b, &subtype); + + g_return_val_if_fail(! mwGetBuffer_error(b), -1); + + switch(subtype) { + case msg_in_SECTION_LIST: + res = recv_SECTION_LIST(place, b); + break; + + case msg_in_SECTION_PEER: + res = recv_SECTION_PEER(place, b); + break; + + case msg_in_SECTION_PART: + res = recv_SECTION_PART(place, b); + break; + + default: + res = -1; + } + + return res; +} + + +static int recv_UNKNOWNa(struct mwPlace *place, struct mwGetBuffer *b) { + int res = 0; + + if(place->state == mwPlace_JOINING) { + ; + /* place_state(place, mwPlace_JOINED); + res = send_SECTION_LIST(place, place->section); */ + + } else if(place->state == mwPlace_JOINED) { + ; + /* if(GET_MEMBER(place, place->our_id)) + place_opened(place); */ + } + + return res; +} + + +static void recv(struct mwService *service, struct mwChannel *chan, + guint16 type, struct mwOpaque *data) { + + struct mwPlace *place; + struct mwGetBuffer *b; + int res = 0; + + place = mwChannel_getServiceData(chan); + g_return_if_fail(place != NULL); + + b = mwGetBuffer_wrap(data); + switch(type) { + case msg_in_JOIN_RESPONSE: + res = recv_JOIN_RESPONSE(place, b); + break; + + case msg_in_INFO: + res = recv_INFO(place, b); + break; + + case msg_in_MESSAGE: + res = recv_MESSAGE(place, b); + break; + + case msg_in_SECTION: + res = recv_SECTION(place, b); + break; + + case msg_in_UNKNOWNa: + res = recv_UNKNOWNa(place, b); + break; + + default: + mw_mailme_opaque(data, "Received unknown message type 0x%x on place %s", + type, NSTR(place->name)); + } + + if(res) { + mw_mailme_opaque(data, "Troubling parsing message type 0x0%x on place %s", + type, NSTR(place->name)); + } + + mwGetBuffer_free(b); +} + + +static void stop(struct mwServicePlace *srvc) { + while(srvc->places) + mwPlace_destroy(srvc->places->data, ERR_SUCCESS); + + mwService_stopped(MW_SERVICE(srvc)); +} + + +static int send_JOIN_PLACE(struct mwPlace *place) { + struct mwOpaque o = {0, 0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + gboolean_put(b, FALSE); + guint16_put(b, 0x01); + guint16_put(b, 0x02); /* 0x01 */ + guint16_put(b, 0x01); /* 0x00 */ + + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(place->channel, msg_out_JOIN_PLACE, &o); + + mwOpaque_clear(&o); + + if(ret) { + place_state(place, mwPlace_ERROR); + } else { + place_state(place, mwPlace_JOINING); + } + + return ret; +} + + +static void recv_channelAccept(struct mwService *service, + struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + struct mwServicePlace *srvc; + struct mwPlace *place; + int res; + + srvc = (struct mwServicePlace *) service; + g_return_if_fail(srvc != NULL); + + place = mwChannel_getServiceData(chan); + g_return_if_fail(place != NULL); + + res = send_JOIN_PLACE(place); +} + + +static void recv_channelDestroy(struct mwService *service, + struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + struct mwServicePlace *srvc; + struct mwPlace *place; + + srvc = (struct mwServicePlace *) service; + g_return_if_fail(srvc != NULL); + + place = mwChannel_getServiceData(chan); + g_return_if_fail(place != NULL); + + place_state(place, mwPlace_ERROR); + + place->channel = NULL; + + if(srvc->handler && srvc->handler->closed) + srvc->handler->closed(place, msg->reason); + + mwPlace_destroy(place, msg->reason); +} + + +static void clear(struct mwServicePlace *srvc) { + + if(srvc->handler && srvc->handler->clear) + srvc->handler->clear(srvc); + + while(srvc->places) + place_free(srvc->places->data); +} + + +static const char *get_name(struct mwService *srvc) { + return "Places Conferencing"; +} + + +static const char *get_desc(struct mwService *srvc) { + return "Barebones conferencing via Places"; +} + + +struct mwServicePlace * +mwServicePlace_new(struct mwSession *session, + struct mwPlaceHandler *handler) { + + struct mwServicePlace *srvc_place; + struct mwService *srvc; + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(handler != NULL, NULL); + + srvc_place = g_new0(struct mwServicePlace, 1); + srvc_place->handler = handler; + + srvc = MW_SERVICE(srvc_place); + mwService_init(srvc, session, mwService_PLACE); + srvc->start = NULL; + srvc->stop = (mwService_funcStop) stop; + srvc->recv_create = NULL; + srvc->recv_accept = recv_channelAccept; + srvc->recv_destroy = recv_channelDestroy; + srvc->recv = recv; + srvc->clear = (mwService_funcClear) clear; + srvc->get_name = get_name; + srvc->get_desc = get_desc; + + return srvc_place; +} + + +struct mwPlaceHandler * +mwServicePlace_getHandler(struct mwServicePlace *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->handler; +} + + +const GList *mwServicePlace_getPlaces(struct mwServicePlace *srvc) { + g_return_val_if_fail(srvc != NULL, NULL); + return srvc->places; +} + + +struct mwPlace *mwPlace_new(struct mwServicePlace *srvc, + const char *name, const char *title) { + struct mwPlace *place; + + g_return_val_if_fail(srvc != NULL, NULL); + + place = g_new0(struct mwPlace, 1); + place->service = srvc; + place->name = g_strdup(name); + place->title = g_strdup(title); + place->state = mwPlace_NEW; + + place->members = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) member_free); + + srvc->places = g_list_prepend(srvc->places, place); + + return place; +} + + +struct mwServicePlace *mwPlace_getService(struct mwPlace *place) { + g_return_val_if_fail(place != NULL, NULL); + return place->service; +} + + +static char *place_generate_name(const char *user) { + guint a, b; + char *ret; + + user = user? user: "meanwhile"; + + srand(clock() + rand()); + a = ((rand() & 0xff) << 8) | (rand() & 0xff); + b = time(NULL); + + ret = g_strdup_printf("%s(%08x,%04x)", user, b, a); + g_debug("generated random conference name: '%s'", ret); + return ret; +} + + +const char *mwPlace_getName(struct mwPlace *place) { + g_return_val_if_fail(place != NULL, NULL); + + if(! place->name) { + struct mwSession *session; + struct mwLoginInfo *li; + + session = mwService_getSession(MW_SERVICE(place->service)); + li = mwSession_getLoginInfo(session); + + place->name = place_generate_name(li? li->user_id: NULL); + } + + return place->name; +} + + +static char *place_generate_title(const char *user) { + char *ret; + + user = user? user: "Meanwhile"; + ret = g_strdup_printf("%s's Conference", user); + g_debug("generated conference title: %s", ret); + + return ret; +} + + +const char *mwPlace_getTitle(struct mwPlace *place) { + g_return_val_if_fail(place != NULL, NULL); + + if(! place->title) { + struct mwSession *session; + struct mwLoginInfo *li; + + session = mwService_getSession(MW_SERVICE(place->service)); + li = mwSession_getLoginInfo(session); + + place->title = place_generate_title(li? li->user_name: NULL); + } + + return place->title; +} + + +int mwPlace_open(struct mwPlace *p) { + struct mwSession *session; + struct mwChannelSet *cs; + struct mwChannel *chan; + struct mwPutBuffer *b; + int ret; + + g_return_val_if_fail(p != NULL, -1); + g_return_val_if_fail(p->service != NULL, -1); + + session = mwService_getSession(MW_SERVICE(p->service)); + g_return_val_if_fail(session != NULL, -1); + + cs = mwSession_getChannels(session); + g_return_val_if_fail(cs != NULL, -1); + + chan = mwChannel_newOutgoing(cs); + mwChannel_setService(chan, MW_SERVICE(p->service)); + mwChannel_setProtoType(chan, PROTOCOL_TYPE); + mwChannel_setProtoVer(chan, PROTOCOL_VER); + + mwChannel_populateSupportedCipherInstances(chan); + + b = mwPutBuffer_new(); + mwString_put(b, mwPlace_getName(p)); + mwString_put(b, mwPlace_getTitle(p)); + guint32_put(b, 0x00); /* ? */ + + mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b); + + ret = mwChannel_create(chan); + if(ret) { + place_state(p, mwPlace_ERROR); + } else { + place_state(p, mwPlace_PENDING); + p->channel = chan; + mwChannel_setServiceData(chan, p, NULL); + } + + return ret; +} + + +int mwPlace_destroy(struct mwPlace *p, guint32 code) { + int ret = 0; + + place_state(p, mwPlace_CLOSING); + + if(p->channel) { + ret = mwChannel_destroy(p->channel, code, NULL); + p->channel = NULL; + } + + place_free(p); + + return ret; +} + + +GList *mwPlace_getMembers(struct mwPlace *place) { + GList *l, *ll; + + g_return_val_if_fail(place != NULL, NULL); + g_return_val_if_fail(place->members != NULL, NULL); + + ll = map_collect_values(place->members); + for(l = ll; l; l = l->next) { + struct place_member *pm = l->data; + l->data = &pm->idb; + g_info("collected member %u: %s, %s", pm->place_id, + NSTR(pm->idb.user), NSTR(pm->idb.community)); + } + + return ll; +} + + +int mwPlace_sendText(struct mwPlace *place, const char *msg) { + struct mwOpaque o = {0,0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + guint32_put(b, 0x01); /* probably a message type */ + mwString_put(b, msg); + mwPutBuffer_finalize(&o, b); + + b = mwPutBuffer_new(); + guint32_put(b, place->section); + mwOpaque_put(b, &o); + mwOpaque_clear(&o); + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(place->channel, msg_out_MESSAGE, &o); + mwOpaque_clear(&o); + return ret; +} + + +int mwPlace_legacyInvite(struct mwPlace *place, + struct mwIdBlock *idb, + const char *message) { + + struct mwOpaque o = {0,0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + mwIdBlock_put(b, idb); + mwString_put(b, idb->user); + mwString_put(b, idb->user); + mwString_put(b, message); + gboolean_put(b, FALSE); + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(place->channel, msg_out_OLD_INVITE, &o); + mwOpaque_clear(&o); + return ret; +} + + +int mwPlace_setAttribute(struct mwPlace *place, guint32 attrib, + struct mwOpaque *data) { + + struct mwOpaque o = {0,0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + guint32_put(b, place->our_id); + guint32_put(b, 0x00); + guint32_put(b, attrib); + mwOpaque_put(b, data); + + ret = mwChannel_send(place->channel, msg_out_SET_ATTR, &o); + mwOpaque_clear(&o); + return ret; +} + + +int mwPlace_unsetAttribute(struct mwPlace *place, guint32 attrib) { + struct mwOpaque o = {0,0}; + struct mwPutBuffer *b; + int ret; + + b = mwPutBuffer_new(); + guint32_put(b, place->our_id); + guint32_put(b, attrib); + + ret = mwChannel_send(place->channel, msg_out_SET_ATTR, &o); + mwOpaque_clear(&o); + return ret; +} + + +void mwPlace_setClientData(struct mwPlace *place, + gpointer data, GDestroyNotify clear) { + + g_return_if_fail(place != NULL); + mw_datum_set(&place->client_data, data, clear); +} + + +gpointer mwPlace_getClientData(struct mwPlace *place) { + g_return_val_if_fail(place != NULL, NULL); + return mw_datum_get(&place->client_data); +} + + +void mwPlace_removeClientData(struct mwPlace *place) { + g_return_if_fail(place != NULL); + mw_datum_clear(&place->client_data); +} diff --git a/src/srvc_resolve.c b/src/srvc_resolve.c new file mode 100644 index 0000000..aac0d49 --- /dev/null +++ b/src/srvc_resolve.c @@ -0,0 +1,389 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#include "mw_channel.h" +#include "mw_common.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_srvc_resolve.h" + + +#define PROTOCOL_TYPE 0x00000015 +#define PROTOCOL_VER 0x00000000 + + +/** oddly, there is only one message type in this service */ +#define RESOLVE_ACTION 0x02 + + +struct mwServiceResolve { + struct mwService service; + + struct mwChannel *channel; /**< channel for this service */ + GHashTable *searches; /**< guint32:struct mw_search */ + guint32 counter; /**< incremented to provide searche IDs */ +}; + + +/** structure representing an active search. keeps track of the ID, + the handler, and the optional user data and cleanup */ +struct mw_search { + struct mwServiceResolve *service; + guint32 id; + mwResolveHandler handler; + gpointer data; + GDestroyNotify cleanup; +}; + + +static struct mw_search *search_new(struct mwServiceResolve *srvc, + mwResolveHandler handler, + gpointer data, GDestroyNotify cleanup) { + + struct mw_search *search = g_new0(struct mw_search, 1); + + search->service = srvc; + search->handler = handler; + + /* we want search IDs that aren't SEARCH_ERROR */ + do { + search->id = srvc->counter++; + } while(search->id == SEARCH_ERROR); + + search->data = data; + search->cleanup = cleanup; + + return search; +} + + +/** called whenever a mw_search is removed from the searches table of + the service */ +static void search_free(struct mw_search *search) { + g_return_if_fail(search != NULL); + + if(search->cleanup) + search->cleanup(search->data); + + g_free(search); +} + + +static const char *get_name(struct mwService *srvc) { + return "Identity Resolution"; +} + + +static const char *get_desc(struct mwService *srvc) { + return "Resolves short IDs to full IDs"; +} + + +static struct mwChannel *make_channel(struct mwServiceResolve *srvc) { + struct mwSession *session; + struct mwChannelSet *cs; + struct mwChannel *chan; + + session = mwService_getSession(MW_SERVICE(srvc)); + cs = mwSession_getChannels(session); + chan = mwChannel_newOutgoing(cs); + + mwChannel_setService(chan, MW_SERVICE(srvc)); + mwChannel_setProtoType(chan, PROTOCOL_TYPE); + mwChannel_setProtoVer(chan, PROTOCOL_VER); + + return mwChannel_create(chan)? NULL: chan; +} + + +static void start(struct mwServiceResolve *srvc) { + struct mwChannel *chan; + + g_return_if_fail(srvc != NULL); + + chan = make_channel(srvc); + if(chan) { + srvc->channel = chan; + } else { + mwService_stopped(MW_SERVICE(srvc)); + return; + } + + /* semi-lazily create the searches table */ + srvc->searches = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) search_free); +} + + +static void stop(struct mwServiceResolve *srvc) { + g_return_if_fail(srvc != NULL); + + if(srvc->channel) { + mwChannel_destroy(srvc->channel, ERR_SUCCESS, NULL); + srvc->channel = NULL; + } + + /* destroy all the pending requests. */ + g_hash_table_destroy(srvc->searches); + srvc->searches = NULL; + + mwService_stopped(MW_SERVICE(srvc)); +} + + +static void clear(struct mwServiceResolve *srvc) { + if(srvc->searches) { + g_hash_table_destroy(srvc->searches); + srvc->searches = NULL; + } +} + + +static void recv_create(struct mwServiceResolve *srvc, + struct mwChannel *chan, + struct mwMsgChannelCreate *msg) { + + /* you serve me, not the other way around */ + mwChannel_destroy(chan, ERR_FAILURE, NULL); +} + + +static void recv_accept(struct mwServiceResolve *srvc, + struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + + g_return_if_fail(srvc != NULL); + g_return_if_fail(chan != NULL); + g_return_if_fail(chan == srvc->channel); + + mwService_started(MW_SERVICE(srvc)); +} + + +static void recv_destroy(struct mwServiceResolve *srvc, + struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + + struct mwSession *session; + + g_return_if_fail(srvc != NULL); + g_return_if_fail(chan != NULL); + g_return_if_fail(chan == srvc->channel); + + srvc->channel = NULL; + mwService_stop(MW_SERVICE(srvc)); + + session = mwService_getSession(MW_SERVICE(srvc)); + g_return_if_fail(session != NULL); + + mwSession_senseService(session, mwService_getType(MW_SERVICE(srvc))); +} + + +static GList *load_matches(struct mwGetBuffer *b, guint32 count) { + GList *matches = NULL; + + while(count--) { + struct mwResolveMatch *m = g_new0(struct mwResolveMatch, 1); + + mwString_get(b, &m->id); + mwString_get(b, &m->name); + mwString_get(b, &m->desc); + guint32_get(b, &m->type); + + matches = g_list_append(matches, m); + } + + return matches; +} + + +static GList *load_results(struct mwGetBuffer *b, guint32 count) { + GList *results = NULL; + + while(count--) { + struct mwResolveResult *r = g_new0(struct mwResolveResult, 1); + guint32 junk, matches; + + guint32_get(b, &junk); + guint32_get(b, &r->code); + mwString_get(b, &r->name); + + guint32_get(b, &matches); + r->matches = load_matches(b, matches); + + results = g_list_append(results, r); + } + + return results; +} + + +static void free_matches(GList *matches) { + for(; matches; matches = g_list_delete_link(matches, matches)) { + struct mwResolveMatch *m = matches->data; + g_free(m->id); + g_free(m->name); + g_free(m->desc); + g_free(m); + } +} + + +static void free_results(GList *results) { + for(; results; results = g_list_delete_link(results, results)) { + struct mwResolveResult *r = results->data; + g_free(r->name); + free_matches(r->matches); + g_free(r); + } +} + + +static void recv(struct mwServiceResolve *srvc, + struct mwChannel *chan, + guint16 type, struct mwOpaque *data) { + + struct mwGetBuffer *b; + guint32 junk, id, code, count; + struct mw_search *search; + + g_return_if_fail(srvc != NULL); + g_return_if_fail(chan != NULL); + g_return_if_fail(chan == srvc->channel); + g_return_if_fail(data != NULL); + + if(type != RESOLVE_ACTION) { + mw_mailme_opaque(data, "unknown message in resolve service: 0x%04x", type); + return; + } + + b = mwGetBuffer_wrap(data); + guint32_get(b, &junk); + guint32_get(b, &id); + guint32_get(b, &code); + guint32_get(b, &count); + + if(mwGetBuffer_error(b)) { + g_warning("error parsing search result"); + mwGetBuffer_free(b); + return; + } + + search = g_hash_table_lookup(srvc->searches, GUINT_TO_POINTER(id)); + + if(search) { + GList *results = load_results(b, count); + if(mwGetBuffer_error(b)) { + g_warning("error parsing search results"); + } else { + g_debug("triggering handler"); + search->handler(srvc, id, code, results, search->data); + } + free_results(results); + g_hash_table_remove(srvc->searches, GUINT_TO_POINTER(id)); + + } else { + g_debug("no search found: 0x%x", id); + } + + mwGetBuffer_free(b); +} + + +struct mwServiceResolve *mwServiceResolve_new(struct mwSession *session) { + struct mwServiceResolve *srvc_resolve; + struct mwService *srvc; + + g_return_val_if_fail(session != NULL, NULL); + + srvc_resolve = g_new0(struct mwServiceResolve, 1); + + srvc = MW_SERVICE(srvc_resolve); + + mwService_init(srvc, session, mwService_RESOLVE); + srvc->get_name = get_name; + srvc->get_desc = get_desc; + srvc->recv_create = (mwService_funcRecvCreate) recv_create; + srvc->recv_accept = (mwService_funcRecvAccept) recv_accept; + srvc->recv_destroy = (mwService_funcRecvDestroy) recv_destroy; + srvc->recv = (mwService_funcRecv) recv; + srvc->start = (mwService_funcStart) start; + srvc->stop = (mwService_funcStop) stop; + srvc->clear = (mwService_funcClear) clear; + + return srvc_resolve; +} + + +guint32 mwServiceResolve_resolve(struct mwServiceResolve *srvc, + GList *queries, enum mwResolveFlag flags, + mwResolveHandler handler, + gpointer data, GDestroyNotify cleanup) { + + struct mw_search *search; + struct mwPutBuffer *b; + struct mwOpaque o = { 0, 0 }; + int ret, count = 0; + + g_return_val_if_fail(srvc != NULL, SEARCH_ERROR); + g_return_val_if_fail(handler != NULL, SEARCH_ERROR); + + count = g_list_length(queries); + g_return_val_if_fail(count > 0, SEARCH_ERROR); + + search = search_new(srvc, handler, data, cleanup); + + b = mwPutBuffer_new(); + guint32_put(b, 0x00); /* to be overwritten */ + guint32_put(b, search->id); + guint32_put(b, count); + for(; queries; queries = queries->next) + mwString_put(b, queries->data); + guint32_put(b, flags); + + mwPutBuffer_finalize(&o, b); + + ret = mwChannel_send(srvc->channel, RESOLVE_ACTION, &o); + if(ret) { + search_free(search); + return SEARCH_ERROR; + + } else { + g_hash_table_insert(srvc->searches, + GUINT_TO_POINTER(search->id), search); + return search->id; + } +} + + +void mwServiceResolve_cancelResolve(struct mwServiceResolve *srvc, + guint32 id) { + + g_return_if_fail(srvc != NULL); + g_return_if_fail(srvc->searches != NULL); + + g_hash_table_remove(srvc->searches, GUINT_TO_POINTER(id)); +} + diff --git a/src/srvc_store.c b/src/srvc_store.c new file mode 100644 index 0000000..90f2efc --- /dev/null +++ b/src/srvc_store.c @@ -0,0 +1,608 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +#include "mw_channel.h" +#include "mw_debug.h" +#include "mw_error.h" +#include "mw_message.h" +#include "mw_service.h" +#include "mw_session.h" +#include "mw_srvc_store.h" + + +#define PROTOCOL_TYPE 0x00000025 +#define PROTOCOL_VER 0x00000001 + + +enum storage_action { + action_load = 0x0004, + action_loaded = 0x0005, + action_save = 0x0006, + action_saved = 0x0007, +}; + + +struct mwStorageUnit { + /** key by which data is referenced in service + @see mwStorageKey */ + guint32 key; + + /** Data associated with key in service */ + struct mwOpaque data; +}; + + +struct mwStorageReq { + guint32 id; /**< unique id for this request */ + guint32 result_code; /**< result code for completed request */ + enum storage_action action; /**< load or save */ + struct mwStorageUnit *item; /**< the key/data pair */ + mwStorageCallback cb; /**< callback to notify upon completion */ + gpointer data; /**< user data to pass with callback */ + GDestroyNotify data_free; /**< optionally frees user data */ +}; + + +struct mwServiceStorage { + struct mwService service; + + /** collection of mwStorageReq */ + GList *pending; + + /** current service channel */ + struct mwChannel *channel; + + /** keep track of the counter */ + guint32 id_counter; +}; + + +static void request_get(struct mwGetBuffer *b, struct mwStorageReq *req) { + guint32 id, count, junk; + + if(mwGetBuffer_error(b)) return; + + guint32_get(b, &id); + guint32_get(b, &req->result_code); + + if(req->action == action_loaded) { + guint32_get(b, &count); + + if(count > 0) { + guint32_get(b, &junk); + guint32_get(b, &req->item->key); + + mwOpaque_clear(&req->item->data); + mwOpaque_get(b, &req->item->data); + } + } +} + + +static void request_put(struct mwPutBuffer *b, struct mwStorageReq *req) { + + guint32_put(b, req->id); + guint32_put(b, 1); + + if(req->action == action_save) { + guint32_put(b, 20 + req->item->data.len); /* ugh, offset garbage */ + guint32_put(b, req->item->key); + mwOpaque_put(b, &req->item->data); + + } else { + guint32_put(b, req->item->key); + } +} + + +static int request_send(struct mwChannel *chan, struct mwStorageReq *req) { + struct mwPutBuffer *b; + struct mwOpaque o = { 0, 0 }; + int ret; + + b = mwPutBuffer_new(); + request_put(b, req); + + mwPutBuffer_finalize(&o, b); + ret = mwChannel_send(chan, req->action, &o); + mwOpaque_clear(&o); + + if(! ret) { + if(req->action == action_save) { + req->action = action_saved; + } else if(req->action == action_load) { + req->action = action_loaded; + } + } + + return ret; +} + + +static struct mwStorageReq *request_find(struct mwServiceStorage *srvc, + guint32 id) { + GList *l; + + for(l = srvc->pending; l; l = l->next) { + struct mwStorageReq *r = l->data; + if(r->id == id) return r; + } + + return NULL; +} + + +static const char *action_str(enum storage_action act) { + switch(act) { + case action_load: return "load"; + case action_loaded: return "loaded"; + case action_save: return "save"; + case action_saved: return "saved"; + default: return "UNKNOWN"; + } +} + + +static void request_trigger(struct mwServiceStorage *srvc, + struct mwStorageReq *req) { + + struct mwStorageUnit *item = req->item; + + g_message("storage request %s: key = 0x%x, result = 0x%x, length = %u", + action_str(req->action), + item->key, req->result_code, (guint) item->data.len); + + if(req->cb) + req->cb(srvc, req->result_code, item, req->data); +} + + +static void request_free(struct mwStorageReq *req) { + if(req->data_free) { + req->data_free(req->data); + req->data = NULL; + req->data_free = NULL; + } + + mwStorageUnit_free(req->item); + g_free(req); +} + + +static void request_remove(struct mwServiceStorage *srvc, + struct mwStorageReq *req) { + + srvc->pending = g_list_remove_all(srvc->pending, req); + request_free(req); +} + + +static const char *get_name(struct mwService *srvc) { + return "User Storage"; +} + + +static const char *get_desc(struct mwService *srvc) { + return "Stores user data and settings on the server"; +} + + +static struct mwChannel *make_channel(struct mwServiceStorage *srvc) { + struct mwSession *session; + struct mwChannelSet *cs; + struct mwChannel *chan; + + session = mwService_getSession(MW_SERVICE(srvc)); + cs = mwSession_getChannels(session); + chan = mwChannel_newOutgoing(cs); + + mwChannel_setService(chan, MW_SERVICE(srvc)); + mwChannel_setProtoType(chan, PROTOCOL_TYPE); + mwChannel_setProtoVer(chan, PROTOCOL_VER); + + return mwChannel_create(chan)? NULL: chan; +} + + +static void start(struct mwService *srvc) { + struct mwServiceStorage *srvc_store; + struct mwChannel *chan; + + g_return_if_fail(srvc != NULL); + srvc_store = (struct mwServiceStorage *) srvc; + + chan = make_channel(srvc_store); + if(chan) { + srvc_store->channel = chan; + } else { + mwService_stopped(srvc); + } +} + + +static void stop(struct mwService *srvc) { + + struct mwServiceStorage *srvc_store; + GList *l; + + g_return_if_fail(srvc != NULL); + srvc_store = (struct mwServiceStorage *) srvc; + + if(srvc_store->channel) { + mwChannel_destroy(srvc_store->channel, ERR_SUCCESS, NULL); + srvc_store->channel = NULL; + } + +#if 1 + /* the new way */ + /* remove pending requests. Sometimes we can crash the storage + service, and when that happens, we end up resending the killer + request over and over again, and the service never stays up */ + for(l = srvc_store->pending; l; l = l->next) + request_free(l->data); + + g_list_free(srvc_store->pending); + srvc_store->pending = NULL; + + srvc_store->id_counter = 0; + +#else + /* the old way */ + /* reset all of the started requests to their unstarted states */ + for(l = srvc_store->pending; l; l = l->next) { + struct mwStorageReq *req = l->data; + + if(req->action == action_loaded) { + req->action = action_load; + } else if(req->action == action_saved) { + req->action = action_save; + } + } +#endif + + mwService_stopped(srvc); +} + + +static void recv_channelAccept(struct mwService *srvc, + struct mwChannel *chan, + struct mwMsgChannelAccept *msg) { + + struct mwServiceStorage *srvc_stor; + GList *l; + + g_return_if_fail(srvc != NULL); + srvc_stor = (struct mwServiceStorage *) srvc; + + g_return_if_fail(chan != NULL); + g_return_if_fail(chan == srvc_stor->channel); + + /* send all pending requests */ + for(l = srvc_stor->pending; l; l = l->next) { + struct mwStorageReq *req = l->data; + + if(req->action == action_save || req->action == action_load) { + request_send(chan, req); + } + } + + mwService_started(srvc); +} + + +static void recv_channelDestroy(struct mwService *srvc, + struct mwChannel *chan, + struct mwMsgChannelDestroy *msg) { + + struct mwSession *session; + struct mwServiceStorage *srvc_stor; + + g_return_if_fail(srvc != NULL); + g_return_if_fail(chan != NULL); + + session = mwService_getSession(srvc); + g_return_if_fail(session != NULL); + + srvc_stor = (struct mwServiceStorage *) srvc; + srvc_stor->channel = NULL; + + mwService_stop(srvc); + mwSession_senseService(session, mwService_getType(srvc)); +} + + +static void recv(struct mwService *srvc, struct mwChannel *chan, + guint16 type, struct mwOpaque *data) { + + /* process into results, trigger callbacks */ + + struct mwGetBuffer *b; + struct mwServiceStorage *srvc_stor; + struct mwStorageReq *req; + guint32 id; + + g_return_if_fail(srvc != NULL); + srvc_stor = (struct mwServiceStorage *) srvc; + + g_return_if_fail(chan != NULL); + g_return_if_fail(chan == srvc_stor->channel); + g_return_if_fail(data != NULL); + + b = mwGetBuffer_wrap(data); + + id = guint32_peek(b); + req = request_find(srvc_stor, id); + + if(! req) { + g_warning("couldn't find request 0x%x in storage service", id); + mwGetBuffer_free(b); + return; + } + + g_return_if_fail(req->action == type); + request_get(b, req); + + if(mwGetBuffer_error(b)) { + mw_mailme_opaque(data, "storage request 0x%x, type: 0x%x", id, type); + + } else { + request_trigger(srvc_stor, req); + } + + mwGetBuffer_free(b); + request_remove(srvc_stor, req); +} + + +static void clear(struct mwService *srvc) { + struct mwServiceStorage *srvc_stor; + GList *l; + + srvc_stor = (struct mwServiceStorage *) srvc; + + for(l = srvc_stor->pending; l; l = l->next) + request_free(l->data); + + g_list_free(srvc_stor->pending); + srvc_stor->pending = NULL; + + srvc_stor->id_counter = 0; +} + + +struct mwServiceStorage *mwServiceStorage_new(struct mwSession *session) { + struct mwServiceStorage *srvc_store; + struct mwService *srvc; + + srvc_store = g_new0(struct mwServiceStorage, 1); + srvc = MW_SERVICE(srvc_store); + + mwService_init(srvc, session, mwService_STORAGE); + srvc->get_name = get_name; + srvc->get_desc = get_desc; + srvc->recv_accept = recv_channelAccept; + srvc->recv_destroy = recv_channelDestroy; + srvc->recv = recv; + srvc->start = start; + srvc->stop = stop; + srvc->clear = clear; + + return srvc_store; +} + + +struct mwStorageUnit *mwStorageUnit_new(guint32 key) { + struct mwStorageUnit *u; + + u = g_new0(struct mwStorageUnit, 1); + u->key = key; + + return u; +} + + +struct mwStorageUnit *mwStorageUnit_newOpaque(guint32 key, + struct mwOpaque *data) { + struct mwStorageUnit *u; + + u = g_new0(struct mwStorageUnit, 1); + u->key = key; + + if(data) + mwOpaque_clone(&u->data, data); + + return u; +} + + +struct mwStorageUnit *mwStorageUnit_newBoolean(guint32 key, + gboolean val) { + + return mwStorageUnit_newInteger(key, (guint32) val); +} + + +struct mwStorageUnit *mwStorageUnit_newInteger(guint32 key, + guint32 val) { + struct mwStorageUnit *u; + struct mwPutBuffer *b; + + u = g_new0(struct mwStorageUnit, 1); + u->key = key; + + b = mwPutBuffer_new(); + guint32_put(b, val); + mwPutBuffer_finalize(&u->data, b); + + return u; +} + + +struct mwStorageUnit *mwStorageUnit_newString(guint32 key, + const char *str) { + struct mwStorageUnit *u; + struct mwPutBuffer *b; + + u = g_new0(struct mwStorageUnit, 1); + u->key = key; + + b = mwPutBuffer_new(); + mwString_put(b, str); + mwPutBuffer_finalize(&u->data, b); + + return u; +} + + +guint32 mwStorageUnit_getKey(struct mwStorageUnit *item) { + g_return_val_if_fail(item != NULL, 0x00); /* feh, unsafe */ + return item->key; +} + + +gboolean mwStorageUnit_asBoolean(struct mwStorageUnit *item, + gboolean val) { + + return !! mwStorageUnit_asInteger(item, (guint32) val); +} + + +guint32 mwStorageUnit_asInteger(struct mwStorageUnit *item, + guint32 val) { + struct mwGetBuffer *b; + guint32 v; + + g_return_val_if_fail(item != NULL, val); + + b = mwGetBuffer_wrap(&item->data); + + guint32_get(b, &v); + if(! mwGetBuffer_error(b)) val = v; + mwGetBuffer_free(b); + + return val; +} + + +char *mwStorageUnit_asString(struct mwStorageUnit *item) { + struct mwGetBuffer *b; + char *c = NULL; + + g_return_val_if_fail(item != NULL, NULL); + + b = mwGetBuffer_wrap(&item->data); + + mwString_get(b, &c); + + if(mwGetBuffer_error(b)) + g_debug("error obtaining string value from opaque"); + + mwGetBuffer_free(b); + + return c; +} + + +struct mwOpaque *mwStorageUnit_asOpaque(struct mwStorageUnit *item) { + g_return_val_if_fail(item != NULL, NULL); + return &item->data; +} + + +void mwStorageUnit_free(struct mwStorageUnit *item) { + if(! item) return; + + mwOpaque_clear(&item->data); + g_free(item); +} + + +static struct mwStorageReq *request_new(struct mwServiceStorage *srvc, + struct mwStorageUnit *item, + mwStorageCallback cb, + gpointer data, GDestroyNotify df) { + + struct mwStorageReq *req = g_new0(struct mwStorageReq, 1); + + req->id = ++srvc->id_counter; + req->item = item; + req->cb = cb; + req->data = data; + req->data_free = df; + + return req; +} + + +void mwServiceStorage_load(struct mwServiceStorage *srvc, + struct mwStorageUnit *item, + mwStorageCallback cb, + gpointer data, GDestroyNotify d_free) { + + /* - construct a request + - put request at end of pending + - if channel is open and connected + - compose the load message + - send message + - set request to sent + - else + - start service + */ + + struct mwStorageReq *req; + + req = request_new(srvc, item, cb, data, d_free); + req->action = action_load; + + srvc->pending = g_list_append(srvc->pending, req); + + if(MW_SERVICE_IS_STARTED(MW_SERVICE(srvc))) + request_send(srvc->channel, req); +} + + +void mwServiceStorage_save(struct mwServiceStorage *srvc, + struct mwStorageUnit *item, + mwStorageCallback cb, + gpointer data, GDestroyNotify d_free) { + + /* - construct a request + - put request at end of pending + - if channel is open and connected + - compose the save message + - send message + - set request to sent + - else + - start service + */ + + struct mwStorageReq *req; + + req = request_new(srvc, item, cb, data, d_free); + req->action = action_save; + + srvc->pending = g_list_append(srvc->pending, req); + + if(MW_SERVICE_IS_STARTED(MW_SERVICE(srvc))) + request_send(srvc->channel, req); +} + diff --git a/src/st_list.c b/src/st_list.c new file mode 100644 index 0000000..949696a --- /dev/null +++ b/src/st_list.c @@ -0,0 +1,668 @@ + +/* + Meanwhile - Unofficial Lotus Sametime Community Client Library + Copyright (C) 2004 Christopher (siege) O'Brien + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include + +#include "mw_debug.h" +#include "mw_util.h" +#include "mw_st_list.h" + + +struct mwSametimeList { + guint ver_major; + guint ver_minor; + guint ver_micro; + + GList *groups; +}; + + +struct mwSametimeGroup { + struct mwSametimeList *list; + + enum mwSametimeGroupType type; + char *name; + char *alias; + gboolean open; + + GList *users; +}; + + +struct mwSametimeUser { + struct mwSametimeGroup *group; + + enum mwSametimeUserType type; + struct mwIdBlock id; + char *name; + char *alias; +}; + + +static void user_free(struct mwSametimeUser *u) { + struct mwSametimeGroup *g; + + g = u->group; + g->users = g_list_remove(g->users, u); + + mwIdBlock_clear(&u->id); + g_free(u->name); + g_free(u->alias); + g_free(u); +} + + +static void group_free(struct mwSametimeGroup *g) { + struct mwSametimeList *l; + + l = g->list; + l->groups = g_list_remove(l->groups, g); + + while(g->users) + mwSametimeUser_free(g->users->data); + + g_free(g->name); + g_free(g->alias); + g_free(g); +} + + +static void list_free(struct mwSametimeList *l) { + while(l->groups) + mwSametimeGroup_free(l->groups->data); + + g_free(l); +} + + +struct mwSametimeList * +mwSametimeList_new() { + + struct mwSametimeList *stl; + + stl = g_new0(struct mwSametimeList, 1); + stl->ver_major = ST_LIST_MAJOR; + stl->ver_minor = ST_LIST_MINOR; + stl->ver_micro = ST_LIST_MICRO; + + return stl; +} + + +void mwSametimeList_setMajor(struct mwSametimeList *l, guint v) { + g_return_if_fail(l != NULL); + l->ver_major = v; +} + + +guint mwSametimeList_getMajor(struct mwSametimeList *l) { + g_return_val_if_fail(l != NULL, 0); + return l->ver_major; +} + + +void mwSametimeList_setMinor(struct mwSametimeList *l, guint v) { + g_return_if_fail(l != NULL); + l->ver_minor = v; +} + + +guint mwSametimeList_getMinor(struct mwSametimeList *l) { + g_return_val_if_fail(l != NULL, 0); + return l->ver_minor; +} + + +void mwSametimeList_setMicro(struct mwSametimeList *l, guint v) { + g_return_if_fail(l != NULL); + l->ver_micro = v; +} + + +guint mwSametimeList_getMicro(struct mwSametimeList *l) { + g_return_val_if_fail(l != NULL, 0); + return l->ver_micro; +} + + +GList *mwSametimeList_getGroups(struct mwSametimeList *l) { + g_return_val_if_fail(l != NULL, NULL); + return g_list_copy(l->groups); +} + + +struct mwSametimeGroup * +mwSametimeList_findGroup(struct mwSametimeList *l, + const char *name) { + GList *s; + + g_return_val_if_fail(l != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + g_return_val_if_fail(*name != '\0', NULL); + + for(s = l->groups; s; s = s->next) { + struct mwSametimeGroup *g = s->data; + if(! strcmp(g->name, name)) return g; + } + + return NULL; +} + + +void mwSametimeList_free(struct mwSametimeList *l) { + g_return_if_fail(l != NULL); + list_free(l); +} + + +struct mwSametimeGroup * +mwSametimeGroup_new(struct mwSametimeList *list, + enum mwSametimeGroupType type, + const char *name) { + + struct mwSametimeGroup *stg; + + g_return_val_if_fail(list != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + g_return_val_if_fail(*name != '\0', NULL); + + stg = g_new0(struct mwSametimeGroup, 1); + stg->list = list; + stg->type = type; + stg->name = g_strdup(name); + + list->groups = g_list_append(list->groups, stg); + + return stg; +} + + +enum mwSametimeGroupType mwSametimeGroup_getType(struct mwSametimeGroup *g) { + g_return_val_if_fail(g != NULL, mwSametimeGroup_UNKNOWN); + return g->type; +} + + +const char *mwSametimeGroup_getName(struct mwSametimeGroup *g) { + g_return_val_if_fail(g != NULL, NULL); + return g->name; +} + + +void mwSametimeGroup_setAlias(struct mwSametimeGroup *g, + const char *alias) { + g_return_if_fail(g != NULL); + + g_free(g->alias); + g->alias = g_strdup(alias); +} + + +const char *mwSametimeGroup_getAlias(struct mwSametimeGroup *g) { + g_return_val_if_fail(g != NULL, NULL); + return g->alias; +} + + +void mwSametimeGroup_setOpen(struct mwSametimeGroup *g, gboolean open) { + g_return_if_fail(g != NULL); + g->open = open; +} + + +gboolean mwSametimeGroup_isOpen(struct mwSametimeGroup *g) { + g_return_val_if_fail(g != NULL, FALSE); + return g->open; +} + + +struct mwSametimeList *mwSametimeGroup_getList(struct mwSametimeGroup *g) { + g_return_val_if_fail(g != NULL, NULL); + return g->list; +} + + +GList *mwSametimeGroup_getUsers(struct mwSametimeGroup *g) { + g_return_val_if_fail(g != NULL, NULL); + return g_list_copy(g->users); +} + + +struct mwSametimeUser * +mwSametimeGroup_findUser(struct mwSametimeGroup *g, + struct mwIdBlock *user) { + GList *s; + + g_return_val_if_fail(g != NULL, NULL); + g_return_val_if_fail(user != NULL, NULL); + + for(s = g->users; s; s = s->next) { + struct mwSametimeUser *u = s->data; + if(mwIdBlock_equal(user, &u->id)) return u; + } + + return NULL; +} + + +void mwSametimeGroup_free(struct mwSametimeGroup *g) { + g_return_if_fail(g != NULL); + g_return_if_fail(g->list != NULL); + group_free(g); +} + + +struct mwSametimeUser * +mwSametimeUser_new(struct mwSametimeGroup *group, + enum mwSametimeUserType type, + struct mwIdBlock *id) { + + struct mwSametimeUser *stu; + + g_return_val_if_fail(group != NULL, NULL); + g_return_val_if_fail(id != NULL, NULL); + + stu = g_new0(struct mwSametimeUser, 1); + stu->group = group; + stu->type = type; + mwIdBlock_clone(&stu->id, id); + + group->users = g_list_append(group->users, stu); + + return stu; +} + + +struct mwSametimeGroup *mwSametimeUser_getGroup(struct mwSametimeUser *u) { + g_return_val_if_fail(u != NULL, NULL); + return u->group; +} + + +enum mwSametimeUserType mwSametimeUser_getType(struct mwSametimeUser *u) { + g_return_val_if_fail(u != NULL, mwSametimeUser_UNKNOWN); + return u->type; +} + + +const char *mwSametimeUser_getUser(struct mwSametimeUser *u) { + g_return_val_if_fail(u != NULL, NULL); + return u->id.user; +} + + +const char *mwSametimeUser_getCommunity(struct mwSametimeUser *u) { + g_return_val_if_fail(u != NULL, NULL); + return u->id.community; +} + + +void mwSametimeUser_setShortName(struct mwSametimeUser *u, const char *name) { + g_return_if_fail(u != NULL); + g_free(u->name); + u->name = g_strdup(name); +} + + +const char *mwSametimeUser_getShortName(struct mwSametimeUser *u) { + g_return_val_if_fail(u != NULL, NULL); + return u->name; +} + + +void mwSametimeUser_setAlias(struct mwSametimeUser *u, const char *alias) { + g_return_if_fail(u != NULL); + g_free(u->alias); + u->alias = g_strdup(alias); +} + + +const char *mwSametimeUser_getAlias(struct mwSametimeUser *u) { + g_return_val_if_fail(u != NULL, NULL); + return u->alias; +} + + +void mwSametimeUser_free(struct mwSametimeUser *u) { + g_return_if_fail(u != NULL); + g_return_if_fail(u->group != NULL); + user_free(u); +} + + +static void str_replace(char *str, char from, char to) { + if(! str) return; + for(; *str; str++) if(*str == from) *str = to; +} + + +static char user_type_to_char(enum mwSametimeUserType type) { + switch(type) { + case mwSametimeUser_NORMAL: return '1'; + case mwSametimeUser_EXTERNAL: return '2'; + case mwSametimeUser_UNKNOWN: + default: return '9'; + } +} + + +static enum mwSametimeUserType user_char_to_type(char type) { + switch(type) { + case '1': return mwSametimeUser_NORMAL; + case '2': return mwSametimeUser_EXTERNAL; + default: return mwSametimeUser_UNKNOWN; + } +} + + +static void user_put(GString *str, struct mwSametimeUser *u) { + char *id, *name, *alias; + char type; + + id = g_strdup(u->id.user); + name = g_strdup(u->name); + alias = g_strdup(u->alias); + type = user_type_to_char(u->type); + + if(id) str_replace(id, ' ', ';'); + if(name) str_replace(name, ' ', ';'); + if(alias) str_replace(alias, ' ', ';'); + + if(!name && alias) { + name = alias; + alias = NULL; + } + + g_string_append_printf(str, "U %s%c:: %s,%s\r\n", + id, type, (name? name: ""), (alias? alias: "")); + + g_free(id); + g_free(name); + g_free(alias); +} + + +static char group_type_to_char(enum mwSametimeGroupType type) { + switch(type) { + case mwSametimeGroup_NORMAL: return '2'; + case mwSametimeGroup_DYNAMIC: return '3'; + case mwSametimeGroup_UNKNOWN: + default: return '9'; + } +} + + +static enum mwSametimeGroupType group_char_to_type(char type) { + switch(type) { + case '2': return mwSametimeGroup_NORMAL; + case '3': return mwSametimeGroup_DYNAMIC; + default: return mwSametimeGroup_UNKNOWN; + } +} + + +static void group_put(GString *str, struct mwSametimeGroup *g) { + char *name, *alias; + char type; + GList *gl; + + name = g_strdup(g->name); + alias = g_strdup((g->alias)? g->alias: name); + type = group_type_to_char(g->type); + + str_replace(name, ' ', ';'); + str_replace(alias, ' ', ';'); + + g_string_append_printf(str, "G %s%c %s %c\r\n", + name, type, alias, (g->open? 'O':'C')); + + for(gl = g->users; gl; gl = gl->next) { + user_put(str, gl->data); + } + + g_free(name); + g_free(alias); +} + + +/** composes a GString with the written contents of a sametime list */ +static GString *list_store(struct mwSametimeList *l) { + GString *str; + GList *gl; + + g_return_val_if_fail(l != NULL, NULL); + + str = g_string_new(NULL); + g_string_append_printf(str, "Version=%u.%u.%u\r\n", + l->ver_major, l->ver_minor, l->ver_micro); + + for(gl = l->groups; gl; gl = gl->next) { + group_put(str, gl->data); + } + + return str; +} + + +char *mwSametimeList_store(struct mwSametimeList *l) { + GString *str; + char *s; + + g_return_val_if_fail(l != NULL, NULL); + + str = list_store(l); + s = str->str; + g_string_free(str, FALSE); + return s; +} + + +void mwSametimeList_put(struct mwPutBuffer *b, struct mwSametimeList *l) { + GString *str; + guint16 len; + + g_return_if_fail(l != NULL); + g_return_if_fail(b != NULL); + + str = list_store(l); + len = (guint16) str->len; + guint16_put(b, len); + mwPutBuffer_write(b, str->str, len); + + g_string_free(str, TRUE); +} + + +static void get_version(const char *line, struct mwSametimeList *l) { + guint major = 0, minor = 0, micro = 0; + int ret; + + ret = sscanf(line, "Version=%u.%u.%u\n", &major, &minor, µ); + if(ret != 3) { + g_warning("strange sametime list version line:\n%s", line); + } + + l->ver_major = major; + l->ver_minor = minor; + l->ver_micro = micro; +} + + +static struct mwSametimeGroup *get_group(const char *line, + struct mwSametimeList *l) { + struct mwSametimeGroup *group; + char *name, *alias; + char type = '2', open = 'O'; + int ret; + + ret = strlen(line); + name = g_malloc0(ret); + alias = g_malloc0(ret); + + ret = sscanf(line, "G %s %s %c\n", + name, alias, &open); + + if(ret < 3) { + g_warning("strange sametime list group line:\n%s", line); + } + + str_replace(name, ';', ' '); + str_replace(alias, ';', ' '); + + if(name && *name) { + int l = strlen(name)-1; + type = name[l]; + name[l] = '\0'; + } + + group = g_new0(struct mwSametimeGroup, 1); + group->list = l; + group->name = name; + group->type = group_char_to_type(type); + group->alias = alias; + group->open = (open == 'O'); + + l->groups = g_list_append(l->groups, group); + + return group; +} + + +static void get_user(const char *line, struct mwSametimeGroup *g) { + struct mwSametimeUser *user; + struct mwIdBlock idb = { 0, 0 }; + char *name, *alias = NULL; + char type = '1'; + int ret; + + ret = strlen(line); + idb.user = g_malloc0(ret); + name = g_malloc0(ret); + + ret = sscanf(line, "U %s %s", + idb.user, name); + + if(ret < 2) { + g_warning("strange sametime list user line:\n%s", line); + } + + str_replace(idb.user, ';', ' '); + str_replace(name, ';', ' '); + + if(idb.user && *idb.user) { + char *tmp = strstr(idb.user, "::"); + if(tmp--) { + type = *(tmp); + *tmp = '\0'; + } + } + + if(name && *name) { + char *tmp = strrchr(name, ','); + if(tmp) { + *tmp++ = '\0'; + if(*tmp) alias = tmp; + } + } + + user = g_new0(struct mwSametimeUser, 1); + user->group = g; + user->id.user = idb.user; + user->type = user_char_to_type(type); + user->name = name; + user->alias = g_strdup(alias); + + g->users = g_list_append(g->users, user); +} + + +/** returns a line from str, and advances str */ +static char *fetch_line(char **str) { + char *start = *str; + char *end; + + /* move to first non-whitespace character */ + while(*start && g_ascii_isspace(*start)) start++; + if(! *start) return NULL; + + for(end = start + 1; *end; end++) { + if(*end == '\n' || *end == '\r') { + *(end++) = '\0'; + break; + } + } + + *str = end; + return start; +} + + +static void list_get(const char *lines, struct mwSametimeList *l) { + char *s = (char *) lines; + char *line; + + struct mwSametimeGroup *g = NULL; + + while( (line = fetch_line(&s)) ) { + switch(*line) { + case 'V': + get_version(line, l); + break; + + case 'G': + g = get_group(line, l); + break; + + case 'U': + get_user(line, g); + break; + + default: + g_warning("unknown sametime list data line:\n%s", line); + } + } +} + + +struct mwSametimeList *mwSametimeList_load(const char *data) { + struct mwSametimeList *l; + + g_return_val_if_fail(data != NULL, NULL); + + l = mwSametimeList_new(); + list_get(data, l); + + return l; +} + + +void mwSametimeList_get(struct mwGetBuffer *b, struct mwSametimeList *l) { + char *str = NULL; + + g_return_if_fail(l != NULL); + g_return_if_fail(b != NULL); + + mwString_get(b, &str); + list_get(str, l); + g_free(str); +} +